diff --git a/.gitignore b/.gitignore index 1170717..a73c482 100644 --- a/.gitignore +++ b/.gitignore @@ -134,3 +134,34 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* +.vercel +.env* + +# Author multipacks (regen via scripts/* — do not ship in Vercel) +public/models/vfx/**/*_src.glb +public/models/vfx/**/*_src.gltf + +# Duplicate override mirror (runtime SSOT is public/skills/production) +/skills/ + +# Large local weapon sources until R2 (keep on disk for lab) +public/models/weapons/t0-flintlock.glb + + +# >100MB — keep local / CDN later +public/models/fish/species/rare/ocean_creature.glb + +# Local QA dumps (never ship) +_qa_* +deploy-out*.txt +tmp/ +.tmp/ + +# Loose fish extracts (tracked SSOT lives in poles/lures/docks/species) +public/models/fish/*.glb +public/models/fish/_bundle/ + +# WIP scenery / class-form dumps until convert + R2 +public/models/class-forms/ +public/models/island-scenery/ + diff --git a/README.md b/README.md index 412c91d..4b6737b 100644 --- a/README.md +++ b/README.md @@ -1,322 +1,418 @@ -# Bending Sandbox +# Grudge Casting Abilities (Three.js) -An Avatar-inspired elemental bending sandbox built with **Three.js**, **Vite** and hand-written -**GLSL**. Draw a path on the ground with the mouse, release, and a Fire, Water, Earth or Air -ability travels that spline and detonates at the end. Every visual parameter is editable at -runtime through an in-game VFX editor, and the settings can be saved as presets. +**Warlords-era lab** for Grudge Studio: Toon RTS hero, DRC combat, catalog weapon skills, elemental path cast + linear skillshots, three-layer terrain, windsurf ride, and production skill/VFX authoring before fleet ship. -The same gesture has a second meaning. Switch to **walk mode** and the drawn path is not cast but -*ridden*: the avatar leaps onto the head of the stroke, folds into the meditation pose on a -spinning ball of air and rides the path to its end, banking into every turn. +**Repo:** [MolochDaGod/CastingAbilitiesThreeJS](https://github.com/MolochDaGod/CastingAbilitiesThreeJS) +**Live (control plane):** [casting.grudge.studio](https://casting.grudge.studio) — dev → production lab +**Weapon skill DO:** [weapon-skills.grudge-studio.com](https://weapon-skills.grudge-studio.com/api/health) +**Live (Vercel):** [casting-abilities-threejs.vercel.app](https://casting-abilities-threejs.vercel.app) +**Lab contract:** `/api/v1/casting-lab-contract.json` · SDK `import … from 'casting-abilities/sdk'` +**API/DB:** same-origin `/api/*` → Railway `grudge-api` → Postgres · `docs/CASTING_DEPLOY_ENV_SSOT.md` +**Skills DO:** `docs/WEAPON_SKILL_DO_SSOT.md` +**SSOT index:** `docs/SSOT_INDEX.md` · **SDK export:** `docs/CASTING_SDK_EXPORT_SSOT.md` +**Team:** Vercel `grudgenexus` · project `casting-abilities-threejs` + +### Hosts + +| Host | Role | +|------|------| +| `casting.grudge.studio` | **Primary lab** — equip / promote / handoff | +| `weapon-skills.grudge-studio.com` | CF Worker + Durable Object (CNAME live) | +| `casting-abilities-threejs.vercel.app` | Project hostname | +| `casting.grudge-studio.com` | Legacy (optional) | --- -## Quick start +## Stack (fleet package gate) -```bash -npm install -``` +| Package | Role | +|---------|------| +| `three` ^0.185 | **Vanilla** scene graph · GLTF · AnimationMixer · Draco + Meshopt + KTX2 | +| `@dimforge/rapier3d-compat` ^0.19 | Ground heightfield + human CCT (SI capsule) | +| `xstate` | Player activity machine | +| VFX beauty | [vfxgrudge.puter.site](https://vfxgrudge.puter.site/) → `src/vfx/VfxDirector.js` | -```bash -npm run dev -``` +**Not R3F** — this lab is vanilla Three + Vite. Forge uses R3F elsewhere. +**Engine / deploy / client handoff:** `docs/ENGINE_DEPLOY_SSOT.md` +**Dev:** `npm run dev` · **Prod:** `npm run deploy:prod` (build + `vercel --prod`) -Then open the URL Vite prints (default ). +**Do not** reintroduce Mixamo FBX as play, Meshy heroes, dual mixers, dual physics, dual cameras, dual Draco loaders, or OrbitControls writing mid-TPS combat. -```bash -npm run build -``` +### Render layers vs terrain L0–L3 (not the same) -```bash -npm run preview +| System | Meaning | +|--------|---------| +| `LAYER` WORLD/VFX/DISTORTION/CONTACT | Three.js camera bits (`src/core/Layers.js`) | +| `TERRAIN_LAYER` L0–L3 | Map authoring labels only | +| Dummy overhead bars | HTML (`OverheadNameplates`) — not a second 3D layer | + +### Spell SFX (no death moan on cast) + +Default cast = `cast_ramp` · land = `impact_magic` a/b/c. +`cast_chant` (blood shaman) only for explicit blood/void skill ids. + +### Fishing profession (3D harvest) + +**Start:** equip `t0-fishing-pole` (`?t0=t0-fishing-pole`) or **Shift+F** · **RMB** aim · **LMB** cast lure · **S/RMB** snag · **wheel** reel/slack · **LMB/RMB** move Palworld bar zone. + +Doc: `docs/FISHING_PROFESSION_SSOT.md` · assets `public/models/fish/` · UI `public/ui/fishing/` + +### Gameplay + UX gates (must pass) + +After boot: feet on heightfield · one mixer + anim state · Fortnite **shoulder TPS** (5.5–6 m, Orbit off in combat) · weapon skills · loco/physics · HUD. + +```js +window.__gameplayGates // { ok, summary, gates[] } ``` -### Assets +Doc: `docs/GAMEPLAY_UX_GATES_SSOT.md` · code `src/core/gameplayGates.js` +Camera: combat = TPS shoulder; equip/builder = orbit; focus = tighter 5.5 m. -Two binary assets are served from `public/` and loaded automatically at boot: +### VFX Studio (singular skill authoring shell) -| File | Purpose | -| --- | --- | -| `public/models/Standing Idle.fbx` | Rigged character **and** its idle animation clip | -| `public/hdri/spruit_sunrise.hdr` | HDR probe used for image-based lighting and the sky | +**Toggle:** **V** / HUD **VFX** · `src/ui/vfxStudio/VfxStudio.js` +**Doc:** `docs/SKILL_AUTHORING_STUDIO_SSOT.md` · map `src/config/skillAuthoringSSOT.js` -The FBX is a Mixamo export: it contains a skinned mesh plus a single animation stack, so the -character and the idle clip come from the same file. Its texture paths are absolute local paths -baked in by the exporting tool and cannot resolve over HTTP; the loader redirects those to a -neutral placeholder and falls back to a muted cloth tone (see `loaders/AssetLoader.js`). -Replacing the FBX with a textured one works without code changes — real textures are kept. +Tabs: **Pipeline · Skill · Delivery · VFX · Linear · Samples · Knobs · Export** +uMMORPG-shaped (scriptable skill → delivery → effect modules → export). +Knobs tab hosts existing lil-gui (`settings.js` live). Samples set meshId + colors. ---- +### Loaders (shared pipeline) -## Controls +| Decoder | Pin | +|---------|-----| +| Draco | `gstatic` versioned **1.5.7** | +| Meshopt | three `meshopt_decoder` | +| KTX2 / Basis | `three@0.185.1` basis transcoder · bind after WebGLRenderer | -| Input | Action | -| --- | --- | -| **Hold left mouse + drag** | Draw a path on the ground | -| **Release** | Cast the selected element along that path — or ride it, in walk mode | -| **Right mouse + drag** | Orbit the camera (zoom is locked) | -| **M** | Switch between **cast** and **walk** mode | -| **1 / 2 / 3 / 4** | Fire / Water / Earth / Air | -| **Q / E** | Cycle elements | -| **G** | Show/hide the VFX editor | -| **C** | Clear all active effects (and cancel a ride) | -| **P** | Pause / resume the simulation | -| **T** | Toggle the character between the standing idle and the meditation sit | -| **H** | Hide the controls panel | - -Left mouse is reserved for drawing, which is why orbiting is bound to the right button. +Code: `src/loaders/gltfPipeline.js` · `AssetLoader` · **one** `sharedGltfLoader()` for weapons / projectiles / equip. +Audit: `docs/LOADER_DRACO_KTX2_AUDIT.md` · health: `docs/SYSTEMS_HEALTH_AUDIT.md` --- -## Project layout +## Combat · weapons · VFX (production pattern) + +**Product language = weapons** (not free “elements”). Catalog items and staff brands own play. + +| Layer | Role | +|-------|------| +| Catalog weapons | `t0-weapons` / `master-weapon-prefabs` — equip owns skills + mesh | +| Catalog skills | `master-weaponSkills` — **never invent skill ids** | +| Production package | `weaponSkillProduction.js` — anim · VFX · physics · statuses | +| Statuses | push · freeze · stun · slow · burn · root · knockup (`skillStatusSystem`) | +| Staff weapons (1–6) | Fire…Arcane brands · path cast pools (`WEAPON_STAFF_IDS`) | +| **Hold pose residual** | `applyWeaponHoldPose(mixer, gait, kind)` after mixer — see below | + +### Weapon hold pose (play equip · same SSOT as Main Panel) +**Canonical source:** ObjectStore `js/grudge6-weapon-hold-pose.js` +**Play mirror (keep in sync):** `src/character/weaponHoldPose.js` + +After equip (`equipWeaponById` sets `character.weaponHoldKind`), every frame: + +```text +mixer.update(dt) +→ applyWeaponHoldPose(mixer, this._gait, weaponHoldKind) // residual on R/L hand +→ pistol reload overlay (if active — hold residual skipped) ``` -src/ - abilities/ Ability base class, the four elements, the pooling manager - animation/ FBX character loading, material conversion, AnimationMixer, - procedural cross-legged meditation pose (SittingPose.js), - walk mode's leap → ride → dismount sequence (WalkController.js) - assets/ Procedural geometry generators (rocks, ground plates, tower, shards) - config/ settings.js — the single source of truth for every parameter - core/ App, Renderer, CameraRig, Time, Layers, shared frame uniforms - effects/ Ribbon builder, cast trail, decals, bursts, lights, shake, flash, - the air scooter the avatar rides in walk mode (AirScooter.js) - input/ InputManager (events) and PathDrawer (raycast → spline) - loaders/ AssetLoader with a shared LoadingManager - materials/ Fire volume / Ocean water / Wind / Rock / Trail / Distortion / - Air scooter materials - particles/ GPU particle system + engine and rate emitters - postprocessing/ Composer pipeline, grade shader, distortion shader - shaders/lib/ Shared GLSL: noise library, common helpers - ui/ HUD, lil-gui editor, preset manager, styles - utils/ Maths, colour cache, pooling, disposal, shader patching - world/ Environment (stage lighting), floor, dust, contact shadows + +Wired in `CharacterController.update` · kind from `equippedWeaponRuntime`. +**Do not** add a second grip/hold module — extend the ObjectStore SSOT and re-copy. + +### Weapon mesh collider (cylinder + 0.02 m pad) + +**Doc:** `docs/WEAPON_MESH_COLLIDER_SSOT.md` · **Code:** `src/character/weaponMeshCollider.js` + +| Step | Action | +|------|--------| +| 1 | Resolve weapon **mesh** (`WeaponAttach` child or kit sword/axe/…) | +| 2 | Sample verts → principal axis (blade/barrel) | +| 3 | Cylinder radius = max radial + **0.02 m** pad | +| 4 | Markers: tip / grip / mid under mesh (ride anim) | +| 5 | `character.rebuildWeaponVolume()` after equip | + +Feeds: **tip trail**, **apex residual projectiles**, **IK grip**, **effects**, **SFX**, **parry** (`weaponVolumeBlocks` / `tryParryBlock`). + +### Melee weapon tip · trail · apex residual + +| Piece | Module | +|-------|--------| +| Tip (mesh cylinder / muzzle) | `CharacterController.getWeaponTip` ← `weaponVolume` | +| Swing ribbon | `src/vfx/weaponTipTrail.js` · samples tip each frame | +| Apex (combo hit frame) | `settings.residual.hitFrameDelay` → getsuga + fire blur + physics past blade | +| Beyond-blade hit | `beyondBladeM` + contact from volume.radius | +| Knobs | Editor → Melee residual · `settings.residual` | + +```text +equip → rebuildWeaponVolume (mesh cylinder + 0.02 m) +attack start → tipTrail.beginSwing + · each frame: sample tip → ribbon (blur if fireTrail) + · at apex: residual from tip+beyondBlade · fire path · physics proj (radius from mesh) +combo step 2/3 / finisher → new beginSwing (apex per hit) +parry (C) → open _parryUntil · rebuild volume +incoming player hit (point) → invuln? cancel · tryParryBlock(point)? cancel dmg/status/push + · wired: _onProjectileHit + SkillStatusSystem.applyHit ``` +| Staff normal | Hotbar **1** + **focus LMB** → shared stream orb | +| Linear skillshots | ice / thunder / meteor / beam / snare / glacier (`elementalLinearCast`) | +| Path cast | Fire/Water/Earth/Wind Ability pools (draw stroke under staff weapons) | +| Mesh delivery | orbs · rocks · freeze nova · bubbles · dual arrows · bullets | ---- +```bash +# Full T0 weapon play-stack checklist (mesh · pack · loco · skills · VFX · deploy) +node scripts/scaffold-t0-weapon-play.mjs --id t0-sword +# Per-skill production override +node scripts/scaffold-weapon-skill.mjs --id staff_fire_bolt --weapon STAFF +# Split multipacks → SI production meshes +node scripts/split-gd-orbs-and-charge.mjs +node scripts/split-element-attack-meshes.mjs +``` -## How it fits together +**Agent skill:** `casting-t0-weapon-play` · **Doc:** `docs/T0_WEAPON_PLAY_STACK_SSOT.md` +Also: `WEAPON_SKILL_PRODUCTION_SSOT.md` · `T0_WEAPONS_SSOT.md` · `ELEMENTAL_LINEAR_CAST_SSOT.md` · `STAFF_NORMAL_ORBS_CHARGE_SSOT.md` -### Settings are the API +### Adding a T0 weapon (summary) -`src/config/settings.js` holds every tweakable value. Nothing else owns that state: shaders, -particle systems, lights and post passes *read* those objects every frame. That is what makes -the editor work with no rebuild — moving a slider changes abilities that are already in flight, -future casts, the environment and the post stack at once. Preset loading deep-merges *into* the -same objects so every live binding stays valid. +1. Catalog row exists (`t0-weapons` + prefab) — do not invent ids +2. `weaponType` → anim pack (`weaponAnimPack.js`) +3. Equip → mesh · pack · hotbar (`equipWeaponById`) +4. Scaffold each skill → fill production override +5. Projectiles = SI splits only (`public/models/vfx/…`) +6. Dual loadout: Weapon 1/2 paperdoll · combat **Tap Q** +7. Smoke `?t0=` · deploy Vercel when code/assets change -```js -import { settings } from './config/settings.js'; -settings.fire.flameHeight = 5; // visible on the next frame -settings.global.speed = 0.35; // slow every element down -``` +### Controls (combat) -### Drawing → spline → ability +| Input | Action | +|-------|--------| +| **Tap Q** | Dual weapon loadout swap (Weapon 1 ↔ Weapon 2 · mesh · anim pack · skills) | +| **Hold Q** | Mode radial (combat ↔ harvest) | +| **1–4** | Weapon skills (catalog hotbar) | +| **RMB** | Focus aim (TPS) | +| **Focus + LMB** | Staff/wand → **slot 1 normal** (orb stream) | +| **F** | Weapon primary skill | +| **Hold LMB + drag** | Path cast stroke (non-focus / freeride) | +| **Alt+Shift+Q/E/R/F/V/G** | Arm linear skillshot (MOBA aim) | + +Dodge stays **AA / DD / X** — **Q never dodges**. -`PathDrawer` raycasts the pointer against the ground plane, drops samples closer together than -`input.minPointDistance` (jitter filter), exponentially smooths the rest, and builds a -`CatmullRomCurve3`. The same resampled polyline feeds the preview ribbon and the ability, so the -trail you drew and the path travelled are guaranteed to agree. Strokes shorter than -`input.minPathLength` are discarded. +--- + +## Assets (SSOT) + +### Character (runtime CDN — not bundled) -`Ability.followPath()` advances by **arc length**, not by curve parameter, so travel speed is a -true metres-per-second regardless of frame rate or how unevenly the path was drawn. +| Resource | URL | +| --- | --- | +| **Play kit ★** | `https://assets.grudge-studio.com/asset-packs/toon-rts-characters/glb/characters/{human\|barbarian\|elf\|orc\|undead\|dwarf}.glb` | +| Race atlas (optional) | `https://assets.grudge-studio.com/textures/grudge6/…/*.webp` | +| Gear presets | `https://assets.grudge-studio.com/api/v1/grudge6-gear-presets.json` | +| Idle clip | `https://open.grudge-studio.com/anims/baked/magic/standing%20idle.json` | +| Cast clip | `https://open.grudge-studio.com/anims/baked/magic/standing%201h%20cast%20spell%2001.json` | +| Walk clip | `https://open.grudge-studio.com/anims/baked/magic/Standing%20Walk%20Forward.json` | -The path is always drawn on the ground, but an element does not have to crawl along it: -`Ability.pathHeight(u)` lifts the trajectory, and the head, the trailing window, the dynamic -light and every particle emission point follow it together. Fire overrides it to fly. +Code: `src/config/grudge6SSOT.js` + `src/config/assets.js` +Deploy path: `deployToonPlayKit` (`src/character/toonKitPlay.js`) — bone SI fit, **yaw 0**, mesh_ids equip. -### Walk mode: the same stroke, ridden +**Not play defaults** (author/lab only): `models/grudge6/races/*_Characters.glb` / `.fbx` -`settings.mode` decides what a finished stroke means. In `'casting'` it goes to `AbilityManager`; -in `'walk'` it goes to `WalkController`, which runs a four-phase sequence over the curve -`PathDrawer` already built: +### Windsurf ride (shipped with site) -| Phase | What happens | +| Resource | Path | | --- | --- | -| `leap` | A parabolic arc from wherever he stands to the head of the path. He turns in the air onto the path's own heading, and at `walk.tuck` of the way through he starts folding into the meditation pose so he arrives already seated. | -| `ride` | The air ball puffs into existence under him. He follows the curve **by arc length** at `walk.speed` m/s, easing up over `walk.accel` and gliding to a stop over the last `walk.brake` seconds' worth of path. | -| `dismount` | The ball blows apart, he steps off forward and settles onto the floor. | -| `idle` | Placement is handed straight back to the idle clip. | - -Banking is derived, not authored: the controller tracks how fast the heading is actually swinging -and rolls the body about its own forward axis in proportion, so tight corners lean hard and -straights run level. The roll lives on a `tilt` joint underneath the character root, which keeps it -from fighting the heading for the same rotation. - -Drawing a new path mid-ride is allowed — he simply leaps off whatever he is doing onto the head of -the new one. `walk.returnHome` makes him hop back to where the whole thing started once the path is -finished; by default he stays at the far end. - -### The air scooter - -`windball.jpg` (an air scooter, from the show) is the reference, not an asset: the ball is a shader -sphere, not a texture. `AirScooterMaterial` partitions the sphere into `walk.bands` streamlines of -constant *longitude + twist × latitude* — the family of curves that spirals from one pole to the -other — and draws one strand per lane, warped by an fbm field so the strands weave and break up -instead of reading as a wireframe globe. The pattern rotates about the mesh's local Y axis, which -`AirScooter` aligns with the rider's side vector, so the swirl rolls the way the ball rolls. On the -dark stage the arcs come out as the bright part under additive blending, which is the standard -translation of a light-on-light reference. - -Keep `walk.bands` on whole numbers: the longitude coordinate wraps at ±π, so a fractional band -count leaves a visible seam down one side. - -### Fire is a raymarched volume - -Fire is not a textured ribbon like earth and air. `VolumetricFireMaterial` renders an -actual volume: the mesh is only a camera-facing proxy hull around the flight path, and each -fragment reconstructs the path's local frame from the hull's `aCenter` / `aTangent` attributes, -fires a ray from the camera and integrates through a density field wrapped around that axis — -a capsule falloff eroded by three octaves of noise that stream backwards along the path and -climb with buoyancy, stretched upward so tongues lick off the top. - -Emission is radiated as `pow(heat, 2.4)` off a geometric temperature (hot on the axis, cooling -outward and backward through the wake), which is what blows the core out to white while the -fringe stays deep red. Soot absorbs, so the cool gas genuinely *occludes* the background instead -of adding to it — hence premultiplied "over" blending rather than the additive blending the rest -of the VFX use. The march is clipped by the depth prepass, so the flame is correctly occluded by -the ground and the character. - -`fire.volumeSteps` is the quality/cost dial: 26 samples per pixel costs ~1.3 ms at 960×540. - -### Water is a raymarched surface - -Water uses the same proxy-hull trick as fire and then does the opposite thing with it. -A flame is emissive gas, so fire integrates *through* its medium; water has a surface, and -everything that reads as water happens on it. `OceanWaterMaterial` marches for the first sign -change of +| Package GLB | `public/models/ride/windsurf_package.glb` (~11.7 MB) | +| Hoverboard fallback | `public/models/ride/hoverboard.glb` | +| IK sockets | `public/models/ride/ride.manifest.json` | +| IK reference | `public/models/ride/ik-reference.png` | -``` -field(p) = radius(u) * (1 + swell + chop) − distanceToAxis(p) -``` +Live: `https://casting-abilities-threejs.vercel.app/models/ride/…` +Code: `HoverboardRide.js` + `RideIK.js` (feet → deck, hands → boom/`sailRail`) -refines that crossing with four bisections, takes the gradient of the same field as the normal — -so every ripple in the field is a real ripple in the lighting — and shades the point: Schlick -fresnel over the HDR probe plus one tight sun glint, a Beer-Lambert depth tint over the -*analytic* chord through the tube (thin edges stay turquoise, the belly goes deep blue), backlit -translucency through thin crests, and foam where the swell peaks upward, where a ridged octave -erodes the surface, and at the silhouette — all broken into cells by a voronoi so it reads as -bubbles rather than white paint. +### VFX meshes (shipped splits — SI) -The glint and the backlight are aimed with the stage's key light, so the water is lit from where -the rest of the scene is lit from. The impact lands as a crown of jets, a foam sheet that washes -outward over wet stone and then drains back into a ring, and expanding surface rings. +| Pack | Path | Use | +|------|------|-----| +| Element orbs | `public/models/vfx/orbs/orb-*.glb` | Staff normal projectiles (gd_orbs split) | +| Charge shell | `public/models/vfx/charge/staff-charge.glb` | Cast charge (kamehameha bake) | +| Earth rocks | `public/models/vfx/rocks/rock-0..7.glb` | Nature/earth pull → linear/aimed | +| Arrows | `public/models/vfx/arrows/arrow-path.glb` · `arrow-loft.glb` | Linear path vs lofted throw/trap | +| Summons | `public/models/vfx/summons/` | Fire fist / ice shard (legacy heavy) | +| Skill overrides | `public/skills/production/.json` | Optional per-skill production tweaks | -`water.volumeSteps` is its quality/cost dial. +**Never** load multipack sources whole as projectiles (`*_src.glb` = author only). -### Earth paves the ground, then breaks it +### Local only -Earth is the one element made of solid geometry rather than shaders. It plays in three beats. -As the head travels it *paves*: rows of procedural stone plates (`createSlabGeometry`) surface -flush with the floor in a band `earth.crustWidth` wide, overlapping heavily so the crust reads as -one continuous slab. A fracture wave then follows the head by `earth.crackDelay` and breaks that -crust — each plate levers up on its low edge, tips over or drops into the seam, slides apart and -coughs out dust and chips. The cast ends with a tower (`createTowerGeometry`, an obelisk on a -stepped plinth) climbing out of the floor while a ring of boulders is shouldered up around its -base, and everything sinks back after `earth.towerHold`. +| Resource | Path | +| --- | --- | +| HDRI | `public/hdri/spruit_sunrise.hdr` | + +### HUD · Bars pack (shipped) -Plates, boulders and the tower are three meshes — two `InstancedMesh` and one `Mesh` — sharing -`RockMaterial`, so all of it casts and receives the scene's cascaded shadows. Each gets its own -material instance: one material used by both an instanced and a non-instanced mesh compiles two -program variants and CSM only tracks the uniforms of the last one it saw. `mossAmount` differs -per instance because the moss term keys off upward-facing normals, which a field of flat ground -plates would otherwise saturate completely. +Product picks from `D:\Games\Models\bars-hud-pack` → `public/hud/bars/`. +SSOT: `src/ui/barsHudUi.js` · styles `bars-hud.css` · world plates `OverheadNameplates.js`. -### Adding a fifth element +| Role | File | +|------|------| +| **Player unit frame** | `unit-frames/unit_frame_009.png` | +| **Enemy overhead HP** | `overhead/overhead_health_003.png` | +| **Ally overhead HP** | `overhead/overhead_health_001.png` | +| **Bar fillers** | `fillers/health_fill_*` · `mana_fill_*` · `stamina_fill_*` | -1. Add a settings block in `config/settings.js` and an entry in `ELEMENTS` / `ELEMENT_META`. -2. Subclass `Ability` and implement `createShaders`, `createParticles`, `onTravel`, `onImpact`, - `onFade`. -3. Register the class in `abilities/AbilityManager.js`. -4. Add an editor folder case in `ui/Editor.js#_buildElement`. +- Player TL frame = `009` chrome + fillers for HP/MP/STA (CraftPix portrait layers under). +- Training dummies / hostiles = world overhead **003** (fill tracks `userData.hp01`). +- Ally strip = **001** chrome when party members exist. +- CraftPix hotbar / cast bar stay under `public/ui/craftpix/` (complement, not replaced). -Everything else — pooling, path following, lights, phases, camera framing — is inherited. +### Paperdoll · dual weapons (Main Panel **I**) -### Particles +No belt / amulet / ring / cloak. Slots: -`particles/ParticleSystem.js` is a GPU-simulated, instanced-quad system. Motion (velocity, -gravity, analytic drag, turbulence, vortex swirl), size-over-lifetime, the colour gradient and -alpha fade are all evaluated in the shader from per-instance attributes; the CPU only ever writes -spawn data, and only the slots that changed are uploaded. Particles live in a ring buffer, so -spamming abilities recycles slots instead of allocating. Silhouettes (soft, smoke, streak, leaf, -rock chip, ring) are procedural — there are no sprite textures anywhere in the project. +| Column | Slots | +|--------|--------| +| Left | Head · Body · Arms · Legs · Shoulders · **Relic** | +| Right | **Weapon 1** · **Off 1** · **Weapon 2** · **Off 2** · **Back** · Mount | -### Render pipeline +Combat **Tap Q** swaps set A ↔ B (`equippedWeaponRuntime.swapWeaponLoadout`). +Code: `src/ui/mainPanelSlots.js` · `src/combat/equippedWeaponRuntime.js`. -Per frame: +--- -1. **Depth prepass** — the opaque world into a half-res packed-depth buffer. Every VFX shader - samples it for soft intersections, so nothing cuts a hard line into the ground. -2. **Distortion pass** — meshes on the distortion layer write screen-space UV offsets into a - second half-res buffer (heat shimmer, water refraction, air pressure waves). -3. **Composer** — scene → refraction warp → bloom → tone map (ACES) → grade. +## Terrain (three layers) -The grade pass folds chromatic aberration, lift/gain/contrast/saturation/temperature, vignette, -film grain and the impact flash into one resample. +| Layer | Role | Code | +|-------|------|------| +| **L0** | Height field (one sample) | `IslandHeightfield` · Rapier heightfield | +| **L1** | Surface ground colors | heightfield mesh | +| **L2** | Grass + growing forest | `StylizedGrassLayer` · `GrowingForest` | +| **L3** | Harvest rocks | `DevIslandHarvest` | +| Water | Ocean sibling | `StageWater` | -Shadows come from a single directional light whose orthographic shadow camera is re-centred on -the character each frame and fitted to a 52 m box at 4096² (~1.3 cm/texel). The `three/addons` -CSM module was tried first and removed: it replaces three's `lights_fragment_begin` chunk -*globally*, so any material not explicitly registered with it silently loses all directional -lighting. For a play area this size, one tight cascade is sharper and far less fragile. +Patterns: snakey-locomotion · three-stylized · forestoutline · Rapier terrain. +Docs: `THREE_LAYER_TERRAIN_SSOT.md` · `TERRAIN_PHYSICS_SSOT.md` -Contact shadows are a real render: the character's depth is captured from below into a 256² -target, blurred twice and projected onto the ground. +Aim / path / feet all use **one** `terrain.sample` via `projectToTerrain`. --- -## Editor and presets +## Controls -Press **G** for the panel. Folders: Presets, Global, Cast trail, the four elements, -Environment, Post processing, Camera, Character, Walk mode. +| Input | Action | +| --- | --- | +| **Hold LMB + drag** | Draw path (equip / freeride cast) | +| **Release** | Cast element — or **windsurf path course** in walk mode | +| **RMB** | Focus aim (combat) · orbit in equip | +| **Focus + LMB** | Staff normal (slot 1) | +| **M** | Cast ↔ **walk** (windsurf vehicle mode) | +| **Space** (walk, not riding) | Deploy windsurf **vehicle** (frontflip + board) | +| **E** (mounted) | **Get off** — unparent, remove board, land controller | +| **WASD** | Land move (DRC) · freeride boat thrust/turn when mounted | +| **1–4** | Weapon skills (combat) / elements (equip) | +| **Tap Q** | Combat: swap Weapon 1 ↔ 2 (skills + loco) · Harvest: return combat | +| **Hold Q** | Mode radial (combat ↔ harvest) | +| **I** | Inventory / paperdoll (dual weapons · relic · back) | +| **F** | Weapon primary skill | +| **G** | VFX editor | +| **C** | Clear effects / hard cancel ride | +| **P** | Pause | +| **H** | Help | +| **Alt+V/B/F/G/T/C** | Sandbox VFX previews | +| **Alt+Shift+Q/E/R/F/V/G** | Linear skillshot arm | + +--- -- **Global** multipliers scale every element at once (speed, glow, noise, particles, lights, - distortion, explosion intensity, camera shake, animation speed, time scale…). -- **Per element** folders expose the full parameter set for that element — fire's flight height, - volume radius/density/soot/raymarch steps and its gradient, water's surge, crest, chop, depth - tint, glint and foam, earth's crust width/plate size/crack delay/tower height/shake duration, - spiral radius/vortex strength/tornado size, and so on. -- **Walk mode** holds the mode switch itself plus the leap arc, ride speed and braking, bank - angle and response, the air ball's streamlines/twist/turbulence/colours, and its dust and light. -- **Presets** save to `localStorage`, and can be duplicated, deleted, exported to JSON, - imported from JSON, or reset to the shipped defaults. +## Character + ride stack -Presets are plain snapshots of the settings tree, so an exported file is readable and editable -by hand. +- **Loader:** shared glTF pipeline (Draco + Meshopt + KTX2 after renderer bind) +- **Clone:** SkeletonUtils / deployToonPlayKit +- **Equip:** mesh_ids visibility (`EquipmentManager`) + catalog hand mesh +- **Anims:** Bip001 magic + sword_shield packs (Open baked JSON) · **one mixer** +- **Combat:** `DrcCombatController` + Rapier CCT + TPS camera + production skills +- **Ride vehicle:** walk mode → deploy → **parent under deckCenter** → **RideIK** until **E** get-off → board removed + +``` +[ ] Height ~1.55–2.05 m (bone SI fit) +[ ] Feet grounded (structural min.y / bone feet), not pelvis-as-feet +[ ] Toon play GLB only (asset-packs/toon-rts-characters/…) +[ ] One AnimationMixer; RideIK only while `_rideActive` / mounted +[ ] windsurf_package.glb 200 on prod + manifest sockets +[ ] Dismount: vehicle gone + character.restoreFromRide land loco +[ ] No bare GLTFLoader (use sharedGltfLoader) +``` --- -## Performance notes +## Quick start -- Abilities, decals, bursts and particles are pooled; a warm session allocates almost nothing - per frame. -- Ribbon geometry is pre-allocated once per ability and rewritten in place with partial buffer - uploads. -- The six dynamic point lights are created at boot and parked at zero intensity rather than - added and removed — changing the light count forces three to recompile every material. -- Shadow maps update exactly once per frame even though the scene is rendered several times. -- `renderer.compileAsync()` runs during boot so the first cast never stutters on shader compile. -- Pixel ratio is capped at 1.75; the depth and distortion buffers are half resolution. +```bash +npm install +npm run dev +``` + +```bash +npm run build +npm run preview +``` -Live counters (FPS, live particles, draw calls, active abilities) are in the top-right of the HUD. +First boot needs network for race kits + baked clips. --- -## Known rough edges +## Deploy (Vercel) + +```bash +npm run build +vercel --prod +``` + +Linked project: **casting-abilities-threejs** (`grudgenexus`). +`vercel.json`: Vite · `dist` · COOP header. -- The impact bursts read better on Fire and Water than on Earth and Air; the burst shell could - use more art direction (it currently reads as a faceted shard at some angles). -- Water's thickness is an analytic chord through the tube rather than a marched exit point, so - the depth tint ignores the wave displacement it is passing through. It is hidden by foam at the - crests, but a very slow, very close shot can show the belly reading slightly too shallow. -- The stage floor is a single flat plane, so it cannot show the fog rolling over uneven ground — - the illusion depends on the fog and the backdrop sharing one colour. -- The heat-distortion buffer is half resolution, so very thin shimmer detail is soft. +| What ships in `dist` | What loads at runtime | +| --- | --- | +| App JS/CSS, HDRI, **ride GLBs**, `hud/bars/*`, CraftPix UI | Toon RTS kits, atlases, anim JSON (CDN / Open) | --- -## Licence +## Project layout + +``` +src/ + abilities/ Element path-cast Ability pools + animation/ CharacterController, WalkController (one mixer) + character/ toonKitPlay, RideIK, EquipmentManager, WeaponMeshAttach + combat/ DRC, skill production, statuses, delivery, linear plan + loaders/ gltfPipeline (Draco/Meshopt/KTX2), AssetLoader + config/ settings, assets, grudge6SSOT, worldScale + effects/ HoverboardRide, trails, bursts + physics/ Rapier world + heightfield + skillshot/ Linear skillshots (ice/thunder/meteor/…) + ui/ HUD (bars pack + CraftPix), OverheadNameplates, inventory, AdminHub + vfx/ VfxDirector, staffOrbVfx, elementAttackVfx + world/ IslandHeightfield, grass, forest, StageWater, harvest +public/ + models/ride/ windsurf_package.glb + manifest + models/vfx/ orbs, charge, rocks, arrows, summons + skills/production/ optional skill override JSON + hdri/ +docs/ CASTING_LAB · WEAPON_SKILL_PRODUCTION · THREE_LAYER_TERRAIN · LOADER audit … +scripts/ split orbs/rocks · scaffold-weapon-skill +``` + +### Key docs + +| Doc | Topic | +|-----|--------| +| `CASTING_LAB_SSOT.md` | Lab macro map | +| `WEAPON_SKILL_PRODUCTION_SSOT.md` | Scriptable production skills | +| `ELEMENTAL_LINEAR_CAST_SSOT.md` | Element × linear merge | +| `THREE_LAYER_TERRAIN_SSOT.md` | L0–L2 terrain | +| `LOADER_DRACO_KTX2_AUDIT.md` | Decoder pins · no conflicts | +| `SYSTEMS_HEALTH_AUDIT.md` | Conflict / forget checklist | +| `FLEET_ENTRY_DEPLOY_LOADERS_RAYCAST_REVIEW.md` | Entry · deploy · raycast review | +| `ENTRY_CATCH` (gameopen) | Player start URLs · anti-loop | + +### Player entry (fleet) -Code is provided as-is for the purposes of this project. The bundled HDR probe and the character -FBX retain their original licences. +Use Open `PRODUCT_STARTS` / `startUrlForIntent` — not guessed hosts. +Library: `open.grudge-studio.com` · create hero: `character.grudge-studio.com/foundry` · cabinets: `grudox.grudge-studio.com/arcade`. diff --git a/Standing Idle.fbx b/Standing Idle.fbx deleted file mode 100644 index 359a94c..0000000 Binary files a/Standing Idle.fbx and /dev/null differ diff --git a/api/cdn-proxy.js b/api/cdn-proxy.js new file mode 100644 index 0000000..46a0e9e --- /dev/null +++ b/api/cdn-proxy.js @@ -0,0 +1,141 @@ +/** + * Same-origin R2 proxy — classic Vercel Node (req, res). + * Pattern from Warlords api/cdn-proxy.ts — do not invent a second CDN. + * + * vercel.json: + * /api/assets/:path* → /api/cdn-proxy?key=:path* + * + * node:https with same-host Referer. Do not use fetch() — Vercel forwards + * the incoming Referer and Cloudflare Hotlink Protection 1011s PNGs. + */ +import https from 'node:https'; + +const CDN_HOST = 'assets.grudge-studio.com'; +const MAX_PATH = 512; + +function setCors(res) { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Range'); + res.setHeader( + 'Access-Control-Expose-Headers', + 'ETag, Accept-Ranges, Content-Length, Content-Type, Content-Range, X-Upstream-Status' + ); + res.setHeader('X-Grudge-Asset-Proxy', 'cdn-proxy'); +} + +function sanitizeKey(raw) { + let key = String(raw || '').trim(); + try { + key = decodeURIComponent(key); + } catch { + return null; + } + key = key.replace(/^\/+/, ''); + if (!key || key.length > MAX_PATH) return null; + if (key.includes('..') || key.includes('\\') || key.includes('://')) return null; + if (key.includes('//')) return null; + if (!/^[A-Za-z0-9._\-/% ]+$/.test(key)) return null; + return key; +} + +function keyFromReq(req) { + const host = req.headers.host || 'localhost'; + const url = new URL(req.url || '/', `http://${host}`); + const q = url.searchParams.get('key'); + if (q) return sanitizeKey(q); + const path = url.pathname; + for (const prefix of ['/api/cdn-proxy/', '/api/assets/']) { + if (path.startsWith(prefix)) return sanitizeKey(path.slice(prefix.length)); + } + return null; +} + +export default function handler(req, res) { + setCors(res); + + if (req.method === 'OPTIONS') { + res.statusCode = 204; + res.end(); + return; + } + if (req.method !== 'GET' && req.method !== 'HEAD') { + res.statusCode = 405; + res.end('Method Not Allowed'); + return; + } + + const key = keyFromReq(req); + if (!key) { + res.statusCode = 400; + res.end('Bad Request'); + return; + } + + const host = req.headers.host || 'localhost'; + const url = new URL(req.url || '/', `http://${host}`); + const extra = [...url.searchParams.entries()] + .filter(([k]) => k !== 'key') + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join('&'); + const path = `/${key}${extra ? `?${extra}` : ''}`; + + const headers = { + Host: CDN_HOST, + Accept: typeof req.headers.accept === 'string' ? req.headers.accept : '*/*', + 'User-Agent': 'grudge-vercel-asset-proxy', + Referer: `https://${CDN_HOST}/` + }; + if (typeof req.headers.range === 'string') headers.Range = req.headers.range; + if (typeof req.headers['if-none-match'] === 'string') { + headers['If-None-Match'] = req.headers['if-none-match']; + } + + const up = https.request( + { + protocol: 'https:', + hostname: CDN_HOST, + path, + method: req.method, + headers + }, + (upRes) => { + res.statusCode = upRes.statusCode || 502; + res.setHeader('X-Upstream-Status', String(upRes.statusCode || 502)); + for (const name of [ + 'content-type', + 'content-length', + 'content-range', + 'accept-ranges', + 'etag', + 'last-modified', + 'cache-control' + ]) { + const v = upRes.headers[name]; + if (typeof v === 'string') res.setHeader(name, v); + else if (Array.isArray(v)) res.setHeader(name, v.join(', ')); + } + if (!res.getHeader('cache-control')) { + res.setHeader('Cache-Control', 'public, max-age=86400, stale-while-revalidate=3600'); + } + if (req.method === 'HEAD') { + upRes.resume(); + res.end(); + return; + } + upRes.pipe(res); + } + ); + up.setTimeout(20000, () => { + up.destroy(new Error('cdn timeout')); + }); + up.on('error', () => { + if (!res.headersSent) { + res.statusCode = 502; + res.end('Bad Gateway'); + } else { + res.end(); + } + }); + up.end(); +} diff --git a/devnode.html b/devnode.html new file mode 100644 index 0000000..f23a47d --- /dev/null +++ b/devnode.html @@ -0,0 +1,48 @@ + + + + + + Grudge · Training Room · DevNode + + + + + +
+ + +
+ + + diff --git a/docs/ADMIN_HUB_F1_F5_SSOT.md b/docs/ADMIN_HUB_F1_F5_SSOT.md new file mode 100644 index 0000000..690165b --- /dev/null +++ b/docs/ADMIN_HUB_F1_F5_SSOT.md @@ -0,0 +1,136 @@ +# Admin Hub F1–F4 · ] World — tools, editors, deployables + +**Code:** `src/ui/AdminHub.js` · `src/api/deployableContract.js` · `src/api/prefabDraftStore.js` +**Host:** casting.grudge.studio (lab control plane) +**Related:** `PREFAB_SCAFFOLD_CONTROL_SSOT.md` · `WEAPON_PREFAB_UUID_SSOT.md` · `GAME_ITEM_PREFAB_PRODUCTION_SSOT.md` + +--- + +## Hotkeys + +| Key | Tab | Owns | +|-----|-----|------| +| **F1** | Player | Hero session, race kit, controller, equip shortcuts, Foundry/ID links | +| **F2** | Assets | Buildables · harvestables · vehicles — **scripts + purpose** | +| **F3** | Creatures | Enemy · ally · boss · NPC · vendor · Grudge race kits · AI brain | +| **F4** | Prefabs | Weapons · armour · T0 scaffold · craft · export | +| **]** | World | Stage notes · loot spawn · deploy surface map (**not F5**) | +| **`** | — | Auto run / freeride sail-row (toggle) | +| **Esc** | — | Close Admin Hub | +| **?** / **H** | — | Keyboard help (not F1) | + +**F5 is unbound** (browser refresh elsewhere). Same admin key **toggles close** if that tab is already open. Top chips + TightBar **Admin** also open F4. + +--- + +## Product intent + +Continue **tools, editors, and systems** for: + +1. **Prefab creation + saves** (local drafts → export JSON) +2. **Buildable assets** — mesh + **script** + **purpose** (structure, camp, craft, storage, harvest node…) +3. **Everything deployable** — weapons, armour, enemies, Grudge characters, vehicles, props + +Do **not** invent a second catalog. Read ObjectStore / info SSOT; mint UUIDs only via ObjectStore pipelines. + +--- + +## Deployable kinds + +| Kind | Admin tab | Layers (have) | Jobs (do) | +|------|-----------|---------------|-----------| +| weapon | F4 | identity · stats · skills · assets · runtime · loadout | bag · equip · controller · hotbar · combat · craft · export | +| armour | F4 | identity · stats · assets · runtime · slots | bag · mesh_ids · stats · export | +| buildable | F2 | identity · **purpose** · **script** · assets · placement · runtime | place · snap · interact · use · save instance · export | +| harvestable | F2 | + drops | spawn · E harvest · loot · export | +| vehicle | F2 | + seats | spawn · mount · drive · export | +| enemy / ally / npc | F3 | identity · kit · anims · ai · combat · assets · runtime | spawn · aggro/follow · skills · loot · export | +| character_kit | F3 | race · mesh_ids · anims | load kit · equip · export | + +Contract API: `createDeployableDraft` · `validateDeployableDraft` · `exportDeployableSnapshot`. + +--- + +## Create + save flow + +``` +Admin F2/F3/F4 → Create draft + ↓ + localStorage grudge.casting.deployableDrafts.v1 + ↓ + Export JSON (deployable-*.json) + ↓ + ObjectStore pipeline / R2 CDN (real ITEM- / PREFAB- / NPC- uuids) + ↓ + Fleet clients consume catalog +``` + +| Store | What | +|-------|------| +| **localStorage drafts** | Lab authoring only | +| **Download JSON** | Handoff pack + validation | +| **ObjectStore** | Catalog mint (`build:weapon-pipeline`, `generate:master`) | +| **Railway** | Player instances (bag, equip) — never drafts | +| **R2 CDN** | GLB / icons binaries only | + +--- + +## Buildable purpose + script + +Purpose tags (`BUILDABLE_PURPOSES`): structure · camp · crafting · storage · defense · decoration · resource_node · spawn_pad · transport. + +Script stub (not eval’d): + +```js +{ + id, type, purpose, + onPlace, onInteract, onDestroy, tick?, stats? +} +``` + +Interact routes by purpose (craft UI · depot · harvest swing · inspect). + +--- + +## Creatures (F3) + +Each entry ties: + +- **Model** — grudge6 race GLB (`…/toon-rts-characters/glb/characters/{short}.glb`) +- **Anims** — pack + roles (idle/walk/attack/death…) +- **AI brain** — passive · guard · patrol · hunter · boss_phase · ally_follow · vendor_idle +- **Role** — enemy · monster · boss · ally · commander · npc · vendor · training_dummy + +--- + +## Prefabs (F4) + +- Load live T0 / equippable catalog +- One-click **scaffold pack** (existing `buildItemScaffoldPack`) +- New weapon/armour drafts +- Link to Inventory **Prefabs** tab for full browser equip + +--- + +## Agent rules + +1. Extend **this** hub + `deployableContract` — do not fork `AdminPanel2` / parallel F-keys. +2. Never mint GRUDGE UUIDs in the lab. +3. Buildables must declare **purpose + script**, not mesh-only. +4. Enemies use grudge6 kits + SI heights (human ~1.8 m). +5. Saves are local drafts until ObjectStore register. +6. Help stays on **?** — F1 is Player. + +--- + +## Files + +| File | Role | +|------|------| +| `src/ui/AdminHub.js` | Panel UI | +| `src/ui/adminHub.css` | Styles + top chips | +| `src/api/deployableContract.js` | Kinds · layers · create · validate · export | +| `src/api/prefabDraftStore.js` | localStorage CRUD · import/export | +| `src/api/prefabScaffold.js` | Weapon scaffold (F4) | +| `src/input/InputManager.js` | F1–F4 · ] World · ` auto · Esc | +| `src/core/App.js` | Wire AdminHub | diff --git a/docs/ANIM_LIBRARY_SSOT.md b/docs/ANIM_LIBRARY_SSOT.md new file mode 100644 index 0000000..5dd9f56 --- /dev/null +++ b/docs/ANIM_LIBRARY_SSOT.md @@ -0,0 +1,118 @@ +# Animation Library SSOT — Casting / Warlords lab + +**Code:** `src/config/animLibrary.js` · `src/config/assets.js` (`ANIM_PACKS`) · `src/config/weaponAnimPack.js` +**Play:** `src/animation/CharacterController.js` · mobility drive `src/combat/DrcCombatController.js` +**MM:** `src/combat/motionMath.js` · afterimage `src/vfx/DodgeAfterimage.js` +**UI:** Showcase **Anims** tab (`O` / Show) — roles grouped by family + +Use this language in code comments, toasts, agent notes, and fleet ports. +**Do not invent parallel role names or a second mixer.** + +--- + +## Vocabulary + +| Term | Meaning | Example | +|------|---------|---------| +| **pack** | Named clip set bound to the hero mixer | `magic`, `sword_shield`, `longbow`, `combat_mobility` | +| **role** | Logical clip key on the mixer | `idle`, `walk`, `dodgeL`, `rollR`, `slide` | +| **family** | Grouping for UI / agents | `gait` · `combat` · `mobility` · `utility` | +| **channel** | How the role is driven | `gait` (`setGait`) · `oneShot` · `mobility` (impulse + clip) | +| **MM** | Motion-math body travel | **100 MM = 1 m** | +| **afterimage** | Trailing model ghosts on MM dodge | wind cyan additive mesh clones | +| **invuln** | I-frames while dodge channel runs | `drc.isInvincible` | + +Prefixed roles like `combat_mobility:rollL` mean “same role, non-primary pack bind.” +`baseRoleName()` strips the prefix. + +--- + +## Bind order (hero load) + +1. **Weapon pack** from equip / preset (`magic` | `sword_shield` | `longbow`) +2. **Magic fallback** if needed (hands-on idle) +3. **`combat_mobility` always** — rolls, dodges, slide, parry/block + +Weapon mesh swap → `animPackForLoadout` → `setAnimPack` → rebind mobility. + +--- + +## Packs + +| Pack | Owns | Notes | +|------|------|--------| +| `magic` | idle, cast, walk, run, jump | Staff; prod idle has Hand tracks | +| `sword_shield` | idle, **attack1–3**, **finisher**, **finisherAir**, attack (finisher alias), block, walk, run, jump | Melee combo + jump-dash finisher — `docs/MELEE_COMBO_SSOT.md` | +| `longbow` | idle, attack, walk, run, jump, dodge* | Dodges also in combat_mobility | +| `combat_mobility` | roll L/R/F/B, slide, dodge L/R/F/B, parry | Shared; Ghost Rider rolls first | +| `locomotion_8way` | optional overlay | Bind only if CDN clips exist | + +Clip URL candidates: `bakedClipUrlsForRole` — `prod:…` then open baked. + +--- + +## Families & play API + +| Family | Roles | API | +|--------|-------|-----| +| **gait** | idle, walk, run, jump | `setGait(0\|1\|2, sprinting)` · `playJump` | +| **combat** | cast, attack1–3, finisher, finisherAir, attack, block, parry | `playMeleeAttack` · `playWeaponCombat` · `requestOneShot` · `playParry` | +| **mobility** | dodge*, roll*, slide | `playDodge` · `playRoll` · `playSlide` + DRC input | +| **utility** | anything else | `playLibraryClip(role)` | + +### Mobility inputs (DRC combat) + +| Action | Input | Clip priority | Motion | +|--------|-------|---------------|--------| +| **MM dodge** | AA / DD / WW double-tap · X back | longbow dodge → locomotion → ghost_rider dodge | Lateral **720 MM (7.2 m)**; F/B 240 MM; afterimage + invuln | +| **Roll** | Ctrl+A/D (W/S) | **ghost_rider/roll_*** → locomotion → longbow dodge | Impulse + one-shot | +| **Slide** | Shift+Ctrl while sprint | `prod:extra/running-slide` | Forward impulse | + +--- + +## Extend pattern (agents) + +``` +1. Add role URL list under ANIM_PACKS[pack] in assets.js +2. Register meta in animLibrary.js ANIM_ROLE_META (+ MOBILITY_BINDINGS if input) +3. roleMap LoopOnce/LoopRepeat in CharacterController._bindPack +4. play* helper or DRC poll (do not one-off in App.js) +5. Showcase Anims tab picks it up via listAnimRoles / getAnimLibrary +6. Document in this file + CASTING_LAB_SSOT if fleet-facing +7. Smoke casting.grudge-studio.com / casting-abilities-threejs.vercel.app +``` + +### Hard bans + +- ❌ Second `AnimationMixer` on the same body +- ❌ Inventing `rollLeft` when role is `rollL` +- ❌ Strafe-as-run without Shift freelook channel +- ❌ Binding residual to Space (jump only) +- ❌ Skipping `combat_mobility` after pack swap + +--- + +## Diagnostics + +```js +character.getAnimLibrary() +// { version, activePack, packs, roles, byFamily, mobility, dodgeMm, playApi, extendPattern } + +character.describeRole('rollL') +// "Roll left (Ghost Rider) · Ctrl+A [mobility/mobility]" + +drc.isInvincible // true during MM dodge window +``` + +Console on load lists bound clips: `[CharacterController] clip rollL ← …ghost_rider/roll_left…` + +--- + +## Related + +| Doc / skill | Role | +|-------------|------| +| `docs/CASTING_LAB_SSOT.md` | Lab macro | +| `casting-warlords-lab` skill | Agent entry | +| `grudge6-combat-runtime` | Fleet combat one-shots | +| Animator Studio `MM_TO_M` | Same 100 MM = 1 m contract | diff --git a/docs/ASSET_LIBRARY_AUDIT_SSOT.md b/docs/ASSET_LIBRARY_AUDIT_SSOT.md new file mode 100644 index 0000000..b355807 --- /dev/null +++ b/docs/ASSET_LIBRARY_AUDIT_SSOT.md @@ -0,0 +1,181 @@ +# Asset & library audit — half-done, conflicts, keep/remove + +**Date:** 2026-08-08 (live HEAD checks) +**Scope:** ObjectStore / info / R2 / D1 + casting lab importers +**Goal:** one design — no parallel prefab systems. + +Re-run audit: + +```bash +node scripts/audit-asset-library.mjs +``` + +--- + +## 1. Verdict (short) + +| Layer | Verdict | +|-------|---------| +| **Weapon runtime SSOT** | **KEEP** `master-weapon-prefabs.json` + `master-weaponSkills.json` + `t0-weapons.json` | +| **Icons + family GLBs** | **KEEP** R2 `assets.grudge-studio.com` — most family meshes **200** | +| **Per-item dropPrefab.glb** | **HALF-DONE** — keys in JSON, sample **404** on R2 — do not block equip on these | +| **Armor** | Catalog **KEEP** `master-armor.json` — **DO NOT invent** `master-armor-prefabs` in lab until ObjectStore ships it | +| **D1 `weapon_prefabs`** | Seed SQL exists — index only; **JSON remains authority** | +| **Deprecated catalogs still HTTP 200** | **DO NOT wire** into lab/games loaders (see §3) | +| **Custom T0 wand meshes** | **UPLOADED 2026-08** `prod/gltf/weapons/t0-wand.glb` · `t0-nature-staff.glb` · `wand.glb` (HEAD 200) | + +--- + +## 2. USE (canonical) — wire these only + +| File / surface | Role | Live? | +|----------------|------|-------| +| `master-weapon-prefabs.json` | Weapons + tools + offhand equip prefabs | **200** · 877 | +| `master-weaponSkills.json` | SKIL-* · WEAPON_SKILLS.html | **200** | +| `t0-weapons.json` | 15 T0 starters (Apprentice Wand, Sapling Staff, …) | **200** | +| `master-armor.json` | Armour definitions | **200** · ~1344 | +| `master-items.json` | Aggregate catalog | **200** | +| `master-item-prefabs.json` | byUuid index | **200** | +| `master-relics.json` | Relics | **200** | +| `master-mounts.json` | Mounts | **200** | +| `master-classRelics.json` | Class items / relics | **200** | +| `master-consumables.json` | Food / potions | **200** | +| `master-materials.json` · `master-recipes.json` | Craft | **200** | +| `master-registry.json` | UUID index | **200** | +| `canonical-items-manifest.json` | Category map | **200** | +| CDN family models | `prod/gltf/weapons/{sword\|axe\|staff\|bow\|dagger\|hammer\|assault_rifle}.glb` | **200** | +| CDN icons | `game-assets/icons/...` | used by prefabs | + +**Item browser:** https://info.grudge-studio.com/GRUDGE_Item_Database.html +**Skills:** https://info.grudge-studio.com/WEAPON_SKILLS.html + +--- + +## 3. DO NOT USE (still online — conflict risk) + +| File | Why conflict | Replace with | +|------|--------------|--------------| +| `weapons.json` | Design templates only | `master-weapon-prefabs` | +| `armor.json` | Design sets only | `master-armor.json` | +| `master-weapons.json` | Old flat list (~1MB) | `master-weapon-prefabs` | +| `master-t0-items.json` | **Archived** stub (`deprecated: true`) | `t0-weapons.json` + prefabs | +| `weaponSkills.json` | Empty / legacy | `master-weaponSkills.json` | +| `items-database.json` | Empty / legacy | `master-items.json` | +| `weapon-prefabs.json` | **404** (good) | never reintroduce | +| `master-armor-prefabs.json` | **404 planned** | wait for ObjectStore pipeline | +| `game-library.json` on **info** | **404** (2026-08-08) | use `canonical-items-manifest.json` until restored | + +Agents must not “fix 404” by inventing a second library file in CastingAbilitiesThreeJS. + +--- + +## 4. HALF-DONE (edit / finish carefully) + +### 4.1 R2 drop prefabs (declared, missing binaries) + +- Prefab JSON: `assets.dropPrefabR2Key` → + `prefabs/items/weapons/{type}/{tier}/{name}.prefab.glb` +- Sample HEAD: **all 404** +- **Action:** equip/HUD use **icon + family `modelUrl`** only. Upload drop prefabs later via convert + `wrangler r2 object put`. Do not code paths that require dropPrefab 200. + +### 4.2 Family models incomplete + +| Key | Status | +|-----|--------| +| sword, axe, staff, bow, dagger, hammer, assault_rifle | **200** | +| **wand.glb** | **404** — T0 wand uses **staff.glb** | +| **shield.glb** | **404** — offhand shields may 404 model | + +**Action:** either upload `wand.glb` / `shield.glb` or keep staff/sword family mapping explicit in prefab `modelUrl`. + +### 4.3 T0 Apprentice Wand / Sapling Staff meshes + +| Item | Skills (catalog) | Model on CDN now | +|------|------------------|------------------| +| t0-wand | Practice Bolt / Focus / Frost Spark\|Arcane Ping | **staff.glb** (generic) | +| t0-nature-staff | Practice Root / Nature Ward / Vine Lash\|Healing Sprout | **staff.glb** (generic) | + +Local author GLBs (not in R2 yet): + +- `D:\Games\Models\arcane_staff_of_resonance_-_wow_inspired_weapon.glb` → intended Apprentice Wand +- `D:\Games\Models\mushroom_staff.glb` → intended Sapling Staff + +**Action:** convert SI wand scale → R2 `prod/gltf/weapons/t0-wand.glb` + `t0-nature-staff.glb` → patch prefab `modelUrl`. Until then skills import is fine; mesh is placeholder. + +### 4.4 Armor prefabs + +- Catalog live; **runtime armor-prefabs file not shipped** +- Lab Prefabs tab can **browse** armor; equip combat bar is **weapons/T0 only** + +### 4.5 D1 `weapon_prefabs` (objectstore-meta) + +- Seed: `ObjectStore/workers/seed/weapon-prefabs.sql` (huge upsert) +- Columns: uuid, weapon_type, tier, icon_r2_key, model_r2_key, prefab_json +- **Role:** search/index mirror — **not** player bag SSOT +- **Action:** keep seed in ObjectStore pipeline; games read **JSON first**. Confirm remote D1 applied after `build:weapon-pipeline` (ops). + +### 4.6 R2 layout dual paths + +| Path pattern | Use | +|--------------|-----| +| `prod/gltf/weapons/{family}.glb` | **What models resolve to today** | +| `models/weapons/{category}/{baseName}.glb` | Declared in r2Layout — may be empty/sparse | +| `prefabs/items/weapons/...` | Drop prefabs — **missing** | + +**Action:** prefer writing **prod/gltf** for play meshes; treat `models/weapons/...` as secondary until audited full. + +--- + +## 5. Lab (CastingAbilitiesThreeJS) — overlap map + +| Module | Role | Conflict? | +|--------|------|-----------| +| `prefabAssets.js` | Load master-weapon-prefabs | **OK** — keep | +| `t0WeaponCatalog.js` | t0-weapons + equip | **OK** — keep | +| `weaponSkillsCatalog.js` | master-weaponSkills | **OK** — keep | +| `gameItemCatalog.js` | Multi-category import | **OK** — keep; soft-fail if game-library 404 | +| `t0ApprenticeWand.js` | **Local skill mirror** | **HALF** — offline fallback only; prefer live t0-weapons | +| `staffWeaponSkillsBind.js` | VFX bind for catalog skill ids | **OK** — not a skill DB | +| Prefabs tab / Weapon tab | Dev equip | **OK** | + +**Remove / freeze (do not grow):** + +- Growing **local skill tables** that duplicate `t0-weapons` / WEAPON_SKILLS +- Lab-only `staff_nature_trap` style invented skills (already removed) +- Second “items database” JSON in the casting repo + +--- + +## 6. Design rules (no conflicts) + +1. **One authority per layer** — weapons = prefabs; skills = weaponSkills; T0 = t0-weapons; armor = master-armor. +2. **Never load deprecated files** in new code even if HTTP 200. +3. **Missing R2 binary ≠ missing catalog row** — show icon + family mesh. +4. **Player inventory** = Railway (production-wiring), not D1 assets. +5. **D1** = asset/prefab **index** only. +6. New meshes: convert skill → R2 → update prefab modelUrl → (optional) D1 reseed. +7. Casting lab is **dev proof**; ObjectStore pipelines own generate/publish. + +--- + +## 7. Recommended cleanup actions + +| Priority | Action | Owner | +|----------|--------|-------| +| P0 | Lab already uses USE list — **do not** add master-weapons / weapons.json loaders | agents | +| P0 | Document game-library 404 — use canonical-items-manifest | ObjectStore ops / this doc | +| P1 | Upload wand/shield family or keep explicit modelUrl | convert + R2 | +| P1 | Bake + upload t0-wand / t0-nature-staff author GLBs | convert + prefab patch | +| P2 | Batch dropPrefab.glb upload or stop writing dead keys | ObjectStore pipeline | +| P2 | Confirm D1 weapon_prefabs seed on remote | wrangler d1 execute | +| P3 | Ship master-armor-prefabs when ready | ObjectStore only | +| P3 | Shrink/archive 200 deprecated JSON from public API (redirect stubs) | ObjectStore | + +--- + +## 8. Related + +- `GAME_ITEM_PREFAB_PRODUCTION_SSOT.md` — category → consumer +- `WEAPON_EQUIP_PREFABS_SSOT.md` — equip try path +- `T0_STARTERS_WEAPON_SKILLS_SSOT.md` — Apprentice + Sapling +- ObjectStore `docs/CANONICAL-ITEMS-WARLORDS.md` · `CANONICAL-EQUIPMENT.md` diff --git a/docs/BACK_SLOT_MOBILITY_SSOT.md b/docs/BACK_SLOT_MOBILITY_SSOT.md new file mode 100644 index 0000000..2b14bbb --- /dev/null +++ b/docs/BACK_SLOT_MOBILITY_SSOT.md @@ -0,0 +1,99 @@ +# Back-slot mobility SSOT + +**Code:** `src/config/backSlotMobilitySsot.js` · `BackSlotEquip.js` · `WalkController` · `HoverboardRide` +**Live:** casting.grudge.studio / casting-abilities-threejs.vercel.app + +--- + +## Domains (hard) + +| Domain | Item | Deploy on land? | Deploy on water? | Air? | +|--------|------|-----------------|------------------|------| +| **water** | Windsurf | **No** | **Yes** (vehicle) | No | +| **water** | **Shark Fin** | Equip yes | **Passive always-on** | No | +| **land** | Cape · shell · pack | Cosmetic / passive | No vehicle | No | +| **air** | Holy / Traveler wings | Equip yes | Equip yes | **Flight deploy** | + +**Windsurf is a water mobility tool only.** Dry island pad → deploy rejected. + +### Shark Fin (passive water) + +| Buff | Value | +|------|--------| +| Swim on + under water | **×2** (100% faster) | +| Shark aggro | **Immune** (`sharkAggroImmune`) | +| Underwater breath | **Yes** (no oxygen drain / drown) | + +Equip: `character.equipBackSlot('shark_fin')` · mesh `public/models/ride/shark_fin.glb` +Source: `D:\Games\Models\Shark fin by Poly by Google - 1L9OjE5KOlC.glb` +Code: `waterBuffs` on catalog · applied in `DrcCombatController` loco + breath tick. + +--- + +## Windsurf parts (when deployed) + +Author GLB (`windsurf_package.glb`) is a **single Sketchfab mesh** (`Tube003` / `OBJ_Para_Coat`) — no named board/sail nodes. +Parts are **logical SI sockets** from `ride.manifest.json`: + +| Part | Sockets | Color (debug) | Role | +|------|---------|---------------|------| +| **Board / deck** | `deckCenter`, `footL`, `footR`, `mastBase` | blue | Feet IK · seat | +| **Front / bow** | `bowTip` | green | Travel +Z nose | +| **Sail / boom** | `sailPeak`, `sailRail`, `sailBoomL/R`, `mastBase` | amber | Hands IK · SailCloth | +| **Engine / stern** | `engineMount` | red | Aft (−Z) thrust mount | + +**Back stow** uses `back_fly_windsurf.glb` (same Para mesh, quiver scale) — not the full vehicle. + +### Debug + +```js +settings.walk.debugSockets = true // or debugParts +// colored spheres on sockets after deploy +``` + +API: `scooter.describeParts()` · `scooter.getPartWorldPosition('sail')` + +--- + +## Wings prefabs (next system) + +| Id | Label | Jump model (WoW DF-like) | +|----|-------|---------------------------| +| `holy_wings` | Holy Wings | **Jump up → glide** (no multi-flap) | +| `traveler_wings` | Traveler's Wings | **Double jump → fly pose · 2 flaps · glide** | + +### Traveler state machine + +``` +idle ── air Space (2nd jump) ──► fly (flapsLeft=2) +fly ── Space flap ──► fly (flapsLeft−1) or glide if 0 +fly/glide ── land ──► idle +``` + +### Asset drop (not found yet on disk/CDN) + +``` +public/models/ride/wings/holy_wings_stow.glb +public/models/ride/wings/holy_wings_open.glb +public/models/ride/wings/traveler_wings_stow.glb +public/models/ride/wings/traveler_wings_open.glb +public/anims/baked/wings/holy-*.json +public/anims/baked/wings/traveler-*.json +``` + +Icons exist under GrudgeBuilder `icons/armor/wings/wing_*.png` (2D only). +**Animated wing meshes not located** in lab public or assets CDN at time of write — drop GLBs into paths above and equip via `equipBackSlot('holy_wings')`. + +--- + +## Equip API + +```js +character.equipBackSlot('windsurf') +character.equipBackSlot('holy_wings') +character.equipBackSlot('traveler_wings') +character.equipBackSlot('none') +``` + +Deploy windsurf: walk mode · wet surface · Space (freeride gate). +Wings flight runtime: scaffold in SSOT; host loop next (Space flaps + fall glide). diff --git a/docs/CASTING_10_SPELLS_SSOT.md b/docs/CASTING_10_SPELLS_SSOT.md new file mode 100644 index 0000000..cbc39aa --- /dev/null +++ b/docs/CASTING_10_SPELLS_SSOT.md @@ -0,0 +1,89 @@ +# Casting lab → 10 spells → WEAPON_SKILLS + +**Live lab:** https://casting-abilities-threejs.vercel.app/ +**Catalog UI:** https://info.grudge-studio.com/WEAPON_SKILLS.html +**JSON kit:** ObjectStore `api/v1/casting-spell-kit.json` +**Runtime:** `src/combat/castingSpellKit.js` · bar via `drcSkills.js` + +## Goal + +Learn **each element’s** animation pack, Ability script, textures/materials, and VFX so staff (and the same pattern for every weapon type) skills on WEAPON_SKILLS are **fully bound** — not empty `animation` / `prefab.vfxRef` nulls. + +## Resources + +Spells cost **mana + stamina**. LMB hold + path length raise intensity (1–3×). +See `castResources.js` · `docs/PRODUCTION_CONTROLLER_SSOT.md`. + +## The 10 core spells + 5 signatures + +| # | Lab id | STAFF catalog id | Element | Path mode | Ability | Cast → Travel → Impact | +|---|--------|------------------|---------|-----------|---------|------------------------| +| 1 | casting_fire_bolt | staff_fire_bolt | fire | stream | FireAbility | fire_hand → fireball → inferno | +| 2 | casting_flame_wave | staff_flame_wave | fire | aoe | FireAbility | fire_hand → fireball → inferno | +| 3 | casting_frost_bolt | staff_frost_bolt | ice | stream | WaterAbility | arcane_swirl → moon_beam → frost_wave | +| 4 | casting_ice_nova | staff_ice_nova | ice | spikes | WaterAbility | … → frost_wave | +| 5 | casting_earth_spike | staff_earthquake | nature | spikes | EarthAbility | earth_surge ×3 | +| 6 | casting_stone_wall | staff_natures_fury | nature | wall | EarthAbility | earth_surge | +| 7 | casting_wind_tempest | staff_storm_call | storm | stream | WindAbility | arcane_swirl → chain_lightning → ice_lightning_burst | +| 8 | casting_gale_nova | staff_thunder_cataclysm | storm | aoe | WindAbility | … → ice_lightning_burst | +| 9 | casting_holy_light | staff_holy_light | holy | stream | (wind path + beauty) | arcane_swirl → moon_beam | +| 10 | casting_meteor_strike | staff_meteor_strike | fire | stream | FireAbility | fire_hand → fireball → inferno (long range) | +| S | casting_inferno | staff_inferno | fire | stream | FireAbility | **Inferno** signature | +| S | casting_blizzard | staff_blizzard | ice | aoe | WaterAbility | **Blizzard** signature | +| S | casting_warp | staff_warp | arcane | stream | arcane | **Warp** signature | +| S | casting_quake | staff_quake | nature | wall | EarthAbility | **Quake** signature | +| S | casting_tempest | staff_tempest | storm | stream | WindAbility | **Tempest** signature | + +**Product elements (hotbar 1–6):** `fire` · `storm` · `ice` · `nature` · `holy` · `arcane` +Ability pools still: FireAbility · WindAbility · WaterAbility · EarthAbility · (holy/arcane → wind pool). + +## What to study per element + +| Element | Script | Settings | Materials / FX | +|---------|--------|----------|----------------| +| **Fire** | `FireAbility.js` | `settings.fire` | `VolumetricFireMaterial`, embers/sparks, BurstSphere | +| **Water** | `WaterAbility.js` | `settings.water` | ribbons, spray, foam, frost_wave | +| **Earth** | `EarthAbility.js` | `settings.earth` | ground rise, earth_surge | +| **Wind** | `WindAbility.js` | `settings.wind` | sheets, residual cyan, chain_lightning | +| **Path place** | `pathCastClassify.js` | `settings.staffCast` | aoe · spikes · wall · stream | +| **Anim** | magic pack | `weaponAnimPack` staff→magic | `standing 1h cast spell 01` | +| **Primitives** | `effectPrefab.js` | `settings.effect` | intensity / aoe / speed / size / color / mesh | + +## Lab UX + +- **I → Skills** — 10-spell kit list + pages **1–4 / 5–8 / 9–10** on hotbar +- **1–4** cast active page · **LMB draw** path place · **F** interact +- **Q** combat session + +## Pattern for *all* weapons (WEAPON_SKILLS) + +| labStyle | animPack | animRole | default residual/VFX | +|----------|----------|----------|----------------------| +| spell | magic | cast | element cast/travel/impact | +| melee | sword_shield | attack | getsuga_slash from tip | +| ranged | longbow | attack | projectile + impact | + +Staff is the **spell** proof. Sword/axe/bow keep the same FleetWeaponSkill shape: `castEffectId` / `travelEffectId` / `impactEffectId` + `animRole` + pack. + +## Export / fleet + +```js +import { exportSpellKitJson, CASTING_SPELL_KIT } from './castingSpellKit.js'; +// ObjectStore api/v1/casting-spell-kit.json +// master-weaponSkills STAFF skills get prefab.castingLab + animation clip +``` + +## Agent rules + +1. Extend **castingSpellKit** + Ability/settings — do not invent a parallel spell list +2. Catalog `id` must match WEAPON_SKILLS (`staff_*`) when binding +3. Orbs for travel mesh — never whole `fireball.glb` as scene +4. Space = jump; residual only on attack frames +5. After enrich, smoke: combat Q → 1–4 each page → toast shows `→ staff_*` + +## Related + +- `docs/CASTING_LAB_SSOT.md` +- `src/combat/elementWeaponSkills.js` +- `src/api/weaponSkillsCatalog.js` +- ObjectStore `docs/DROP_TABLES_SSOT.md` (separate) diff --git a/docs/CASTING_DEPLOY_ENV_SSOT.md b/docs/CASTING_DEPLOY_ENV_SSOT.md new file mode 100644 index 0000000..a8c8213 --- /dev/null +++ b/docs/CASTING_DEPLOY_ENV_SSOT.md @@ -0,0 +1,230 @@ +# Casting deploy + env + data SSOT + +## Deploy practice (one path — do not fork) + +1. **Name the system** you are shipping (`DevIslandHarvest`, `SessionState.play`, clips). Do not mix unrelated dirty-tree WIP into a “fix X” deploy. +2. **Extend existing files.** No `*2` harvest / terrain / controller. +3. **Same-origin only** in the browser: `/api/assets` (cdn-proxy), `/api/open`, `/anims/baked`. Never fetch `assets.*` or `open.*` (CORS / CF hotlink). +4. **Terrain:** ship L0–L3 stylized (`IslandHeightfield` vertex colors + `StylizedGrassLayer`). Do not depend on `dl.polyhaven.org` at runtime. +5. From repo `C:\Users\nugye\Documents\CastingAbilitiesThreeJS`: + ```text + vercel --prod --yes + ``` + Project: `grudgenexus/casting-abilities-threejs`. Alias: **https://casting.grudge.studio** +6. **Smoke the live host** before claiming done: HTML 200, new JS hash, one clip URL, one harvest mesh (`/models/dev-island/…`). +7. `--force` only when cache is the bug. Prefer a normal prod deploy. + +--- + +**Hosts** + +| Host | Role | +|------|------| +| **https://casting.grudge.studio** | **Control plane** — dev → production lab (primary) | +| https://weapon-skills.grudge-studio.com | Weapon skill DO + equipWeaponById mirror API | +| https://casting-abilities-threejs.vercel.app | Vercel project hostname | +| https://casting.grudge-studio.com | Legacy (prefer grudge.studio) | +| Vercel Preview | PR / branch deploys | +| `npm run dev` | Local Vite | + +**Vercel project:** `grudgenexus/casting-abilities-threejs` +**Not:** orphan project `casting.grudge-studio.com` (empty — ignore / delete later) + +--- + +## Data plane (do not invent forks) + +| Concern | System | URL | +|---------|--------|-----| +| Player characters / bag / wallet | **Railway Postgres** via grudge-api | `https://grudge-api-production-0d46.up.railway.app` | +| Auth SSO | Grudge ID | `https://id.grudge-studio.com` | +| Realtime rooms | Colyseus on same Railway | `wss://grudge-api-production-0d46.up.railway.app` | +| Binary meshes / icons | R2 CDN | `https://assets.grudge-studio.com` | +| JSON catalogs / gamedata | **info** (live) + ObjectStore **Pages** | `info.grudge-studio.com/api/v1` · `grudge-objectstore.pages.dev/api/v1` | +| Asset **index** | Cloudflare D1 | not player SSOT | +| Weapon skill drafts + equip mirror | **CF Durable Object** `WeaponSkillDrafts` | `https://weapon-skills.grudge-studio.com` · control plane `casting.grudge.studio` · see `WEAPON_SKILL_DO_SSOT.md` | +| Training Room map layout | Same-origin `maps/training_room/` → info/objectstore/R2 promote | `docs/TRAINING_ROOM_DEPLOY_SSOT.md` · R2 prefix `lab/casting/training-room` | + +**SPA never holds `DATABASE_URL`.** Player data only through `/api/*`. + +### Production clip + texture path (browser) + +Never fetch `assets.grudge-studio.com` or `open.grudge-studio.com` from the page (R2/Open lack CORS → `getTransfer` crash). +Live `/api/assets` goes through `api/cdn-proxy.js` (same-host Referer) so CF hotlink does not 403 images. +`/api/objectstore` rewrites to **grudge-objectstore.pages.dev** (custom-domain `/api/v1` is 404). + +| Kind | Browser URL | +|------|-------------| +| Prod baked clips | `/api/assets/prod/anims/{pack}/{clip}.json` | +| Open baked (ghost rider / longbow dodge) | `/api/open/anims/baked/…` | +| Lab-shipped clips | `/anims/baked/…` (Vercel `public/`) | +| Fall FBX | `/anim/locomotion/fall/*.fbx` | +| Sand / R2 binaries | `/api/assets/…` | + +Resolver: `bakedClipUrls` — 5733a06 first-URL-that-loads lists, every remote URL wrapped in `sameOriginFleetUrl` (`/api/assets` · `/api/open`). Prefixes `prod:` · `open:` · `local:` plus unprefixed Open/prod/local fan-out. Do not fetch assets.* / open.* from the page. + +--- + +## How the SPA talks to API/DB + +``` +Browser (casting.grudge.studio) + → GET/POST /api/* (same origin) + → Vercel rewrite (vercel.json) + → Railway grudge-api + → Postgres + → weapon-skills.grudge-studio.com (skill DO / equip mirror, CORS) +``` + +`src/api/fleetApi.js` uses: + +- Production casting / vercel.app → **same-origin** (`baseUrl = ''`) +- Local vite → absolute `VITE_FLEET_API` / Railway +- Override: `VITE_FLEET_API=https://…` or `same-origin` + +Railway CORS already allowlists: + +- `https://casting.grudge.studio` +- `https://casting.grudge-studio.com` (legacy) +- `https://casting-abilities-threejs.vercel.app` + +Same-origin is still preferred (cookies / fewer CORS footguns). + +--- + +## Environment variables + +### Vercel (Production / Preview / Development) + +| Name | Value | Notes | +|------|-------|--------| +| `VITE_FLEET_API` | `same-origin` | build-time; empty string also OK | +| `VITE_RAILWAY_API` | `https://grudge-api-production-0d46.up.railway.app` | fallback | +| `VITE_AUTH_URL` | `https://id.grudge-studio.com` | | +| `VITE_ASSETS_URL` | `https://assets.grudge-studio.com` | | +| `VITE_OBJECTSTORE_URL` | `https://objectstore.grudge-studio.com` | | +| `VITE_INFO_API` | `https://info.grudge-studio.com/api/v1` | | +| `VITE_COLYSEUS_URL` | `wss://grudge-api-production-0d46.up.railway.app` | | +| `VITE_WEAPON_SKILL_DO_URL` | `https://weapon-skills.grudge-studio.com` | Multiverse bundle + equipWeaponById mirror DO | + +**Do not** put `DATABASE_URL`, `CF_DNS_API_TOKEN`, or Postgres secrets on this frontend project. + +### Local + +Copy `.env.example` → `.env.local` (absolute Railway for vite). + +--- + +## DNS (casting.grudge-studio.com) + +Live **200** as of 2026-08-18 (legacy alias). Primary control plane is **casting.grudge.studio**. + +If the alias ever NXDOMAINs again: + +| Type | Name | Content | Proxy | +|------|------|---------|-------| +| A | `casting` | `76.76.21.21` | DNS only (grey) first | +| or CNAME | `casting` | `0788085f42cc3574.vercel-dns-016.com` | DNS only | + +Domain Connect: +https://vercel.com/api/v9/projects/prj_UqrvF6d04qmAFGF7N2cpzPKLoYE/domains/casting.grudge-studio.com/domain-connect/apply?teamId=team_VZ7uiFGiR9QBdqtzne04xygG + +### CF secrets status (2026-08-09) + +All local `CF_DNS_API_TOKEN` / `CLOUDFLARE_USER_API` copies in ObjectStore + grudox + grudgeproduction **return 401/403**. +Wrangler OAuth: workers scopes only — **cannot write zone DNS**. + +Owner must create **Edit zone DNS** token → save as `CF_DNS_API_TOKEN` → +`node scripts/fix-casting-dns.mjs` + +--- + +## Deploy + +```bash +cd CastingAbilitiesThreeJS +npx vercel --prod +npx vercel domains verify casting.grudge-studio.com +``` + +Smoke: + +```bash +curl.exe -sI https://casting-abilities-threejs.vercel.app/api/health +curl.exe -sI https://casting-abilities-threejs.vercel.app/devnode.html +curl.exe -sI https://casting-abilities-threejs.vercel.app/maps/training_room/layout.default.json +curl.exe -sI https://casting.grudge.studio/ # control plane +``` + +Training Room promote (optional fleet handoff): DevNode Export → `*-promote-*.json` → R2 + D1 + info layout upload. + +--- + +## Data-plane audit (2026-08-18) — one player DB + +**Worry:** “accounts, characters, Warlords each have their own database.” +**Fact:** Casting does **not** own a player database. Every live host rewrites player `/api/*` to the **same** Railway Postgres (`grudge-api-production-0d46`). + +| Store | What it is | Owns | Not | +|-------|------------|------|-----| +| **Railway Postgres** | One physical DB | `grudge_id` account · bag · wallet · `characters.id` UUID (`era=warlords`) | — | +| **id.grudge-studio.com** | SSO | JWT / session | Roster / bag | +| **ObjectStore / info** | JSON catalogs | Recipes, master-weapon-prefabs, skills | Ownership | +| **R2 + D1** | Binaries + **index** | GLB / icons | Bag / heroes | +| **Weapon-skill DO** | CF Durable Object | Lab skill *drafts* + equip catalog mirror | Player SSOT | +| **localStorage** | Browser | JWT cache, prefab drafts, mesh appearance | Production bag | +| **Colyseus** | Same Railway | Room session | Saves | +| **cNFT `/api/nfts`** | Chain **mirror** | Display ownership | Second bag | + +### Deployments (all same player API) + +| Host | Status 2026-08-18 | Player data | +|------|-------------------|-------------| +| **casting.grudge.studio** | Live 200 (primary) | `/api/*` → Railway | +| casting.grudge-studio.com | Live 200 (legacy alias) | same | +| casting-abilities-threejs.vercel.app | Live 200 | same | +| weapon-skills.grudge-studio.com | Live 200 `/api/health` | drafts only | +| Vercel Preview | branch | same rewrites | + +### Duplicate confusions (do not treat as extra DBs) + +| Looks like a second DB | What it actually is | Rule | +|------------------------|---------------------|------| +| `listInventory` probing `/api/account/bag` + `/materials` | Dead paths | Use `/api/account/inventory` | +| 10+ `localStorage` token keys | Fleet JWT aliases (`authConnect`) | One token, many key names | +| `playerIdentity` vs `fleetApi.listCharacters` | Handoff cache vs Railway roster | UUID from Foundry/`?characterId=` | +| Admin Hub F1–F5 drafts | localStorage authoring | Export JSON → ObjectStore; never a roster | +| D1 `weapon_prefabs` | Asset index | JSON catalog is authority | +| `WARLORDS_ENGINE_URL` (ThreeFlow) | Map/prefab deploy to R2 | Not player DB | +| Casting `worker/` Durable Object | Skill drafts | Not accounts | +| Untracked `tmp/` · `_qa_*` · extra `public/models/fish/*.glb` | Disk dumps | Not deployed SSOT | +| `objectstore.grudge-studio.com/api/v1/*.json` | Dead custom-domain path (404) | Use info + `grudge-objectstore.pages.dev` | +| `game-library.json` on info **and** Pages | 404 both | Use `canonical-items-manifest.json` | +| `/api/characters` · `/api/account/inventory` unauthed | **401** from **same** Railway | JWT required — not a missing second DB | + +### Macro goal (do not grow Casting into Warlords) + +Lab = **UX + Toon play proof + editable effects** before ship. +Not: second MMO shell, second bag, second character table. + +Create heroes on **character.grudge-studio.com**. Bag/craft on **grudgewarlords.com/craft/**. Play body = `loadRaceKit` / `toonKitPlay` only. + +### Blob audit (admin) + +| Location | Size | Git? | Action | +|----------|------|------|--------| +| `public/models/fish/species/rare/*.glb` | 59+24+22 MB | yes | Still Vercel — wrangler put >20 MB flakes | +| `public/models/vfx/summons/*` | 16+8 MB | yes | **On R2** `lab/casting/models/vfx/summons/*` (HEAD 200) | +| `public/models/ride/windsurf*` | ~23 MB | yes | **On R2** `lab/casting/models/ride/*` (HEAD 200) | +| `public/models/fish/` loose GLBs | ~335 MB | **no** | **gitignored** (`fish/*.glb` + `_bundle/`) | +| `public/models/class-forms/` · `island-scenery/` | ~74 MB | **no** | **gitignored** until convert + R2 | +| `tmp/` | ~247 MB | no | Local only (now gitignored) | +| `_qa_*.png` · `deploy-out*.txt` | ~4 MB | no | Deleted / gitignored | +| `worker/node_modules` | ~181 MB | no | Already ignored | + +R2 prefix: `lab/casting/models/{fish|ride|vfx}/…` +Ride + summons **HEAD 200** on assets.* (2026-08-18). Rare fish still Vercel-only (22–59 MB wrangler put failed). +Resolver exists: `resolveLabAssetUrl(path, { preferCdn: true })` — **do not flip preferCdn** on loaders until fish keys are 200. +CF Hotlink still 403s **images** on `/api/assets` with a foreign Referer; JS + Vercel-shipped GLBs are 200. + +Player data law: **`grudge-production-wiring`** → `references/account-game-uuid-law.md`. diff --git a/docs/CASTING_DNS_FIX.md b/docs/CASTING_DNS_FIX.md new file mode 100644 index 0000000..dba5942 --- /dev/null +++ b/docs/CASTING_DNS_FIX.md @@ -0,0 +1,102 @@ +# casting.grudge-studio.com DNS fix + +**Re-diagnosed 2026-08-09** + +| Check | Result | +|-------|--------| +| https://casting-abilities-threejs.vercel.app/ | **200** — app healthy | +| https://casting.grudge-studio.com/ | **NXDOMAIN** — no DNS answer (Status=3) | +| open / client / id.grudge-studio.com | Resolve OK (Cloudflare) | +| Vercel project | `casting-abilities-threejs` · domain **Verified** · DNS **Invalid** | +| Zone NS | Cloudflare `colette` / `lou` (not Vercel) | + +**Root cause:** missing Cloudflare DNS record for hostname `casting`. +Not a Vercel build, not app code, not R2. + +--- + +## Secrets review (why agents cannot auto-fix) + +| Source | Key | Status | +|--------|-----|--------| +| `F:\GitHub\ObjectStore\.env` | `CF_DNS_API_TOKEN` | **401/403 Invalid** | +| `Documents\grudox\.env.local` | `CF_DNS_API_TOKEN`, `CF_ZERO_TRUST_TOKEN`, `CLOUDFLARE_USER_API` | **401** | +| `Desktop\grudgeproduction\.env` | `Cloudflare_api_token`, `CLOUDFLARE_USER_API` | **400/401** | +| Wrangler OAuth (`~/.wrangler/config`) | oauth_token | **zone:read only** — can list zone, **cannot** create DNS | + +**Action required (owner):** create a fresh Cloudflare API token: + +1. Cloudflare → My Profile → **API Tokens** → Create Token +2. Template **Edit zone DNS** (or custom: Zone → DNS → Edit, Zone → Zone → Read) +3. Zone Resources: **Include → Specific zone → grudge-studio.com** +4. Save value as `CF_DNS_API_TOKEN` in ObjectStore `.env` (and grudox if used) +5. Re-run: `node scripts/fix-casting-dns.mjs` + +Until that token works, DNS must be fixed in the **dashboard** or via Domain Connect (below). + +--- + +## Fix path A — Domain Connect (fastest, one browser approve) + +Open (logged into Cloudflare as zone owner): + +https://vercel.com/api/v9/projects/prj_UqrvF6d04qmAFGF7N2cpzPKLoYE/domains/casting.grudge-studio.com/domain-connect/apply?teamId=team_VZ7uiFGiR9QBdqtzne04xygG + +Approve the DNS change in Cloudflare → wait 1–2 min → reload https://casting.grudge-studio.com/ + +--- + +## Fix path B — Manual DNS record + +**Cloudflare → grudge-studio.com → DNS → Add record** + +| Field | Value | +|-------|--------| +| Type | **A** (or CNAME) | +| Name | `casting` | +| Content | **A:** `76.76.21.21` · **CNAME:** `0788085f42cc3574.vercel-dns-016.com` | +| Proxy | **DNS only** (grey cloud) first — Vercel SSL verify | +| TTL | Auto | + +Then: + +```bash +npx vercel domains verify casting.grudge-studio.com +curl.exe -sI https://casting.grudge-studio.com/ +``` + +After SSL is issued you may orange-cloud proxy if desired (optional). Grey is fine for Vercel. + +--- + +## Verify + +```bash +# DoH should return A 76.76.21.21 (or Vercel CNAME chain) +curl.exe -sS "https://cloudflare-dns.com/dns-query?name=casting.grudge-studio.com&type=A" -H "accept: application/dns-json" + +# App +curl.exe -sI https://casting.grudge-studio.com/ +# expect HTTP/2 200 and Server: Vercel +``` + +**Working now without DNS:** https://casting-abilities-threejs.vercel.app/ + +--- + +## Not the problem + +- Vercel production deployment (Ready, alias on project) +- Vite / Three.js app body on `*.vercel.app` +- CDN character meshes / R2 +- Casting **API/DB** wiring (Railway health OK; SPA uses `/api` rewrite + fleet CORS) + +## Related + +- Deploy/env/API SSOT: `docs/CASTING_DEPLOY_ENV_SSOT.md` +- After DNS: `curl.exe -sI https://casting.grudge-studio.com/api/health` → Railway JSON + +## Deployment note + +Do not “redeploy DNS” with another Vercel build — fix Cloudflare record only. +Prod deploy already aliases `casting.grudge-studio.com` (SSL pending DNS). diff --git a/docs/CASTING_LAB_SSOT.md b/docs/CASTING_LAB_SSOT.md new file mode 100644 index 0000000..db26c06 --- /dev/null +++ b/docs/CASTING_LAB_SSOT.md @@ -0,0 +1,275 @@ +# Casting Lab SSOT — Warlords UX / ability effects + character path + +**Hosts:** https://casting-abilities-threejs.vercel.app · https://casting.grudge-studio.com (when DNS live) +**Agent skill:** `~/.grok/skills/casting-warlords-lab/SKILL.md` +**Repo:** `C:\Users\nugye\Documents\CastingAbilitiesThreeJS` +**Anim library:** `docs/ANIM_LIBRARY_SSOT.md` · `src/config/animLibrary.js` +**Session state:** `docs/SESSION_STATE_SSOT.md` · `src/core/SessionState.js` +## Character path (deployed reference — do not fork) + +| Concern | Value | +|---------|--------| +| **Live proof** | https://casting-abilities-threejs.vercel.app/ | +| **Code** | `src/character/toonKitPlay.js` → `deployToonPlayKit` | +| **Mesh** | Toon RTS `assets…/toon-rts-characters/glb/characters/{race}.glb` only | +| **Contract** | `warlordsPlayContract = 2026-08-07.harden.1` (ObjectStore same) | +| **ObjectStore** | `js/grudge6-kit.js#loadRaceKit` — same rules, fleet SSOT module | +| **Removed** | races bake / metaverse / FBX play, multi-pose unify, scaffoldGrudge6Kit | + +Other Warlords apps should **copy this path**, not invent loaders. +CDN may still host races/metaverse for author/compare — **not** play defaults. + +## Purpose + +Test and push **client UX/UI** and **editable ability effects** for Warlords-era game modes +**before** shipping to grudgewarlords.com / Open Danger / island play. + +Macro loop: + +``` +learn source → isolate primitive → edit knobs → bind skill → smoke → export prefab JSON +``` + +**Not:** full MMO shell, Railway player SSOT, second combat engine, Main Panel fork. + +--- + +## What is already strong + +| System | Notes | +|--------|--------| +| Element path VFX | Fire/Water/Earth/Wind full beauty stack (`settings.fire`…) | +| Live editor | lil-gui → `settings.js` (no rebuild mid-cast) | +| **Effect Prefab folder** | intensity / AOE / speed / size / color / mesh + export JSON | +| VfxDirector + catalog | Aligns with vfxgrudge.puter.site | +| **F = melee residual** | Attack anim + tip spawn + `settings.residual` knobs | +| Toon character + packs | SI, hands-on idle, jump/double/backflip, cast | +| DRC combat UX | TPS, 1–4 spells, stamina, Q equip/combat | +| **Lab Panel (I)** | Left tabs: Character · Equipment · Weapon · Race · Mesh · Mount · Anims · Skills · API | +| **HUD frames** | Player / target / allies | +| **Tight bar (Danger)** | threejs-rapier 6+6 + avatar + HP/STA orbs + menu buttons | +| **Combat hotkeys** | Shift freelook-run · Ctrl+A/D Ghost Rider roll · Shift+Ctrl slide · AA/DD/WW dodge · X back · C parry · LMB path · F/1–4 · Q | +| **Roll clips** | `ghost_rider/roll_left|right|forward|back` (primary) · locomotion fallbacks | +| **Slide clip** | `prod:extra/running-slide` (+ quick-roll-to-run) | +| **Dodge clips** | longbow standing dodge L/R/F/B (+ locomotion / ghost_rider dodge) | +| **Anim library SSOT** | `animLibrary.js` vocabulary · families · `getAnimLibrary()` · Showcase Anims by family | +| **Windsurf freeride** | Frontflip + sail from back · feet/hands IK · WASD boat (tslda) · `docs/WINDSURF_RIDE_SSOT.md` | +| **Island stage** | Planar pad + shore + water ring · `docs/ISLAND_STAGE_SSOT.md` | +| **Staff elements** | 1 Fire · 2 Storm · 3 Ice · 4 Nature · 5 Holy · 6 Arcane (`ELEMENTS` / `ELEMENT_META`) | +| **Game item prefabs** | All categories → `docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md` · I→**Prefabs** · ObjectStore masters | +| **Asset library audit** | Keep/remove half-done R2/D1/catalogs → `docs/ASSET_LIBRARY_AUDIT_SSOT.md` | +| **Transit mastery** | `docs/ELEMENT_TRANSIT_MASTERY_SSOT.md` — Ability TRAVEL + materials + 4 path modes | +| **All STAFF skills** | `staffWeaponSkillsBind.js` ← WEAPON_SKILLS.html / master-weaponSkills STAFF | +| **Session SSOT** | `SessionState` mode · drc · ridePhase · gates (no ad-hoc mode forks) | +| **Dodge afterimage** | Model-color blur + vapor dissipate (`DodgeAfterimage.js`) | +| **Dodge MM** | `combat/motionMath.js` — 100 MM = 1 m; AA/DD lateral **720 MM (7.2 m = 3×)** | +| **Dodge afterimage** | `vfx/DodgeAfterimage.js` — SkeletonUtils mesh ghosts, wind cyan additive | +| **Dodge invuln** | `DrcCombatController.isInvincible` / `invuln` for full dodge duration | +| **Staff path cast** | LMB hold-draw → AOE place · spikes · wall · stream (`settings.staffCast`) | +| **Showcase (O / Show)** | Race · mesh · weapon type · anims · bind master skills to 1–4 / F | +| **Weapon skills SSOT** | `info…/api/v1/master-weaponSkills.json` v3.1 · 268 skills · WEAPON_SKILLS.html | +| **Production skill pattern** | `docs/WEAPON_SKILL_PRODUCTION_SSOT.md` · `weaponSkillProduction.js` · statuses · scaffold | +| **SDK export** | `src/sdk/castingLabSdk.js` · `docs/CASTING_SDK_EXPORT_SSOT.md` · `/api/v1/casting-lab-contract.json` | +| **SSOT index** | `docs/SSOT_INDEX.md` | +| **6 races** | Toon RTS GLB via Race tab (`setRace`) | +| **Fleet API client** | `src/api/fleetApi.js` → Railway health + characters | +| World SI | `worldScale.js` MAP_SCALE 1.5 + StageWater | + +### Lab Panel vs Main Panel + +| Surface | Host | Role | +|---------|------|------| +| **Lab Panel** | casting.* | Dev/tester: race kits, mesh_ids, packs, skills, API ping | +| **Main Panel** | ui.grudge-studio.com / Open | Production account inventory + equipment SSOT | + +Do **not** fork Main Panel UI inside casting — link out + exercise contracts. + +--- + +## Effect primitives (isolate for shared use) + +Author **once**, bind many skills. Do not bake one-off mega-shaders per skill. + +| Primitive | Casting source | Open / fleet source | Edit knobs | +|-----------|----------------|---------------------|------------| +| **Trail** | `settings.trail`, PathTrail, ability ribbons | blade trail, strawberry trail | width, length, opacity, color, flow | +| **Travel** | Ability path + particles | orbs (`orb-fire`…), path cast | speed, size, arc, meshId, color, lifetime | +| **Cast** | VfxDirector cast / hand | fire_hand, arcane_swirl | intensity, attach bone | +| **Impact** | Burst, flash, shake, decals | inferno, frost_wave, strawberry force | aoe, intensity, life, color | +| **Residual** | F strike + `settings.residual` | Getsuga / meleeStrikeFx | range 1–10 m, meshScale, contactRadius, variant | +| **Decal** | GroundDecals | same | radius, life, colorA/B | +| **Aura** | fire_aura catalog | status aura | radius, spin, intensity | + +### Schema (`src/vfx/effectPrefab.js`) + +```ts +type EffectPrimitive = { + kind: 'trail' | 'travel' | 'cast' | 'impact' | 'residual' | 'decal' | 'aura'; + intensity: number; // 0..2 + aoe: number; // metres + speed: number; // m/s + size: number; // SI scale + color: string; // #rrggbb + meshId?: string; // slash* | orb-* | none + duration?: number; + attach?: 'R_hand' | 'L_hand' | 'root' | 'feet' | 'weapon_tip'; + effectId?: string; // catalog id +}; +``` + +**Editor:** lil-gui → **⚡ Effect Prefab** +**Export:** solo kind · melee residual · fire bolt · all skill pack JSON + +--- + +## Melee → projectile from weapon mesh (learned + wired) + +**Open SSOT:** `gameopen/docs/MELEE_SLASH_FX.md` · `meleeStrikeFx.ts` + +| Step | Rule | Casting lab | +|------|------|-------------| +| 1 | Attack anim one-shot (`sword_shield` / pack attack) | `playWeaponAttack` / `requestOneShot('attack')` | +| 2 | Hit frame delay (apex) | `settings.residual.hitFrameDelay` (default 0.18 s) | +| 3 | Origin = weapon tip | `getWeaponTip` (muzzle / mesh AABB / tipOffset) | +| 4 | Beyond blade | `beyondBladeM` — hit volume starts past tip | +| 5 | Dir = blade / facing | `getWeaponForward` or facing on XZ | +| 6 | Swing trail | `WeaponTipTrailSystem` ribbon samples tip each frame | +| 7 | Apex residual | `getsuga_slash` + fire blur path + physics projectile | +| 8 | Profile knobs | range, speed, trail*, fireTrail, contactRadius, aoeRadius | + +**Input:** **F** = melee residual · **Space** = jump only · **1–4** = element spells +**Never:** free Space Getsuga · whole `fireball.glb` as bolt + +### Open profile examples (1H) — learn ranges + +| Stage | Range | Variant | meshScale | +|-------|-------|---------|-----------| +| light | ~1.15 m | slashblue | 0.55 | +| mid | ~4 m | slashblue | 0.9 | +| finisher | ~9 m | slashyellow | 1.35 | + +Lab default residual: range **3.2 m**, slashblue, meshScale **0.9** — tune in editor. + +--- + +## Skill ability sources (do not miss) + +| Source | Path / URL | Learn | +|--------|------------|--------| +| **vfxgrudge panel** | https://vfxgrudge.puter.site/ | Hotkeys V/B/F/G/T/C + library | +| **VFX Studio** | https://vfx.grudge.studio/ | Full library / skillswrite | +| **Open catalog** | `artifacts/animator/src/three/fx/vfxEffectCatalog.ts` | Stable effectIds | +| **Casting catalog** | `src/vfx/vfxCatalog.js` | Bound subset + SKILL_VFX_BIND | +| **meleeStrikeFx** | `artifacts/animator/src/three/combat/meleeStrikeFx.ts` | Residual profiles | +| **MELEE_SLASH_FX** | `gameopen/docs/MELEE_SLASH_FX.md` | Deterministic residual rules | +| **Orbs** | `docs/vfx/FIREBALL_ORBS.md` · `models/vfx/orbs/*` | Staff projectiles (not whole fireball) | +| **Strawberry** | `docs/vfx/STRAWBERRY_STRIKE_MULTI_FX.md` | force / trail / slash / cool modes | +| **T0 kits** | `t0WeaponSkills.ts` | Combo/Special/Ranged/Power | +| **Element trees** | `src/combat/elementWeaponSkills.js` | cast/travel/impact per staff | +| **Blade trail** | Open `Vfx.bladeTrailSegment` · Studio swingTimer | grip→tip ribbon | +| **Slash meshes** | `models/vfx/slash/slash{color}.glb` | Residual visuals | +| **Kenney audio** | kenney-audio skill | Hit/cast SFX pairs | + +### Harvested gaps → lab status + +| Opportunity | Status | +|-------------|--------| +| Getsuga residual on F | **Wired** (`useMeleeStrike` + residual knobs) | +| EffectPrefab export | **Wired** (Editor ⚡ folder) | +| Orb travel for staff | Next — bind `meshId: orb-*` to Ability travel | +| Blade trail during swing | **Wired** — `WeaponTipTrailSystem` samples tip each frame | +| Apex residual + physics past blade | **Wired** — `beyondBladeM` + projectile at hitFrameDelay | +| Fire blur trail on residual | **Wired** — fire Ability path + warm ribbon (`fireTrail`) | +| Combo apex per hit | **Wired** — each attack1–3 / finisher begins a new swing session | +| Strawberry force/cool presets | **Editor buttons** apply knobs (no mesh yet) | +| Main Panel T0 icons on DRC bar | Next — icons only, no new radial | +| Wind SKILL_VFX_BIND | **Fixed** | +| Arcane SKILL_VFX_BIND | **Fixed** | + +--- + +## Animation learning (smooth, clean) + +| Role | Lab clips | Expansion | +|------|-----------|-----------| +| idle | prod magic standing-idle (**hands**) | per weapon pack idle | +| walk/run | magic walk/run | locomotion_8way later | +| cast | 1h/2h cast | channel loop overlay | +| attack | sword_shield attack | combo stages 0–2 | +| jump | standing-jump | fall / land | +| backflip | procedural + jump | baked flip if added | + +**Rules:** rotation-only tracks; prod-first; rematch bones-only; re-ground after equip; one mixer; RideIK only mounted. + +--- + +## UX expansion (Warlords game modes) + +``` +casting lab + ├─ Combat mode (TPS · WASD · 1–4 · F interact · E block · C parry · Space jump) + ├─ 10-spell kit (castingSpellKit → WEAPON_SKILLS STAFF) · I Skills pages + ├─ Equip / inventory mesh_ids + ├─ Path cast + VFX editor + Effect Prefab export + ├─ Element bar + optional ?arcane=1 tree + └─ Walk ride (optional) + │ + ▼ push contracts only (JSON prefab + feel) + grudgewarlords.com / Open Danger / island +``` + +**Do not** rebuild Main Panel radial inside casting — link/embed Open when ready. + +--- + +## Agent skill-learning loop + +``` +1. Load casting-warlords-lab + grudge-vfx-hotkeys + grudge-vfx-orbs-strike + combat-runtime +2. Identify primitive (trail|travel|cast|impact|residual) +3. Find existing settings / catalog id — extend, don't fork +4. Knobs in settings.effect + settings.residual + Editor +5. Wire VfxDirector.deploy / Ability / useMeleeStrike +6. Smoke on casting-abilities-threejs.vercel.app +7. Export prefab JSON for Warlords import +``` + +### Confirmation gates + +``` +[x] EffectPrefab type + export +[x] Melee residual from weapon tip + hit frame (F) +[ ] Orb travel for staff (meshId orb-*) +[ ] Blade trail grip→tip during attack window +[ ] Space = jump only (not Getsuga) +[ ] Orbs not whole fireball.glb +[ ] SI sizes +[ ] Shared primitive reusable by ≥2 skills +[ ] Editor knobs map 1:1 to settings fields +``` + +--- + +## Repo anchors + +| Concern | File | +|---------|------| +| Live params | `src/config/settings.js` (`effect`, `residual`, elements) | +| Prefab schema / export | `src/vfx/effectPrefab.js` | +| World SI | `src/config/worldScale.js` | +| VFX catalog | `src/vfx/vfxCatalog.js` | +| Deploy beauty | `src/vfx/VfxDirector.js` | +| Editor | `src/ui/Editor.js` (⚡ Effect Prefab) | +| DRC skills | `src/combat/drcSkills.js` · `elementWeaponSkills.js` | +| Melee F strike | `src/combat/DrcCombatController.js` `useMeleeStrike` | +| Weapon tip | `src/animation/CharacterController.js` `getWeaponTip` | +| Element path FX | `src/abilities/*Ability.js` | + +## Sibling skills + +`grudge-studio` · `grudge6-full-stack` · `grudge6-combat-runtime` · `grudge-fleet-combat` · +`grudge-vfx-hotkeys` · `grudge-vfx-orbs-strike` · `craftpix-rpg-mmo-ui` · `kenney-audio` + +## DNS note + +`casting.grudge-studio.com` requires Cloudflare CNAME (zone edit). App is always on Vercel production URL. diff --git a/docs/CASTING_MASTER_MERGE.md b/docs/CASTING_MASTER_MERGE.md new file mode 100644 index 0000000..c32e3b4 --- /dev/null +++ b/docs/CASTING_MASTER_MERGE.md @@ -0,0 +1,91 @@ +# Casting Master Merge — Linear + CastingAbilities → Islands + +**Version:** 1.2.0 +**Sources:** + +| Repo | Role | +|------|------| +| [LinearAbiltyCastingThreeJS](https://github.com/MolochDaGod/LinearAbiltyCastingThreeJS) | Line/zone skillshots, procedural GLSL materials, MOBA aim | +| [CastingAbilitiesThreeJS](https://github.com/MolochDaGod/CastingAbilitiesThreeJS) | Path cast, weapon skills, VfxDirector, three-layer terrain, staff orbs | +| [simonstorlschulke threejs-examples scene 0](https://simonstorlschulke.github.io/threejs-examples/?scene=0) | Infinite terrain / height sample patterns (L0 SSOT) | + +## What shipped in Grudge Builder + +| Path | Content | +|------|---------| +| `shared/casting/masterCatalog.ts` | Product→linear map, cast planner, terrain rules | +| `client/src/island3d/casting/` | Vendored skillshot + shaders + path abilities + **CastingMaster** | +| `client/src/island3d/vfx/WorldFxBus.ts` | `attachCastingMaster` / `castSkill` | +| `client/public/api/v1/casting-master-contract.json` | Public contract for **info.grudge-studio.com** | +| `client/public/api/v1/casting-lab-contract.json` | Lab SDK contract | + +## Three-layer islands + +``` +L0 height — one sample for feet · mesh · aim · grass +L1 surface — visual ground on L0 +L2 vegetation — grass / forest (heightSample callback) +L3 detail — rocks / harvest +water — sibling (not L1) +``` + +Code: `island3d/casting/world/terrainLayers.js` (+ IslandHeightfield, StylizedGrass, GrowingForest). + +## Linear skillshots (styles) + +| Id | Name | Shape | Product elements | +|----|------|-------|------------------| +| ice | Frost Lance | line | ice | +| thunder | Storm Lance | line | storm | +| meteor | Cinder Fall | line | fire | +| beam | Nova Beam | line | holy | +| snare | Voltaic Snare | zone | arcane | +| glacier | Glacier Wall | zone | nature | + +Hotkeys (lab): Q / E / R / F / V / G + +## GLSL + +- `shaders/lib/common.glsl.js` — soft particles, fresnel, dissolve, gradients +- `shaders/lib/noise.glsl.js` — simplex, fbm, ridged, curl, voronoi +- Materials under `skillshot/materials/` and `materials/` — ice, lightning, meteor, beam, snare, glacier, fire, trail, wind, water + +## Runtime API + +```ts +import { getWorldFxBus } from '@/island3d/vfx/WorldFxBus'; +import { SpellFxSystem } from '@/island3d/vfx/SpellFxSystem'; + +const bus = getWorldFxBus(scene); +const spellFx = new SpellFxSystem({ parent: scene }); +bus.attachCastingMaster({ spellFx, getHeight: (x, z) => heightAt(x, z) }); + +bus.castSkill({ + skill: { element: 'fire', id: 'staff_fire_bolt' }, + origin: player.position, + direction: aimDir, + distance: 14, + intensity: 1, +}); +``` + +## info.grudge-studio.com + +Mirror / fetch: + +- `https://client.grudge-studio.com/api/v1/casting-master-contract.json` +- Lab: `https://casting.grudge.studio/api/v1/casting-lab-contract.json` + +## Deploy + +| Target | Command | +|--------|---------| +| Grudge Builder (Vercel) | `npm run build:client` · existing project aliases | +| Casting lab | `cd CastingAbilitiesThreeJS && npx vercel --prod` | +| Contract only | ship `casting-master-contract.json` on client public | + +## Do not + +- Second heightmap for feet vs grass +- Empty AnimationClips / dual mixers for cast tells +- Invent skill ids outside master-weaponSkills / t0 catalog diff --git a/docs/CASTING_SDK_EXPORT_SSOT.md b/docs/CASTING_SDK_EXPORT_SSOT.md new file mode 100644 index 0000000..2145f22 --- /dev/null +++ b/docs/CASTING_SDK_EXPORT_SSOT.md @@ -0,0 +1,80 @@ +# Casting Lab SDK export SSOT + +**Module:** `src/sdk/castingLabSdk.js` +**Package export:** `casting-abilities/sdk` (see root `package.json` `exports`) +**Runtime contract:** `GET /api/v1/casting-lab-contract.json` (static under `public/`) + +--- + +## Purpose + +Give Open / Warlords / satellites a **single import surface** for lab systems that are ready to ship: + +| Export group | Systems | +|--------------|---------| +| Loaders | shared Draco · Meshopt · KTX2 bind | +| Skills | production compile · statuses · delivery · staff bind | +| Linear | planElementalLinearCast · PRODUCT_TO_LINEAR | +| Terrain | mountTerrainLayers · heightAt · projectToTerrain | +| VFX | staff orbs · element attack kinds | +| Contract | CASTING_LAB_CONTRACT version stamp | + +**Not exported:** full `App` shell, AdminHub, lil-gui editor — those stay lab-only. + +--- + +## Import + +```js +// From Casting repo (monorepo path) +import { + sharedGltfLoader, + bindKtx2, + compileProductionWeaponSkill, + mountTerrainLayers, + CASTING_LAB_CONTRACT +} from 'casting-abilities/sdk'; + +// Or relative +import { … } from '../CastingAbilitiesThreeJS/src/sdk/castingLabSdk.js'; +``` + +### Fleet install pattern (later npm) + +```bash +# When published as @grudge-studio/casting-lab or workspace path: +npm install casting-abilities +``` + +Until then, copy contract JSON to ObjectStore or depend via git path. + +--- + +## Definitions (SSOT authority) + +| Definition | Source | +|------------|--------| +| Skill rows | info `master-weaponSkills` / `t0-weapons` | +| Skill runtime package | `compileProductionWeaponSkill` | +| Status ids | `skillStatusSystem.parseCatalogEffects` | +| Element → linear | `PRODUCT_TO_LINEAR` | +| Decoder pins | `gltfPipeline` DRACO / KTX2 paths | +| Terrain layers | `TERRAIN_LAYER` + THREE_LAYER_TERRAIN_SSOT | +| Character play | grudge6 loadRaceKit / toonKitPlay | + +--- + +## Deploy optimization + +| Do | Don't | +|----|--------| +| Ship split orbs/rocks/arrows only | Ship `*_src.glb` multipacks in prod (gitignored) | +| One sharedGltfLoader | new GLTFLoader / DRACOLoader per system | +| Contract JSON static on Vercel | Duplicate pin strings in consumers | +| Warm skill overrides on equip | Sync-fetch overrides per skill cast | + +--- + +## Version + +Bump `CASTING_LAB_SDK_VERSION` + `public/api/v1/casting-lab-contract.json` together when removing/renaming exports. diff --git a/docs/COMBAT_CAMERA_FOCUS_SSOT.md b/docs/COMBAT_CAMERA_FOCUS_SSOT.md new file mode 100644 index 0000000..6fccfd2 --- /dev/null +++ b/docs/COMBAT_CAMERA_FOCUS_SSOT.md @@ -0,0 +1,133 @@ +# Combat camera + soft-lock focus SSOT + +**Refs:** +- Camera angles: [grudge-third-person-controller](https://github.com/MolochDaGod/grudge-third-person-controller) (Fortnite/WoW blend) +- Targeting: skill `grudge-combat-targeting` (LMB select · RMB focus toggle · soft lock) + +**Code:** `CameraRig.js` · `CombatFocus.js` · `MouseAim.js` · `settings.camera` / `settings.aim` + +--- + +## What we take from the third-person controller + +**Repo:** [grudge-third-person-controller](https://github.com/MolochDaGod/grudge-third-person-controller) · Fortnite mode in `CAMERA_MODES.md` + +| Feature | Production value (Fortnite TPS) | +|---------|----------------------------------| +| Distance | **5.5 m** focus · ~6 m free | +| Shoulder | **0.8 m** focus · 0.72 free | +| FOV | **70°** base · **78°** sprint kick (GRUDOX animator / DGS) — not action zoom | +| Soft lock | **ON in focus** · Tab / Shift+Tab cycle · **no auto camera yaw/pitch** | +| Crosshair | Screen-centre reticle · bloom gap · range ring (close/optimal/far) · hit pulse | +| Soft-look weight | free **0.28** · focus **0.42** (frame target, not snap) | +| Smooth damping | Calm follow without OrbitControls fight | +| Editor | **Closed on focus ON** — VFX edit / admin purged from combat look | + +## What we keep (production) + +| System | Behavior | +|--------|----------| +| Soft lock | Bias **aim point / reticle** toward target only — **no hard snap**, no auto camera angles | +| Session gates | TPS only when combat/freeride | +| Path cast LMB | Staff path remains primary cast stroke | +| OrbitControls | **Disabled in TPS** (fleet hard rule) | +| Ride parent | Windsurf still owns transform | + +--- + +## Input (combat) + +| Input | Action | +|-------|--------| +| **RMB click** (short, no drag) | Toggle **focus** (sticky) · soft lock ON · auto nearest target | +| **RMB hold** (Focus OFF) | Orbit camera pitch/yaw | +| **Mouse move · Focus ON** | **Look** (mouse = camera aim; OS cursor hidden) | +| **Tab** | Soft-lock **next** target in range | +| **Shift+Tab** | Soft-lock **previous** target | +| **LMB · Focus ON** | **Attack** (primary / residual) | +| **LMB · Focus OFF** | **Select** target (soft lock) · **unlocked mouse** | +| **LMB · sandbox / walk** | Path draw (cast or ride course) | +| **Shift hold** | Sprint | +| **Shift + tap Ctrl** | **Slide** (while sprinting) | +| **Ctrl** (+ optional dir) | **Roll** forward / L/R/B with WASD | +| **Focus ON** | Cursor **none** · center **crosshair** · pointer-lock look · body **lag-follows** camera (deadzone + low turn rate) · WASD cam-relative · LMB attack | +| **Focus OFF** | Unlocked cursor; LMB select; A/D tank-turn; W/S body forward | +| Soft target | Soft-lock bias when a target is selected (not hard snap) | + +### Two aim visuals (do not conflate) + +| Visual | When | +|--------|------| +| **Screen crosshair** (HUD center) | Focus ON only — look / projectile aim | +| **Ground ring** (`aimMarker`) | Placement / AoE / path only: wall, spikes, zone, trap, summon, path draw, `at_location` / `path_*` / under-target — **not** idle focus look or pure melee residual | + +Defaults: `focusTurnSpeed` **6.5**, `focusTurnDeadzoneDeg` **16°** (Editor → Controls → Aim). + +### Focus = “remove the mouse” + +| Piece | Focus ON | Focus OFF | +|-------|----------|-----------| +| OS cursor | **Hidden** (`cursor: none` + pointer lock when allowed) | Default / free | +| Reticle | Screen-center HUD crosshair | Off | +| Ground ring | Only if skill needs ground aim | Only if path draw / placement cast | +| Aim point | **Snow-brawl ray**: `setFromCamera(center)` → ground/colliders/far hit | Free pointer NDC → ground | +| Launch | `dir = (hit − spawn).normalize()` 3D · hand offset · chest spawn | Horizontal aim XZ | +| Soft lock | Magnetic pull **inside cone** (`softLockMaxAngleDeg`) — no hard snap | Select only | +| Look | `movementX/Y` drives TPS yaw/pitch · body lag-follows past deadzone | RMB hold only | +| LMB | Attack (projectile uses 3D launch) | Select target | + +Register enemies with `combatFocus.addSelectable(mesh, 'hostile')`. + +### Projectile vector math (snow-brawl ref) + +Discourse: [snow-brawl](https://discourse.threejs.org/t/snow-brawl-full-featured-holiday-snowball-shooter-in-pure-three-js/88678) +Local design HTML: `_grudge-kit-extract/.../Pasted--https-discourse-threejs-org-t-snow-brawl-...html` + +``` +ndc = (0,0) // focus crosshair +ray.setFromCamera(ndc, camera) +hit = intersect(ground|walls) || origin + dir * aimRayFar +// soft-lock: if target in cone, lerp hit toward target.chest +spawn = feet + up*spawnHeight + forward*spawnForward + right*handOff +vel = normalize(hit − spawn) * speed +``` + +Code: `MouseAim.updateFocusAim` · `MouseAim.computeLaunch` · `DrcCombatController` skill release. + +### Focus strafe (lower body) + +When focus is ON and A/D dominate WASD, play pack **walkL/walkR** or **runL/runR** (CDN: `prod:magic/standing-walk-left` etc., longbow side walks). Forward W/S still uses walk/run. + +--- + +## Action mode polish + +| Feature | Behavior | +|---------|----------| +| FOV | Free `camera.fov` (70) → focus `actionFov` (85), damped | +| Crosshair | Positional N/E/S/W ticks · soft-lock tint · fire scale · spread from move | +| Strafe gait | World move · cam right/forward → walkL/R / runL/R when lateral dominates | +| Backflip | Real FBX clip when bound · **camera yaw held** (setup for air attack) | +| Frontflip | Real extra/front-flip FBX when bound | +| **Directional soft lock** | Acquire / Tab order by **camera cone × distance** (not pure nearest) | +| **Yaw assist** | Subtle `softLockYawAssist` rad/s toward target in cone — framing help | +| **Pitch assist** | Soft damp pitch toward target chest while locked | +| **Aim magnet** | Crosshair hit blends toward soft target inside `softLockMaxAngleDeg` | +| **Camera ownership** | Focus: mouse owns orbit yaw (not body-driven) · free: body drives base | + +## Agent rules + +1. Extend `CameraRig` / `CombatFocus` / `MouseAim` — do not import a second controller +2. Soft lock ≠ camera hard lock (cone-limited magnetic only) +3. Focus projectiles use **3D** `forward3d` / `computeLaunch` — do not flatten Y to ground-only +4. Body still rotates with camera look in focus +5. Backflip must **not** whip camera 180° — hold look yaw +6. Prefer Open FBX flip clips rematched to Bip001 (`fbxClip.js`) +7. Never enable OrbitControls during TPS combat +8. Clear `rmbHeld` on blur / visibilitychange + +## Related + +- `PRODUCTION_CONTROLLER_SSOT.md` +- `SESSION_STATE_SSOT.md` +- skill `grudge-combat-targeting` diff --git a/docs/CRAFTPIX_HUD_SSOT.md b/docs/CRAFTPIX_HUD_SSOT.md new file mode 100644 index 0000000..1f17665 --- /dev/null +++ b/docs/CRAFTPIX_HUD_SSOT.md @@ -0,0 +1,60 @@ +# CraftPix HUD SSOT — Casting / Warlords player chrome + +**Skill:** `craftpix-rpg-mmo-ui` +**Host:** casting-abilities-threejs.vercel.app · casting.grudge-studio.com +**Code:** `src/ui/craftpixUi.js` · `src/ui/craftpix-hud.css` · `src/ui/HUD.js` + +## Source + +| Layer | Path | +|-------|------| +| Disk SSOT | `D:\Games\Models\craftpix-rpg-mmo-ui\Textures\` | +| App ship | `public/ui/craftpix/{unit,hotbar,fill,panel}/` | +| CDN | `https://assets.grudge-studio.com/ui/craftpix/...` | + +## Unit frame layer stack + +1. `unit/avatar_bg.png` +2. Race portrait (`client…/images/portraits/{race}.png`) +3. `unit/avatar_border.png` +4. `unit/avatar_overlay.png` +5. Bars: `unit/pb_*` (HP) · `unit/sb_*` (MP/STA) with hue filter tints + +## Hotbar slot stack + +1. `hotbar/slot_bg.png` +2. Glyph / skill icon +3. CD dark plate `scaleY(--cd)` +4. `hotbar/slot_border.png` +5. Press plate on `:active` + +## Rules + +- Do **not** ship Font Awesome–only chrome when CraftPix is available. +- Prefer **local** `/ui/craftpix` (Vercel) over CDN for same-origin paint. +- HYDRA layouts remain at 1920×1080 design space; current casting HUD is responsive absolute chrome, not full HYDRA stage. +- TightBar (Danger orbs) coexists; CraftPix owns unit frames + element hotbar. + +## Cast bar (shipped) + +| File | Role | +|------|------| +| `cast/bg.png` | Track plate | +| `cast/fill.png` | Progress fill (element-tinted) | +| `cast/icon_frame.png` | Spell gem frame | +| `src/ui/CastBar.js` | Progress UI | +| `DrcCombatController` | `castDuration` channel · interrupt on WASD · path cast time | + +**Skill cast:** digits 1–6 → cast bar for `skill.castDuration` (catalog/staff bind) → cast loop anim → release VFX. +**Path cast:** LMB release → cast bar 0.35–1.6s from hold+length → cast anim → ability travel. +**Interrupt:** movement cancels channel (toast “Cast interrupted”). + +## Review checklist (production) + +- [x] Player frame uses UnitFrame avatar + PB/SB plates +- [x] Target frame hostile tint + empty state dimmed +- [x] Action slots use AB_MainSlot layers +- [x] Portrait from production race portraits when race known +- [x] Cast bar under crosshair (Cast Bar pack) + real cast times +- [x] Cast animations loop during channel +- [ ] Optional: full HYDRA combat-2bar layout scale root diff --git a/docs/DEVNODE_SSOT.md b/docs/DEVNODE_SSOT.md new file mode 100644 index 0000000..47f2101 --- /dev/null +++ b/docs/DEVNODE_SSOT.md @@ -0,0 +1,96 @@ +# /devnode — Training Room · DevIsland authoring + +**URL:** `…/devnode.html` (local) · `…/devnode.html` on casting deploy +**Map:** same as play — **`training_room`** (aliases: devisland, devnode) +**Purpose:** Edit **nodes** for biomes, terrain knobs, cliffs, trees, rocks, flowers/hemp, animals, ore, PvE — without inventing a second Forge or harvest engine. + +**SSOT:** `docs/TRAINING_ROOM_SSOT.md` · `src/world/trainingRoomMap.js` + +--- + +## Review: what already exists (extend these) + +| System | Role | Reuse on /devnode | +|--------|------|-------------------| +| `devIslandCatalog.js` | Harvest defs, rock/ore meshes, default layouts | Palette rock/ore/herb | +| `DevIslandHarvest.js` | F interact, HP, loot, dummies | Import layout → play | +| `GrowingForest.js` | Growing trees, wood harvest | Tree palette | +| `IslandHeightfield.js` | Land height + shore | Biome terrain knobs | +| `AdminHub` F2 Assets | Buildable/harvestable drafts + purpose | Export feeds drafts | +| `AdminHub` F3 Creatures | Enemy/ally/NPC | PvE / animals | +| `deployableContract.js` | harvestable · enemy · buildable kinds | Contract alignment | +| `Editor.js` (lil-gui) | VFX/live settings | Not duplicated — link to main lab | +| Desktop `forestoutline.html` | Instanced tree CONFIG / LOD / leaf texture | Reference for tree procedural | + +**Do not:** second physics world, second mixer, second ObjectStore mint path, Meshy capsule heroes. + +--- + +## Creative resources (ready vs stub) + +| Family | Ready assets | Stub / next wire | +|--------|--------------|------------------| +| **Rocks / ore / cliffs** | `public/models/dev-island/rock__*` | — | +| **Herbs** | Procedural stub + loot icons | Nature flora GLB when on CDN | +| **Trees** | GrowingForest procedural | Optional GLB tree packs later | +| **Flowers / hemp** | Palette procedural stubs | Nature pack meshPool | +| **Terrain / biomes** | Heightfield seed/amp | Biome material kits | +| **Animals** | Palette only | F3 creature + fauna CDN | +| **PvE** | Training dummy live | grudge6 enemy draft F3 | +| **Loot icons** | `icons/dev-island/*` | ObjectStore materials | + +--- + +## Node document (export) + +```json +{ + "version": 1, + "source": "casting-devnode", + "biomeId": "temperate_meadow", + "terrain": { "seed": 17, "amp": 0.7, "flatCore": 10 }, + "nodes": [ + { "id": "n1", "paletteId": "node.rock_ore", "x": 4.2, "z": -3.1, "yaw": 0.4, "scale": 1 } + ] +} +``` + +Play lab imports via `DevIslandHarvest.applyNodeLayout(layout)` (localStorage handoff after Export). + +--- + +## Editor options (MVP) + +| Control | Action | +|---------|--------| +| Biome select | Sets terrain seed/amp + filters palette | +| Family filter | cliff · tree · rock · ore · flower · hemp · animal · pve | +| Click place | Drop selected palette on terrain sample Y | +| R click / Del | Remove nearest | +| Export JSON | Download layout | +| Import JSON | Load layout | +| Open play lab | `index.html` main casting | + +--- + +## Admin alignment + +| Hotkey | Tab | Node family | +|--------|-----|-------------| +| F2 | Assets | harvestable, cliff buildable | +| F3 | Creatures | animal, pve_mob | +| ] | World | biome / terrain notes | + +--- + +## Confirmation + +``` +[x] /devnode.html loads palette from NODE_PALETTE +[x] Place rock/ore uses real GLB from dev-island (meshPool + GLTFLoader cache) +[x] Terrain sample places Y on heightfield (IslandHeightfield + projectToTerrain) +[x] Export JSON validates (validateNodeLayout) +[x] No second harvest runtime inside devnode (preview only) +[x] DevIslandHarvest.applyNodeLayout(layout) — play imports Training Room storage +[ ] Fauna CDN meshes for animal_passive / pve_grunt +``` diff --git a/docs/DROP_RATES_SSOT.md b/docs/DROP_RATES_SSOT.md new file mode 100644 index 0000000..ec2fc23 --- /dev/null +++ b/docs/DROP_RATES_SSOT.md @@ -0,0 +1,112 @@ +# Drop rates SSOT (casting lab mirror) + +**Fleet source:** ObjectStore `api/v1/drop-tables.json` + `docs/DROP_TABLES_SSOT.md` +**Runtime:** `src/loot/dropTables.js` +**Presentation:** `src/loot/prefabAssets.js` + `src/world/WorldDrops.js` (T0–T8 full pattern) + +## Prefab pattern (all tiers T0–T8) + +T6 / T7 / T8 **are full citizens** of the prefab systems: + +| System | T6–T8 | +|--------|-------| +| `master-weapon-prefabs` (stats, skills, icons, models) | Yes | +| World drop (icon sprite, bloom glow, tier border, mini model) | Yes | +| Bag / throw / equip / craft / economy | Yes | +| Natural open-world RNG (`mob_*`, common chests, harvest, normal boss/raid) | **No** | + +They are **not** stripped from catalogs or presentation — only gated out of **natural** loot. + +## Natural vs special loot + +| Source class | Max tier | Examples | +|--------------|----------|----------| +| **Natural** | **T5** | `mob_trash`, `mob_normal`, `mob_elite`, `chest_common`, `chest_uncommon`, `boss`, `raid`, `harvest_node` | +| **Special** | **T8** | `player_death`, `special_chest`, `dungeon_loot`, `dungeon_boss`, `raid_mythic`, `event_reward` | + +### T6–T8 may appear from + +1. **Player death** — corpse drops gear the player was **holding** (any tier, full prefab identity) +2. **Special chests** — intentional high-end containers +3. **Dungeon loot / dungeon bosses** +4. **Raid mythic** / event rewards + +### T6–T8 must **not** appear from + +- Trash / normal / elite open-world mobs +- Common / uncommon world chests +- Harvest nodes +- Default open-world boss/raid tables (use `dungeon_boss` / `raid_mythic` when mythic+ is intended) + +## Other hard rules + +| Rule | Value | +|------|--------| +| **Catalog max** | **T8** (prefabs always) | +| **Natural max** | **T5** | +| **T0 in drops** | **Yes** — materials, potions, foods, thrown | +| **Player level** | Caps natural max + soft target; special unlocks T6+ with level gates | +| **Difficulty** | Level bias, qty mul, rare mul, T0 weight mul | + +## Player level → natural max tier + +| Player level | Natural max | +|--------------|-------------| +| 1–9 | T1 | +| 10–19 | T2 | +| 20–29 | T3 | +| 30–39 | T4 | +| 40+ | **T5** | + +Special sources: T6 soft-unlock ~eff level 45, T7 ~55, T8 ~65 (still rare via `mythicChance`). + +## Quick API + +```js +import { + rollLoot, + rollPlayerDeathDrops, + maxDropTier, + assertDropTierLegal, + NATURAL_MAX_DROP_TIER, + MYTHIC_TIERS, + isMythicSource +} from '../loot/dropTables.js'; + +// Natural — never T6–8 +const natural = rollLoot({ + source: 'mob_normal', + playerLevel: 22, + difficulty: 'hard' +}); +// meta.maxTier <= 5; no tier 6–8 in drops + +// Corpse — spill held T6–8 gear as full prefabs +const corpse = rollPlayerDeathDrops(playerHeldItems, { playerLevel: 50 }); + +// Special chest / dungeon +const chest = rollLoot({ source: 'special_chest', playerLevel: 48, difficulty: 'elite' }); +const dung = rollLoot({ source: 'dungeon_loot', playerLevel: 50, difficulty: 'hard' }); +``` + +## Sources + +**Natural (≤T5):** +`mob_trash` · `mob_normal` · `mob_elite` · `chest_common` · `chest_uncommon` · `boss` · `raid` · `harvest_node` + +**Special (≤T8):** +`player_death` · `special_chest` · `dungeon_loot` · `dungeon_boss` · `raid_mythic` · `event_reward` + +## Resolve items after roll + +1. `category` + `tier` from roll (or full held item on `player_death`) +2. Pick item from ObjectStore: `materials.json`, `consumables.json`, `master-weapon-prefabs.json` +3. For natural rolls: never upgrade tier to 6+ +4. For special / corpse: resolve T6–8 through the **same** `presentPrefab` path as T0–5 + +## Related + +- Prefabs: `master-weapon-prefabs.json` (T0–T8) +- ObjectStore: `docs/DROP_TABLES_SSOT.md`, `docs/WORLD_DROP_PRESENTATION.md` +- Skills: `master-weaponSkills.json` +- Showcase binds skills — separate from loot diff --git a/docs/ELEMENTAL_LINEAR_CAST_SSOT.md b/docs/ELEMENTAL_LINEAR_CAST_SSOT.md new file mode 100644 index 0000000..ee09cdd --- /dev/null +++ b/docs/ELEMENTAL_LINEAR_CAST_SSOT.md @@ -0,0 +1,135 @@ +# Elemental skills × linear spell casting (learned SSOT) + +**Lab:** casting.grudge-studio.com / CastingAbilitiesThreeJS +**Code:** `src/combat/elementalLinearCast.js` · `DrcCombatController` · `LinearSkillBridge` +**Upstream learn:** LinearAbilityCastingThreeJS → `docs/LINEAR_SKILLSHOT_SSOT.md` + +This is the **merge complete** map: elemental staff skills and linear skillshot systems are one planned cast, not two silos. + +--- + +## What we took in (elemental skills) + +| Layer | SSOT | Role | +|-------|------|------| +| Product elements | `CASTING_ELEMENT_PHASE_VFX` | fire · storm · ice · nature · holy · arcane | +| Catalog binds | `staffWeaponSkillsBind` | pathMode + VFX from WEAPON_SKILLS | +| Staff normal | slot 1 + focus LMB | shared stream normal | +| Orbs / charge | `staffOrbVfx` | gd_orbs + kamehameha charge | +| Freeze / rocks / bubbles / arrows | `elementAttackVfx` | mesh delivery kinds | +| Path place | `staffCast` + AbilityManager | aoe · spikes · wall · stream strokes | +| Presentation | `elementPresentation` | volley · meteor · flood · lightning · … | + +--- + +## What we learned (linear spell casting) + +| Practice | Where | +|----------|--------| +| Settings SSOT sampled every frame | `skillshot/linearSettings.js` | +| Pooled abilities, no alloc on cast | `skillshot/abilities/*` | +| Phase machine travel → impact → fade | `Ability.js` | +| Constant m/s line front | base Ability | +| MOBA line / zone aim | `AimController` · Alt+Shift arm | +| **Combat castToward** (no re-arm) | DRC skill release via plan | +| Procedural geo + GLSL | materials + ProceduralGeometry | +| Ground marks + fissures | DecalType + FissureSystem | +| Intensity live mid-cast | `applyIntensity` ← focus mul | +| Variants size/speed/angle | `effectVariants` → linear block | + +--- + +## Element → linear map + +| Element | Linear id | Shape | +|---------|-----------|-------| +| ice | ice | LINE | +| storm | thunder | LINE | +| fire | meteor | LINE | +| holy | beam | LINE | +| arcane | snare | ZONE | +| nature | glacier | ZONE | + +--- + +## Cast layers (one plan per skill release) + +`planElementalLinearCast(skill, ctx)` → ordered layers: + +| Layer | System | +|-------|--------| +| `linear_line` / `linear_zone` | `LinearSkillBridge.castToward` | +| `path_ability` | Fire/Water/Earth/Wind Ability curve | +| `mesh_projectile` | orbs / summons | +| `freeze_nova` | expanding freeze AOE | +| `earth_rocks` | pull from below + linear/aimed | +| `water_bubbles` | procedural water | +| `arrow_path` / `arrow_loft` | dual arrow end-event systems | +| `buff` | focus / ward only | + +### When linear fires + +- Combat / focus digit skills with **stream** or **primary** +- Zone elements when pathMode **aoe** / nova text +- **Not** when path stroke is actively drawn (path Ability owns that) +- **Not** for pure freeze_nova / arrow-only plans (mesh first) + +### When path Ability still fires + +- wall / spikes / aoe presentation +- alongside linear for continuity (element volley/flood beauty) + +### When mesh fires + +- staff normal orbs +- nature rocks +- ice bubbles +- freeze / arrows as kind-specific + +--- + +## Input map (product) + +| Input | System | +|-------|--------| +| **1–4** / **F** / focus **LMB** (staff) | Plan → linear + mesh + path Ability | +| **LMB draw** (path cast) | Path Ability + mesh (no linear arm) | +| **Alt+Shift+Q/E/R/F/V/G** | Arm MOBA linear only (learn / sandbox) | +| **Esc** | Cancel linear arm | + +--- + +## Code flow + +``` +useSkill → cast bar → releaseSpell + → planElementalLinearCast(skill, { focusCombat, intensity }) + → fireLinearFromPlan(linearSkills, plan, pose) // if useLinear + → _deploySkillDelivery(skill, pose) // freeze/rocks/orbs/arrows + → abilities.cast(curve, element) // if usePathAbility + → deployPresentation(element, …) +``` + +--- + +## Agent rules + +1. Extend `elementalLinearCast` + existing bridges — **no third combat tree** +2. Catalog skill ids stay WEAPON_SKILLS / t0 only +3. SI metres; one mixer; shared particles/decals/post +4. Linear knobs stay in `linearSettings` (sample, never copy at spawn) +5. Smoke: equip staff · Q combat · RMB focus · LMB / 1 → toast shows `lin:ice` etc. + +--- + +## Checklist + +``` +[x] Elemental skills bound (staff + phase VFX + orbs/rocks/freeze/arrows) +[x] Linear practices ported (skillshot/*) +[x] PRODUCT_TO_LINEAR element map +[x] planElementalLinearCast merge +[x] DRC release → castToward +[x] App wires linearSkills into DrcCombatController +[ ] Live smoke on casting host after deploy +``` diff --git a/docs/ELEMENT_ATTACK_MESHES_SSOT.md b/docs/ELEMENT_ATTACK_MESHES_SSOT.md new file mode 100644 index 0000000..a89018e --- /dev/null +++ b/docs/ELEMENT_ATTACK_MESHES_SSOT.md @@ -0,0 +1,98 @@ +# Element attack meshes · freeze · rocks · arrows + +**Lab:** CastingAbilitiesThreeJS +**Code:** `src/vfx/elementAttackVfx.js` · `SkillProjectileSystem` · `skillDelivery` · `DrcCombatController` +**Split:** `node scripts/split-element-attack-meshes.mjs` + +Does **not** invent catalog skill rows. Delivery inferred from existing staff/catalog skills. + +--- + +## Assets + +| Source | Product | Use | +|--------|---------|-----| +| `bubbles_2.glb` | **Procedural** SI spheres (not whole 37MB morph pack) | Water travel · freeze ring animation | +| `assorted_rock_pack.glb` | `models/vfx/rocks/rock-0..7.glb` (~0.55 m) | Earth: emerge underfoot → linear / aimed | +| `kamehameha_charging.glb` | `models/vfx/charge/staff-charge.glb` | Earth emerge tell + staff charge | +| `teleport_arrow.glb` | `models/vfx/arrows/arrow-path.glb` | Linear attack path | +| `arrow_curved.glb` | `models/vfx/arrows/arrow-loft.glb` | Lofted throw / place / trap / summon | + +**Never** load multipack sources whole as one projectile. + +--- + +## Freeze AOE + +| | | +|--|--| +| Trigger | Ice nova / absolute zero / freeze / frost AoE catalog · `around_caster` ice | +| Spawn | Caster feet | +| Motion | Expanding bubble ring all directions | +| On catch | `freeze: true` · `frozenUntil` on hostile mesh · frost VFX | +| Defaults | radius ~5.5 m · expand 0.45 s · freeze 2.5 s | + +API: `projectiles.spawnFreezeNova({ origin, radiusM, targets })` + +--- + +## Earth rocks + +| | | +|--|--| +| Trigger | Nature/earth stream · spikes · quake · practice root · vine lash | +| Spawn | **Below terrain** beside caster (+ charge emerge tell) | +| Path | **linear** (primary/stream) or **aimed** (soft-lock / aim) | +| Count | 1 normal · 3 spikes · 4 multi/quake | +| Mesh | Random `rock-0..7` (individual) | + +API: `projectiles.spawnEarthRocks({ casterPos, target, aimMode, rockCount })` + +--- + +## Water bubbles + +| | | +|--|--| +| Trigger | Water/ice stream (non-nova) | +| Mesh | Procedural spheres + `MeshPhysicalMaterial` (transmission) | +| Source note | `bubbles_2.glb` morph multipack deferred — do not ship 37MB per cast | + +API: `projectiles.spawnBubbleStream({ origin, target, count })` + +--- + +## Two arrow systems + +| System | Mesh | Loft | Role | +|--------|------|------|------| +| **path** | `arrow-path` (teleport_arrow) | 0 | Linear attack path; **distance** sets end event location | +| **loft** | `arrow-loft` (arrow_curved) | ~0.42 | Throw · place device · trap · summon | + +### Path end events (distance → where) + +`impact` · `explode` · `aoe` · `blink` · `return` + +- Size can scale slightly with distance on path system. +- **blink** moves lab character to end point (teleport arrow learning). + +### Loft end events + +`throw` · `place_device` · `trap` · `summon` + +API: `projectiles.spawnArrow({ system: 'path'|'loft', endEvent, distanceM, origin, target })` + +Infer from catalog text (`blink`, `trap`, `summon arrow`, …) via `inferElementAttackKind`. + +--- + +## Checklist + +``` +[ ] No whole assorted_rock_pack / bubbles_2 / fireball as projectile +[ ] rock-0..7 + arrow-path/loft under public/models/vfx +[ ] Freeze marks hostiles frozenUntil +[ ] Earth rocks emerge from y < 0 then fly +[ ] Arrow path distance drives end event +[ ] SI scales (rocks ~0.55 m, arrows ~1 m) +``` diff --git a/docs/ELEMENT_PRESENTATION_SSOT.md b/docs/ELEMENT_PRESENTATION_SSOT.md new file mode 100644 index 0000000..2b834aa --- /dev/null +++ b/docs/ELEMENT_PRESENTATION_SSOT.md @@ -0,0 +1,91 @@ +# Element presentation SSOT (Casting lab) + +**Host:** casting.grudge-studio.com +**Code:** `src/combat/elementPresentation.js` · `VfxDirector.deployPresentation` · `settings.presentation` +**Shake:** `settings.global.cameraShake` (default **0.32**) · soft flash · low explosionShake + +Product elements reuse **existing** Ability pools + catalog VFX. Creative looks are **recipes**, not new engines. + +## Product → ability → presentation + +| Element | Ability pool | Style | What you see | +|---------|--------------|-------|----------------| +| **Fire** | FireAbility | `volley` + `meteor` | Micro fire bolts (first = bullet size); high intensity / Meteor skill = sky shards + small ground infernos | +| **Storm** | WindAbility | `lightning` + shield on wall | **Narrow fast** electric bolts + **chain hops** + wind silk residual; wall = defensive shield | +| **Ice** | WaterAbility | `groundFlood` | Frost plate crawls like earth pave, then erupts / frost_wave swallow | +| **Nature** | EarthAbility | `vineLash` | Green earth palette; vines rise underground and lash; heal aura ring | +| **Holy** | WindAbility | `radiance` | Moon beam + soft heal aura | +| **Arcane** | WindAbility | `voidBolt` | Purple + void black; micro first bullet then void impact | + +## Hybrid spikes (path cast `spikes`) + +EarthAbility (or water) motion + beauty overlay: + +| Element | Overlay beauty | Read | +|---------|----------------|------| +| fire | inferno | magma spikes | +| ice | frost_wave | ice spears | +| storm | ice_lightning_burst | air-cut spikes | +| nature | earth_surge (green) | vine spears | +| holy | moon_beam | light pillars | +| arcane | arcane_swirl | void spines | + +## Knobs (`settings.presentation`) + +| Field | Default | Role | +|-------|---------|------| +| fireVolleyCount | 5 | micro bolts per fire/arcane cast | +| fireVolleyDelayMs | 65 | stagger | +| fireVolleySize | 0.32 | body shot scale | +| microBulletSize | 0.14 | first projectile | +| meteorHeight | 14 | sky spawn (m) | +| meteorShards | 4 | small falling hits | +| iceFloodRadius | 4.2 | ground crawl radius | +| iceEruptDelayMs | 280 | flood → erupt | +| natureVineCount | 3 | lash count | +| natureHealAura | true | green ring | +| stormShieldRadius | 2.4 | defensive ring | +| lightning.* | see below | narrow electric bolt + chain | +| arcaneCore / arcaneGlow | black / purple | void palette | + +### Lightning knobs (`settings.presentation.lightning`) + +| Field | Default | Role | +|-------|---------|------| +| boltSpeed | 42 | directed particle stream speed | +| boltLife | 0.14 | short = snappy electric | +| coreSize / glowSize | 0.045 / 0.09 | **narrow** core + sheath | +| zigzag | 0.28 m | jag amplitude | +| segments | 7 | polyline density cast→aim | +| chainHops | 3 | cascade hops | +| hopDelayMs | 38 | fast chain timing | +| windResidual | true | cyan silk lag along bolt | + +**Sandbox:** Alt+L → `chain_lightning` preview. + +## Shake policy + +| Layer | Before | Now | +|-------|--------|-----| +| global.cameraShake | 1.0 | **0.32** | +| CameraShake pos scale | 0.55 | **0.28** | +| VFX deploy trauma | 0.08–0.20 | **~0.03–0.06** | +| Ability explosionShake | 0.4–1.0 | **~0.12–0.28** | +| flashStrength | 1.0 | **0.4** | + +Editor: **Global → cameraShake** still live-tunes. + +## Agent rules + +1. Extend `elementPresentation` / `settings.presentation` — do not invent Ability2. +2. Prefer **many small** projectiles/shards over one huge volume (GPU + clarity). +3. Nature = green + earth motion + vines + heal; do not rebrand as brown-only earth. +4. Storm shield is valid defense presentation, not only tornado offense. +5. Arcane = purple + near-black secondary always. +6. Smoke after change on casting.grudge-studio.com. + +## Future (same files) + +- Arrow-shaped residual mesh for fire/ice/wind bolts (`meshId` orb/slash) +- True knockback once combat hit targets exist +- Dedicated HolyAbility / ArcaneAbility materials when art ships diff --git a/docs/ELEMENT_TRANSIT_MASTERY_SSOT.md b/docs/ELEMENT_TRANSIT_MASTERY_SSOT.md new file mode 100644 index 0000000..3060135 --- /dev/null +++ b/docs/ELEMENT_TRANSIT_MASTERY_SSOT.md @@ -0,0 +1,115 @@ +# Element transit mastery SSOT + +**Lab:** casting.grudge-studio.com · **Catalog:** [WEAPON_SKILLS.html](https://info.grudge-studio.com/WEAPON_SKILLS.html) · JSON `master-weaponSkills.json` +**Staff binds:** `src/combat/staffWeaponSkillsBind.js` + +This is the vocabulary for **what you see after you draw a path** — not “smoke shader” generically. + +--- + +## 1. Lifecycle names (what is “in transit”?) + +After LMB path ends (or a digit skill fires), the cast goes through **Ability phases**: + +| Phase | Code | What you see | +|-------|------|----------------| +| **Travel** | `AbilityPhase.TRAVEL` | Head moves along the drawn spline; ribbon/volume **follows** | +| **Impact** | `AbilityPhase.IMPACT` | Head reaches end — burst / tower / splash / tornado | +| **Fade** | `AbilityPhase.FADE` | Effect dies out | +| Beauty layer | `VfxDirector.deploy*` | Catalog effects layered on cast/travel/impact (particles, decals) | + +Base class: `src/abilities/Ability.js` +Pool: `AbilityManager` maps product element → one of four **Ability classes**. + +**There is no separate “smoke shader” product name.** Smoke is **particles** on fire (`fire.smoke` system). Wind is **`WindMaterial`** + silk ribbons. + +--- + +## 2. The four transit styles (ability + material + particles) + +These are the **four projectile/path looks** you master by editing shaders + `settings.*` form knobs: + +| Product element | Ability class (travel) | Material / “shader” | Particles | Settings block | +|-----------------|------------------------|---------------------|-----------|----------------| +| **Fire** | `FireAbility` | **`VolumetricFireMaterial`** (raymarched volume) | embers, sparks, **smoke** | `settings.fire` | +| **Ice** (legacy water) | `WaterAbility` | **`OceanWaterMaterial`** | droplets, spray, foam, mist | `settings.water` | +| **Nature** (legacy earth) | `EarthAbility` | **`RockMaterial`** + ground plates | dust, debris | `settings.earth` | +| **Storm** (legacy wind) | `WindAbility` | **`WindMaterial`** / `WindRibbonMaterial` | leaves, dust, residual cyan | `settings.wind` | +| Holy / Arcane | reuse Wind (for now) | same + beauty colors | motes / moon_beam | `settings.holy` / `arcane` | + +**Trail under cursor (while drawing):** `TrailMaterial` + `PathTrail` — not the ability transit itself. + +**Distortion / heat shimmer:** `DistortionMaterial` (often fire impact). + +### What “transit” is physically + +1. You draw a **CatmullRom curve** (path). +2. Ability head walks the curve (`u` 0→1) at `settings..speed`. +3. A **rolling window** of path points builds the ribbon/volume mesh each frame (`onTravel`). +4. Beauty VFX may **also** fire: cast tell → travel tell → impact (catalog ids). + +So: **transit = Ability TRAVEL + material + particles**, optionally + **VfxDirector presentation** (volley, lightning, vines, trap…). + +--- + +## 3. Four path *placement* modes (how the curve is used) + +Independent of element look — `settings.staffCast` + `pathCastClassify`: + +| Path mode | When | Meaning | +|-----------|------|---------| +| **stream** | long stroke / hold | Ability rides full path (classic bolt/stream) | +| **aoe** | short stroke | Place impact at endpoint (compressed curve) | +| **spikes** | medium stroke | Ground spears / hybrid overlays along path | +| **wall** | long hold / long path | Barrier / cage / shield presentation | + +These four are the **placement styles**. The four Ability classes are the **transit looks**. + +--- + +## 4. Skill mastery checklist (edit order) + +| Skill | Where | Knobs | +|-------|-------|-------| +| Scale / form | `settings.fire|water|earth|wind` | width, length, speed, flightHeight, ribbonCount… | +| Shader look | materials above | colors, density, steps, opacity, glow | +| Beauty VFX | `vfxCatalog` + `elementPresentation` | cast/travel/impact ids, lightning, volley | +| Path place | `settings.staffCast` | aoeMaxLength, spikesMaxLength, wallHoldSec | +| Catalog bind | `staffWeaponSkillsBind.js` | every `staff_*` → pathMode + element + VFX | +| Anim | magic pack `cast` role | `weaponAnimPack` staff→magic | +| New asset from look | clone material palette + new presentation recipe | do **not** invent Ability2 | + +Editor: lil-gui folders per element + Global (`cameraShake`, glow…). + +--- + +## 5. All STAFF skills (catalog only — no invented skills) + +**SSOT:** [WEAPON_SKILLS.html](https://info.grudge-studio.com/WEAPON_SKILLS.html) · `master-weaponSkills.json` → type **STAFF** (~45 ids). + +Lab file **`staffWeaponSkillsBind.js`**: +- Maps **existing catalog ids only** → school (fire/frost/holy/lightning/nature/arcane) +- Fills empty prefab with existing Ability materials + VFX (does **not** invent skill rows) +- Cooldown / castTime / damage / mana from catalog when present +- Anim: magic pack **cast** for all staff + +| School | Ability transit | Texture / material style | +|--------|-----------------|---------------------------| +| fire | FireAbility | VolumetricFireMaterial | +| frost | WaterAbility | OceanWaterMaterial | +| holy | Wind + moon_beam | radiance presentation | +| lightning | Wind + chain_lightning | WindMaterial + electric | +| nature | EarthAbility | RockMaterial + green | +| arcane | Wind + void beauty | purple / black | + +Do **not** add lab-only skill ids. New staff skills land in ObjectStore catalog first. + +--- + +## 6. Agent rules + +1. **WEAPON_SKILLS catalog is skill SSOT** — do not invent `staff_*` ids or trap systems. +2. Only wire transit/VFX/anim onto **existing** catalog skills. +3. Name layers: **Ability TRAVEL** ≠ presentation particles ≠ path mode. +4. Staff school style = fire/frost/holy/lightning/nature/arcane textures already in lab. +5. Smoke = fire particles; wind = WindMaterial; ice = OceanWaterMaterial; nature = RockMaterial. diff --git a/docs/ENGINE_DEPLOY_SSOT.md b/docs/ENGINE_DEPLOY_SSOT.md new file mode 100644 index 0000000..eacd2e1 --- /dev/null +++ b/docs/ENGINE_DEPLOY_SSOT.md @@ -0,0 +1,103 @@ +# Casting engine + deploy SSOT (lab → client production) + +**Host:** https://casting.grudge.studio (control plane) · Vercel `casting-abilities-threejs` +**Stack:** **vanilla Three.js ^0.185** + **@dimforge/rapier3d-compat ^0.19** + Vite 8 — **not R3F** + +R3F lives on Forge / other fleet apps. Do **not** add `@react-three/fiber` here — one scene graph, one mixer, one Rapier world. + +--- + +## Two systems people confuse (not duplicates) + +| Name | File | What it is | +|------|------|------------| +| **`LAYER` (render)** | `src/core/Layers.js` | Camera bit mask: WORLD=0 · VFX=1 · DISTORTION=2 · CONTACT=3 | +| **`TERRAIN_LAYER` L0–L3** | `src/world/terrainLayers.js` | Authoring labels only (height / surface / vegetation / detail) | +| **Overhead bars** | `src/ui/OverheadNameplates.js` | **HTML/DOM** nameplates for dummies — not a Three layer | +| **Camera** | `src/core/CameraRig.js` | **One** PerspectiveCamera · orbit sandbox / TPS combat · Orbit never writes mid-TPS combat | + +Dummy bars floating on a black void was **not** a second camera — land vertex colors matched void `#14181d` + low ambient. Fixed: meadow colors + fill light. + +--- + +## Engine ownership + +| Concern | Module | +|---------|--------| +| WebGL + tone map knobs | `src/core/Renderer.js` | +| Scene / sun / fog / HDR IBL | `src/world/Environment.js` | +| Post (depth · distortion · bloom · grade) | `src/postprocessing/PostProcessing.js` | +| Rapier heightfield + CCT | `src/physics/PhysicsWorld.js` | +| Island mesh + sample | `src/world/IslandHeightfield.js` | +| Character / equip / hold pose | `src/animation/CharacterController.js` | +| Combat / skills / residual | `src/combat/DrcCombatController.js` | +| SFX | `src/audio/skillSfx.js` | +| HUD CraftPix chrome | `src/ui/craftpixUi.js` · **same-origin** `/ui/craftpix` | +| Bars pack | `src/ui/barsHudUi.js` · same-origin `/hud/bars` | + +--- + +## Dev environment + +```bash +cd CastingAbilitiesThreeJS +npm install +npm run dev # Vite → http://localhost:5173 +# optional: .env.local with VITE_FLEET_API=https://grudge-api-production-0d46.up.railway.app +``` + +Smoke local: + +```bash +npm run build && npm run preview +``` + +--- + +## Production deploy (client handoff quality gate) + +```bash +npm run build +npx vercel --prod --yes +# aliases: casting.grudge.studio · casting.grudge-studio.com (vercel.json) +``` + +| Gate | Check | +|------|-------| +| Build | `npm run build` exits 0 (no missing exports) | +| Boot | Loader → “Ready — Dev Island”, `window.app` set | +| Scene | Green/meadow island visible, hero skinned, Draw > 0 | +| HUD | CraftPix + bars load from **same origin** (no CDN CORS errors) | +| Audio | Spell cast = `cast_ramp` · impact = `impact_magic` · not death chant | +| Physics | Rapier heightfield or kinematic fallback logged | +| API | `/api/health` 200 via Vercel rewrite | + +**Ship to client production** only after casting lab smoke passes. Export patterns via: + +- `docs/CASTING_SDK_EXPORT_SSOT.md` · `/api/v1/casting-lab-contract.json` +- Effect prefab JSON · weapon skill production package +- **Not** a second Three/Rapier stack in the client repo — wire the same packages (`three@0.185`, `rapier3d-compat`) + +--- + +## npm pin (do not invent) + +| Package | Role | +|---------|------| +| `three` ^0.185.1 | Scene · GLTF · AnimationMixer · post addons | +| `@dimforge/rapier3d-compat` ^0.19 | Heightfield + CCT | +| `xstate` | Player activity machine | +| `lil-gui` | Lab editor only | +| `vite` ^8 | Build / dev | + +No dual three, no second physics, no R3F dual renderer. + +--- + +## Related + +- `docs/CASTING_DEPLOY_ENV_SSOT.md` — hosts, env, API rewrites +- `docs/CASTING_LAB_SSOT.md` — product role +- `docs/WEAPON_MESH_COLLIDER_SSOT.md` — weapon volume / parry +- `docs/THREE_LAYER_TERRAIN_SSOT.md` — L0–L3 authoring +- `docs/COMBAT_CAMERA_FOCUS_SSOT.md` — single camera TPS/focus diff --git a/docs/FALL_LOCOMOTION_SSOT.md b/docs/FALL_LOCOMOTION_SSOT.md new file mode 100644 index 0000000..8f6a478 --- /dev/null +++ b/docs/FALL_LOCOMOTION_SSOT.md @@ -0,0 +1,45 @@ +# Fall / jump locomotion SSOT + +**Author FBX:** `D:\Games\Models\` +**Lab copies:** `public/anim/locomotion/fall/*.fbx` +**Code:** `config/fallAnimSsot.js` · `fbxClip.js` · `CharacterController` · `DrcCombatController` + +## Clips + +| Role | Disk file | Public | Loop | Use | +|------|-----------|--------|------|-----| +| `fallLoop` | Fall A Loop.fbx | `fall-loop.fbx` | yes | Descending air cycle | +| `fall` | Falling.fbx | `falling.fbx` | yes | Long / deep fall body | +| `fallLand` | Falling To Landing.fbx | `fall-to-landing.fbx` | no | Soft or default land | +| `fallRoll` | Falling To Roll.fbx | `fall-to-roll.fbx` | no | Hard impact + forward → roll | + +Optional: `Falling Idle.fbx` → `fall-idle.fbx` / role `fallIdle`. + +## State machine (deterministic) + +``` +ground ── Space ──► jump (holdAir) +jump / rise ── vy ≤ −0.55 & airTime ≥ 0.12 ──► fallLoop +fallLoop ── land ──► + |impactVy| ≥ 6.5 && horiz ≥ 1.8 (or W/Shift) ──► fallRoll + else ──► fallLand +fallLand / fallRoll ── clip end ──► idle/walk gait +``` + +No random picks. Thresholds in `FALL_THRESHOLDS`. + +## Blending + +| Transition | Fade | +|------------|------| +| jump → fallLoop | `fallInBlend` 0.16 s | +| fall → land/roll | exclusive one-shot | +| land/roll → gait | `landOutBlend` 0.14 s | + +## Bind + +Roles live on **`combat_mobility`** pack (always bound). FBX rematch to Bip001 via `loadFbxClipRematched` when baked JSON missing. + +## Promote later + +Bake rotation-only JSON → `open.grudge-studio.com/anims/baked/locomotion/fall-*.json` and list in `FALL_BAKED_CANDIDATES`. diff --git a/docs/FISHING_PROFESSION_SSOT.md b/docs/FISHING_PROFESSION_SSOT.md new file mode 100644 index 0000000..e097d27 --- /dev/null +++ b/docs/FISHING_PROFESSION_SSOT.md @@ -0,0 +1,185 @@ +# Fishing profession SSOT (3D · Casting lab) + +**Harvest profession** — main-hand fishing pole, not a second combat engine. + +Aligned with ObjectStore `gathering.Fishing` + chef `foodSystem` (SWG-style **one meal per Red / Green / Blue**). + +--- + +## Source review (2D / packs) + +| Source | Status | What we took | +|--------|--------|----------------| +| `grudgewarlordsfishing/.../Dgrudge-fishing-game` | Vite **scaffold only** | Project name / intent | +| `FishingBar.png` · Target · Pointer · Template | 2D UI language | Palworld/Angler **fight bar** chrome | +| Cute Fish Pack rods L1–5 · lures · species | Stats from rarity tiers | Pole tiers · lure bias · difficulty | +| Grudge Angler language | Design | Rod **power / control / line / abilities** · sea_legs | +| `gameopen/.../humanPropsFishing.ts` | Tool defs | Pole idle/cast · bucket · craft hooks | +| `Animated Fish Bundle-glb.zip` | **Production meshes** | `public/models/fish/species\|poles\|lures\|docks` | +| ObjectStore professions.json | Fleet SSOT | gathering.Fishing tiers · chef RGB meals | + +--- + +## Controls + +| Input | Phase | Action | +|-------|--------|--------| +| **Shift+F** | any | Start fishing profession (lab pole if unequipped) | +| **RMB** | idle / aim | Toggle **aim** | +| **LMB** | aim | **Throw lure** | +| **S** or **RMB** | bite | **Snag** (SCUM window) | +| **Wheel down** | fight | **Reel in** | +| **Wheel up** | fight | **Give slack** | +| **LMB / RMB** hold | fight | Move reel zone | +| Inventory · Professions · **Fishing** | UI | Tree · rods · eat meals | + +--- + +## Rod types (Grudge Angler parity) + +| Id | Tier | Family | Nautical | Mesh | +|----|------|--------|----------|------| +| `t0-fishing-pole` | 0 | shore | 1.00 | `poles/fishing_rod.glb` | +| `fishing-pole-t1` | 1 | shore | 1.02 | `…-0yar0lg58p.glb` | +| `fishing-pole-t2` | 2 | river | 1.05 | `…-9aohhrphe7.glb` | +| `fishing-pole-t3` | 3 | sea | 1.08 | `…-aoabqwh68m.glb` | +| `fishing-pole-t4` | 4 | deep | 1.12 | `…-ldlwqjn9zg.glb` | +| `fishing-pole-t5` | 5 | void | 1.15 | same deep mesh until T6–8 art | + +**Stats:** `power` (zone) · `control` · `lineStrength` · `castRangeM` · `reelSpeed` · `biteWindowS` · `abilities[]` + +**Abilities:** `steady_hand` · `quick_snag` · `deep_cast` · `iron_line` · `sea_legs` (+nautical) · `void_line` + +Code: `src/fishing/fishingRodTypes.js` → catalog re-exports as `FISHING_POLES`. + +--- + +## Harvest skill tree + +Nodes in `src/fishing/fishingSkillTree.js` (Main Panel **Fishing** tab is live, not stub): + +- Catch qty · bite rate · zone · line · rare bias +- **Nautical:** `nautical_1` (+5%) · `nautical_2` (+8%) · `nautical_3` (+10%) — **multiplicative** +- `fish_meal_link` → Fisher’s Kitchen (blue meal crafts unlocked flag) +- `fish_master` Grandmaster Angler + +Progress: `localStorage` key `grudge.casting.fishing.profession.v1` via `professionState.js`. + +--- + +## Meals (SWG 3-buff · chef foodSystem) + +| Slot | Source | Example buffs | +|------|--------|----------------| +| **Red** | Land meat / seared fish | HP regen · attack · defense | +| **Green** | Plants / seaweed | stamina · move · armor | +| **Blue** | Ocean / fish soup | mana · spell · **nauticalSpeedMul** | + +One active meal **per color**; eating same color replaces. Combined with tree + rod for freeride speed. + +Code: `src/fishing/mealBuffs.js`. + +--- + +## Nautical speed + +Applied to **windsurf freeride** (`WalkController` × `getNauticalSpeedMul`): + +``` +tree.nauticalSpeedMul × meals.nauticalSpeedMul × rod.nauticalSpeedMul (incl. sea_legs) +``` + +App wires `walk.ctx.getNauticalSpeedMul → fishing.getNauticalSpeedMul()`. + +--- + +## Code map + +| File | Role | +|------|------| +| `src/fishing/fishingRodTypes.js` | T0–T5 rods + abilities | +| `src/fishing/fishingSkillTree.js` | Harvest tree + nautical nodes | +| `src/fishing/mealBuffs.js` | RGB SWG meals | +| `src/fishing/professionState.js` | XP · unlock · resolveProfessionMods | +| `src/fishing/fishingCatalog.js` | Species · poles · lures · roll/zone | +| `src/fishing/fishingFight.js` | Bar sim + reelSpeedMul | +| `src/fishing/FishingController.js` | Runtime | +| `src/ui/fishingUi.js` + `fishing.css` | HUD + meal dots | +| `src/ui/InventoryPanel.js` | Professions · Fishing live UI | +| `public/api/v1/fishing-profession.json` | Contract | +| `public/models/fish/**` | GLB prefabs | +| `public/ui/fishing/**` | Bar textures | + +--- + +## SI fish scale (no mess) + +| Pack | Path | Contents | +|------|------|----------| +| Reef v1 | `public/models/fish/species/*.glb` | clownfish … angler | +| **Game v2** | `public/models/fish/species/large/` | dolphin, fish_*, manta, shark_game, whale | +| Poles / lures / docks | `poles/` · `lures/` · `docks/` | tools only | + +Runtime fit: `src/fishing/fishScale.js` → **lengthM** on longest axis, elongated (longer than tall/wide). + +| sizeClass | lengthM band | Example | +|-----------|--------------|---------| +| tiny | 0.06–0.14 | tetra, goldfish | +| small | 0.15–0.35 | clownfish, piranha | +| medium | 0.4–0.9 | koi, fish_mid | +| large | 1.0–2.2 | tuna, swordfish | +| huge | 2.3–4.5 | dolphin, game shark | +| titan | 5–12 | whale (~8.5 m) | + +**Gates (need both):** rod `maxSizeRank` **and** tree maxSizeRank · lure sizeClass match · lure tier vs rod `lureSlotTier`. + +## Lures (gameplay value) + +| Id | Tier | Sizes | Notes | +|----|------|-------|-------| +| worm | 0 | tiny/small | shore | +| lure_basic | 0 | tiny–medium | general | +| lure_spinner | 1 | small/medium | flash | +| lure_deep | 2 | medium/large | deep | +| lure_heavy | 3 | large/huge | big game | +| lure_game | 4 | large/huge | dolphin/manta | +| lure_titan | 5 | huge/titan | whale | + +## Prefabs / buildables + +| Id | Kind | +|----|------| +| T0–T5 poles | Main-hand TOOL · maxSizeRank · maxFishLengthM · lureSlotTier | +| Lures `worm` … `lure_titan` | sizeClass · bite · value | +| Docks · boat · bucket | Buildable / container | + +--- + +## Super-rare / deep (species/rare/) + +| Id | Behavior | SI | Unlock | +|----|----------|-----|--------| +| `pulbo_monstruo` | Deep rare | ~2.0 m large | ink/tentacle recipes | +| `aetherwing_turtle` | Super rare · slow | ~3.4 m huge | **Worge → form recipe** · else **mount recipe** | +| `ocean_creature` | Passive titan · leviathan prey | ~11 m titan | hide recipes · mesh ~219MB local | +| `glow_whale` | Passive titan · hard catch · leviathan prey | ~10 m titan | glow essence recipes | + +Leviathan strikes during fight: tension spike · progress snatch · rare steal. + +## Docks (boat housing) + +| Id | Claim | Boats | +|----|-------|-------| +| `dock_t1` | **no** — building place anywhere | 1 | +| `dock_t2` | **own claim flag** required | 2 | +| `dock_t3` | **own claim flag** required | 4 | + +Code: `src/fishing/dockBuild.js` · node palette family `fishing_dock`. + +## Playtest + +1. Open Inventory → **Professions** → **Fishing** +2. Unlock Sea Legs / size nodes · eat **blue** meal · pick rod + lure +3. **Shift+F** · cast · snag · fight +4. Plant claim (lab) → place Dock T2/T3 +5. Windsurf freeride for nautical multiplier diff --git a/docs/FLEET_ENTRY_DEPLOY_LOADERS_RAYCAST_REVIEW.md b/docs/FLEET_ENTRY_DEPLOY_LOADERS_RAYCAST_REVIEW.md new file mode 100644 index 0000000..72f15b7 --- /dev/null +++ b/docs/FLEET_ENTRY_DEPLOY_LOADERS_RAYCAST_REVIEW.md @@ -0,0 +1,264 @@ +# Review: player entry · deploy · loaders · anim · textures · raycast + +**Date:** 2026-08 · **Scope:** fleet + Casting lab +**Status:** review of live SSOT / code — gaps called honestly + +--- + +## 1. Player entry paths (start points) + +**SSOT:** `gameopen/docs/ENTRY_CATCH_SSOT.md` · `artifacts/animator/src/lib/entryCatch.ts` +**Rule:** use `PRODUCT_STARTS` / `startUrlForIntent` — never invent hosts. + +| Intent | Start URL | Notes | +|--------|-----------|--------| +| Open library | https://open.grudge-studio.com/ | Steam-like hub | +| Danger Room | https://open.grudge-studio.com/danger | Combat lab on Open | +| Account / roster | https://open.grudge-studio.com/account | After Foundry handoff | +| Campfire roster | https://open.grudge-studio.com/characters | 4-seat hub (not AccountPanel) | +| Sign-in | https://open.grudge-studio.com/login | `returnTo` via `safeReturnUrl` only | +| Create hero | https://character.grudge-studio.com/foundry | Create-only | +| Foundry 4-slot | https://character.grudge-studio.com/ | Hub | +| Warlords home | https://client.grudge-studio.com/home-island?characterId= | Needs id | +| Warlords map | https://client.grudge-studio.com/island-3d?mode=lobby | Sector sail | +| GRUDOX arcade | https://grudox.grudge-studio.com/arcade | Cabinets never Open Danger | +| Cabinet play | https://grudox.grudge-studio.com/arcade/play/<id> | racer, zombie, z-brawl… | +| Mine-Loader | https://mineloader.grudge-studio.com/ | Realms islands | +| Casting lab | https://casting-abilities-threejs.vercel.app / casting.grudge-studio.com | Warlords UX + skills proof | +| Player+grass | https://threejs-player-and-grass.vercel.app/play | L0–L3 grass lobby | + +### Catch rules (must keep) + +| Wrong landing | Fix | +|---------------|-----| +| Open `/arcade/play/racer` | → GRUDOX arcade | +| Open `?mode=create` / foundry | → character.grudge-studio.com/foundry | +| Open `/home-island` | → Warlords client | +| Login return → character.* / id.* | → Open account / hub only | +| Foundry save → Open Danger by default | → home-island or account | + +### Entry quality score + +| Area | Grade | Note | +|------|-------|------| +| PRODUCT_STARTS table | **A** | Clear intents | +| entryCatch tests | **A** | vitest suite | +| Agent habit | **B** | Still risk of guessed hosts in ad-hoc links | +| Casting / grass satellites | **B** | Not all in PRODUCT_STARTS (lab hosts OK) | + +**Best practice:** any new deep-link = one line in `PRODUCT_STARTS` + catch rule + test. + +--- + +## 2. Deployments (production map) + +**SSOT:** skill `grudge-live-servers` · gameopen deploy scripts + +| Surface | Host | Deploy | Player SSOT | +|---------|------|--------|-------------| +| **Open** | open.grudge-studio.com | Vercel `gameopen` → `artifacts/animator/dist/public` | Railway Postgres | +| **GRUDOX** | grudox.grudge-studio.com | CF Worker + Vercel SPA + Railway room | Railway | +| **Warlords client** | client.grudge-studio.com / grudgewarlords.com | GrudgeBuilder | Railway | +| **Foundry** | character.grudge-studio.com | Foundry Vercel | Railway slots | +| **Casting lab** | casting*.vercel.app | Vercel CastingAbilitiesThreeJS | Lab-only (not player bag) | +| **Assets** | assets.grudge-studio.com | R2 | binaries only | +| **Defs** | info / objectstore | CF / Pages | catalogs JSON | +| **Auth** | id.grudge-studio.com | ID worker | session JWT | + +### Five-layer asset SSOT (every deploy) + +| Layer | Authority | +|-------|-----------| +| Player state | Railway Postgres | +| Definitions | ObjectStore / info `/api/v1` | +| Binaries | R2 `assets.grudge-studio.com` | +| Index | D1 `api.grudge-studio.com/assets` | +| Worlds | Mine-Loader Railway | + +### Deploy checklist (Open) + +``` +1. node scripts/verify-fleet-assets.mjs --cdn-only +2. npm run deploy:prod # or vercel-build + vercel --prod +3. verify-fleet-assets --base https://open.grudge-studio.com +4. HEAD / + /api/health +``` + +### Deploy grades + +| Practice | Grade | Note | +|----------|-------|------| +| CDN not git for GLB | **A** | Vercel ban large meshes | +| Same-origin `/api/*` → Railway | **A** | Open proxy | +| WS on Vercel alone | **F** | Use L2 co-locate or L3 CF Worker | +| Magic-byte verify | **B** | Scripts exist; run before wire | +| Casting prod smoke | **B** | Build green; live URL smoke optional | + +--- + +## 3. Loaders (best practices) + +**Casting:** `src/loaders/AssetLoader.js` · `DRACO_DECODER_PATH` gstatic 1.5.7 + +| Practice | Status | Rule | +|----------|--------|------| +| GLTFLoader + **Draco** | ✅ | Production kits compressed | +| **Meshopt** decoder | ✅ | EXT/KHR meshopt | +| Shared LoadingManager | ✅ | Boot progress | +| Block absolute local paths in FBX | ✅ | Placeholder texture (no C:\ leaks) | +| Parallel load during cinema | Open pattern | `warmupProductionSurface` — never serial block | +| Prefer CDN URL then local | Fleet | Open rewrites `/models` → R2 | +| Magic-byte before trust | Ops | Reject HTML-as-GLB | +| Isolate multipack mesh names | Nature / orbs | Never load whole fireball / island pack | +| KTX2 / Basis | ✅ Casting `bindKtx2` + Open gltf.ts (pins aligned) | See `LOADER_DRACO_KTX2_AUDIT.md` — not a Draco conflict | + +### Loader pipeline (target) + +``` +URL resolve (CDN / same-origin) + → HEAD / magic-byte + → GLTFLoader (Draco + Meshopt) + → materials: SRGBColorSpace maps, flipY=false for glTF + → SI fit (Box3 / target 1.8 m hero) + → one AnimationMixer bind +``` + +--- + +## 4. Animations (best practices) + +| Practice | Status | Rule | +|----------|--------|------| +| **One AnimationMixer** per body | ✅ Casting CharacterController | No second mixer for VFX | +| Bip001 packs bones-only rematch | ✅ | `stripPositions: true` | +| Role names in animLibrary | ✅ | No invented aliases | +| Prod magic idle **with hands** | ✅ assets.js note | Open idle handless = bad | +| Weapon pack swap (magic / sword / bow) | ✅ | animPackForLoadout | +| Cast / attack one-shot roles | ✅ DRC | hitFrameDelay production skill | +| RideIK post-mixer | ✅ WalkController order | update → mixer → IK | +| Multi-`pose()` unify | ❌ Ban | grudge6 correctness | + +### Skill anim production loop + +``` +catalog skill id + → compileProductionWeaponSkill (anim.role / clip / hitFrameDelay) + → playWeaponCombat / requestOneShot + → release on cast bar end +``` + +--- + +## 5. Textures · sizing (best practices) + +| Practice | Status | Rule | +|----------|--------|------| +| **SRGBColorSpace** on color maps | ✅ CharacterController atlas | three r152+ | +| **flipY = false** for glTF | ✅ | Author UV convention | +| Atlas WebP on CDN | ✅ grudge6SSOT | Not raw TGA in browser | +| SI texture density | Soft | Ground/vertex color ok; no 8k full-screen | +| Hero height **1.8 m** | ✅ | WORLD / grudge6 | +| Weapon hand scale | WeaponMeshAttach | Soft width cap | +| Grass blades ~0.2–0.55 m | ✅ StylizedGrassLayer | Under human yardstick | +| Hills amp ≤ ~0.85 m | ✅ terrain.amp | Not 100× giants | +| Compress atlases / Draco kits | Ops | R2 pipeline convert skill | + +### Sizing yardstick + +| Asset | Target | +|-------|--------| +| Human | 1.8 m | +| Orc | ~2.0 m | +| Staff / wand | ~0.95–1.25 m hand | +| Orb projectile | ~0.45 m | +| Charge shell | ~0.35 m | +| Grass blade max | ~0.55 m | +| Island hill amp | ~0.85 m | + +--- + +## 6. Player raycasting (best systems) + +**Casting SSOT:** `terrainGround.projectToTerrain` · `MouseAim` · `PathDrawer` · `CombatFocus` + +### Order (correct) + +``` +1. setFromCamera(ndc, camera) +2. Prefer aim colliders (walls/props) in focus +3. projectToTerrain: + a. raycast terrain.mesh (IslandHeightfield) + b. else plane y=0 + sample(x,z) lift +4. Soft-lock blend within cone +5. Launch origin from hand/chest (computeLaunch) +``` + +### Why this is “best of fleet” + +| Pattern | Source | Grade | +|---------|--------|-------| +| One terrain handle for aim + path | terrainGround | **A** | +| Mesh hit then height sample | IslandHeightfield | **A** | +| Soft-lock cone | grudge-combat-targeting | **A** | +| Focus LMB → skill primary | App + DRC | **A** staff | +| Path stroke same ground | PathDrawer | **A** | +| three-mesh-bvh for complex mesh | Not default on island pad | **B** — add for dense colliders | +| BVH accelerate terrain | Gap | Optional when segments high | + +### Do not + +- Ray only against y=0 when heightfield is on +- Second heightmap for feet vs aim +- OrbitControls writing camera during combat TPS +- Soft-lock without max angle + +### Player feet vs raycast + +| System | API | +|--------|-----| +| Feet Y | `terrain.sample` / physics heightfield | +| Aim XZ | camera ray → terrain | +| Soft target | CombatFocus selected point | +| Harvest / pick | ray or proximity + same sample Y | + +--- + +## 7. Gap list (prioritized) + +| # | Gap | Priority | +|---|-----|----------| +| 1 | ~~KTX2/Basis not in AssetLoader~~ → fixed (`bindRenderer` + gltfPipeline) | Done | +| 2 | Infinite terrain stream (simonstorlschulke) not on island | Low (open world later) | +| 3 | three-mesh-bvh on aim colliders for dense props | Medium | +| 4 | PRODUCT_STARTS missing casting / grass lab URLs | Low | +| 5 | Snakey reactive trample map not ported | Low | +| 6 | Hostiles HP from production skill damage | Medium (status layer ready) | +| 7 | Always run entryCatch + CDN verify in CI | High ops | + +--- + +## 8. Agent checklist (before “ship player path”) + +``` +[ ] Deep-link uses startUrlForIntent / PRODUCT_STARTS +[ ] Login returnTo passes safeReturnUrl +[ ] Deploy: CDN GLB not git; build green; smoke live host +[ ] Loader: Draco + Meshopt; SRGB; flipY false +[ ] Anim: one mixer; bones-only; strip position tracks +[ ] SI sizes vs 1.8 m human +[ ] Raycast: projectToTerrain + one height sample +[ ] Map open = same session (controller / skills kept) +``` + +--- + +## References + +| Topic | Doc / code | +|-------|------------| +| Entry | `docs/ENTRY_CATCH_SSOT.md` · `entryCatch.ts` | +| Live deploy | skill `grudge-live-servers` | +| Terrain / ray | `THREE_LAYER_TERRAIN_SSOT.md` · `terrainGround.js` · `MouseAim.js` | +| Assets | `AssetLoader.js` · `grudge6SSOT.js` · `OPEN_PACKAGE_SSOT.md` | +| Combat aim | skill `grudge-combat-targeting` | +| Skills | `WEAPON_SKILL_PRODUCTION_SSOT.md` | diff --git a/docs/GAMEPLAY_UX_GATES_SSOT.md b/docs/GAMEPLAY_UX_GATES_SSOT.md new file mode 100644 index 0000000..7b7cfc6 --- /dev/null +++ b/docs/GAMEPLAY_UX_GATES_SSOT.md @@ -0,0 +1,90 @@ +# Gameplay + UX gates SSOT (Casting lab) + +**Most important checks** for playable lab → client handoff. +**Code:** `src/core/gameplayGates.js` · runs after boot · `window.__gameplayGates` + +--- + +## Gate matrix + +| Gate | Pass means | Owner | +|------|------------|--------| +| **feet_grounded** | Character feet Y ≈ heightfield sample (Δ < 0.35 m) | PhysicsWorld CCT + IslandHeightfield | +| **feet_mesh_ground** | Kit diagnose feetMinY ≈ 0 (no pelvis-as-feet float) | CharacterController / toonKitPlay | +| **anim_mixer** | Single AnimationMixer | CharacterController | +| **anim_state** | Named state (idle/walk/run/attack/…) + gait lock | CharacterController.animState | +| **anim_idle_clip** | Idle action present | anim packs | +| **camera_tps_combat** | Combat → viewMode `tps` | SessionState gates · CameraRig | +| **camera_orbit_off_in_tps** | OrbitControls **disabled** in TPS | CameraRig (fleet hard rule) | +| **camera_fortnite_distance** | Distance 2.5–10 m (not map-scale 14 m) | settings.camera · **not** WORLD.cameraDistance | +| **camera_single** | One PerspectiveCamera | CameraRig | +| **weapon_skills_bar** | ≥1 skill on DRC bar | drcSkills / equip | +| **weapon_pack** | animPackId / hold kind reported | equippedWeaponRuntime | +| **loco_input / loco_drc / loco_physics** | Input + DRC + Rapier CCT | stack | +| **ux_hud / ux_focus_crosshair** | HUD + focus reticle API | HUD · CombatFocus | + +--- + +## Camera (Fortnite shoulder / builder) + +| Mode | Camera | Notes | +|------|--------|--------| +| **Combat / freeride** | TPS shoulder | distance free **6 m** · focus **5.5 m** · shoulder **0.72 / 0.8** · FOV **70 / 78 sprint** | +| **Focus ON** | Same TPS, tighter | Mouse = look · body lag · crosshair · Orbit **off** | +| **Equip / builder** | Orbit allowed | Review assets; focus still forces TPS | + +**Hard ban:** OrbitControls writing camera mid-combat TPS. +**Hard ban:** Soft-lock auto yaw/pitch (aim bias only). + +@see `docs/COMBAT_CAMERA_FOCUS_SSOT.md` + +--- + +## Feet + +1. Spawn/placeAt uses **heightfield sample**, never hardcoded y=0 after terrain load. +2. Physics `setPlayerFeet` + CCT snap-to-ground + landHeightAt. +3. Strip hip **position** tracks on grounded kits (bakeClip). +4. Ground aim ring Y = terrain sample + 0.05. + +--- + +## Anim state + +| State | Source | +|-------|--------| +| idle / walk / run / sprint | gait from DRC movement | +| attack / cast / charge | one-shots · gaitLocked | +| roll / dodge / slide / parry | mobility one-shots | +| jump / fall | fall locomotion pack | + +One mixer. Pack swap via weapon equip (sword_shield / magic / longbow / pistol). + +--- + +## Weapon skills · locomotion · traversal + +| Layer | System | +|-------|--------| +| Skills 1–4 / F | DrcCombatController + catalog | +| Residual F fallback | settings.residual · tip volume | +| WASD / sprint / slide / roll / dodge | DRC + motionMath MM | +| Jump / double | physics vy + anim | + +--- + +## Console + +```js +window.__gameplayGates +// { ok, summary, gates: [{ id, ok, detail }] } +``` + +--- + +## Related + +- `docs/COMBAT_CAMERA_FOCUS_SSOT.md` +- `docs/PRODUCTION_CONTROLLER_SSOT.md` +- `docs/MELEE_COMBO_SSOT.md` · weapon skill production +- Character: `grudge-character-correctness` · combat: `grudge-fleet-combat` diff --git a/docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md b/docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md new file mode 100644 index 0000000..4b976b7 --- /dev/null +++ b/docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md @@ -0,0 +1,171 @@ +# Game item prefab production SSOT + +**Purpose:** One map for prefabbing **weapons · armour · backs · relics · class items · mounts · off-hands · specials** into fleet games: items, character HUD, UI, controller, combat, and the casting **dev environment**. + +**Do not invent parallel catalogs.** Authorities live on ObjectStore / info.grudge-studio.com. + +| Resource | URL | +|----------|-----| +| Item browser | https://info.grudge-studio.com/GRUDGE_Item_Database.html | +| Weapon skills | https://info.grudge-studio.com/WEAPON_SKILLS.html | +| Items manifest (start) | https://info.grudge-studio.com/api/v1/canonical-items-manifest.json | +| game-library.json | **404** as of 2026-08 — do not depend; see ASSET_LIBRARY_AUDIT_SSOT | +| Equipment pattern | https://info.grudge-studio.com/api/v1/_meta/canonical-equipment-pattern.json | +| Lab import | `src/api/gameItemCatalog.js` · Inventory **Prefabs** tab | +| CDN | https://assets.grudge-studio.com | + +Upstream doc: ObjectStore `docs/CANONICAL-ITEMS-WARLORDS.md` + +--- + +## 1. Category → authority (runtime prefab) + +| Category | Catalog authority | UUID | Count (approx) | Prefab status | Lab equip / combat | +|----------|-------------------|------|----------------|---------------|--------------------| +| **Weapons** (1H/2H/bow/gun/staff/wand…) | `master-weapon-prefabs.json` | ITEM-* | ~822 | **live** skills+icon+model family | Equip hand · hotbar · residual | +| **Tools** (harvest) | same · `weaponType=TOOL` | ITEM-* | ~49 | **live** | Equip · gather action | +| **T0 starters** | `t0-weapons.json` (+ prefab merge) | ITEM-* | 15 | **live** skills | Apprentice Wand · Sapling Staff… | +| **Off-hand** shield / tome | `master-weapon-prefabs` SHIELD · TOME | ITEM-* | shields ~48 · tome T0 | **live** | Off-hand mesh · loadout inject | +| **Armour** body/head/arms/legs | `master-armor.json` | ITEM-* | ~1344 | **catalog live** · prefabs **planned** | mesh_ids visibility · stats | +| **Back** (cape / pack / windsurf utility) | armor slot **back** or utility tag | ITEM-* | via armor sets | catalog / utility | back slot · ride deploy | +| **Relics** | `master-relics.json` | RELC-* | ~136 | **live** catalog | HUD relic · passive | +| **Class items / class relics** | `master-classRelics.json` · `classes.json` | RELC-* / class | per class | **live** catalog | class gate · Mage Wand later | +| **Mounts** | `master-mounts.json` | MNT-* | ~8 | **live** catalog | mount controller · not second loco | +| **Specials** (artifacts, enchants, infusions) | `master-artifacts` · enchants · infusions | ARTF/ENCH/INFU | ~112 | **live** catalog | socket · HUD · combat mods | +| **Consumables** | `master-consumables.json` | FOOD/POTN | ~132 | **live** | bag · use | +| **Materials / recipes** | materials · recipes | MATL/RECP | large | **live** craft | craft UI only | + +**Aggregate mirror:** `master-items.json` (~2361) · **UUID index:** `master-registry.json` +**Per-item prefab index:** `master-item-prefabs.json` (byUuid) + +--- + +## 2. Prefab layers (every equippable) + +Same industry layers as `_meta/canonical-equipment-pattern.json`: + +| Layer | Role | Example | +|-------|------|---------| +| **Definition** | Static catalog row | **ITEM-*** uuid, name, tier, slot, base stats | +| **Ability binding** | **SKIL-*** / slots 1–5 | `skills.slots` + `skillUuids` on weapon prefabs | +| **Assets** | CDN only | `iconUrl` (+ **ICON-***), `modelUrl`, dropPrefab key | +| **Instance** | Player ownership | Railway bag / equip (not ObjectStore) | +| **Presentation** | HUD / world drop | tier border, glow, sprite | +| **Economy** | recipes · drops | **RECP-***, drop tables | + +Weapon **runtime prefab** = definition + skills + assets (ready for Unity SO import + Three equip). +Armor is **definition+assets first**; full armor-prefab file planned (`master-armor-prefabs.json`). + +**Weapon UUID contract (deep dive):** `docs/WEAPON_PREFAB_UUID_SSOT.md` · `src/api/weaponPrefabContract.js` +What a weapon must **have** (6 layers) and **do** (bag · equip · controller · hotbar · combat · craft · export). + +--- + +## 3. Consumer surfaces (production) + +| Surface | What it consumes | Lab now | Production target | +|---------|------------------|---------|-------------------| +| **Items / bag** | presentPrefab / presentItem | World drops T0–T8 weapons | full bag from Railway + catalog | +| **Character HUD** | iconUrl · tier · HP/MP · hotbar skills | TightBar · action slots | craftpix + catalog icons | +| **UI panels** | equip slots · relic · mount | Inventory Weapon + Prefabs | Foundry / Open panel | +| **Controller** | meshSlot · animPack · back utility | WeaponMeshAttach · anim pack | fleet controller | +| **Combat** | skillDefToDrc · residual · cast | DRC from equip / STAFF bind | Warlords combat | +| **Dev environment** | Prefabs tab · export JSON | casting.grudge-studio.com | Forge + ObjectStore generate | + +--- + +## 4. Equip slots (character) + +| Slot | Typical items | Anim / mesh | +|------|---------------|-------------| +| mainHand | sword, axe, staff, wand, bow… | sword_shield / magic / longbow | +| offHand | shield, tome | sword_shield / magic | +| head / body / arms / legs | armor | mesh_ids show/hide | +| back | cape, pack, **windsurf** utility | `BackSlotEquip` spine attach · deploy → vehicle · E get-off | +| relic | RELC-* | HUD only / passive | +| classItem | class relic / Mage Wand | class gate | +| mount | MNT-* | mount controller | + +Loadout pattern (weapons): T0 **three-slot starter** · T1+ **five-slot** (`loadoutPattern` on weapon prefabs). + +--- + +## 5. Lab: Equipment + Prefabs (casting develop surface) + +**Import only** from info API + lab back mobility SSOT — no forked skill rows. + +### Paperdoll RMB (primary equip flow) + +1. **I** → Character / Equipment +2. **RMB empty or filled slot** → + - **Browse T0–T1 catalog…** (icons · filter · equip live) + - Unequip / Replace from bag + - **Edit mesh color / scale / rotate…** (weapons & armour appearance) +3. Live 3D: + - **mainHand/offHand** → `equipWeaponById` + mesh appearance + - **head/body/arms/legs** → kit `mesh_ids` + colour tint + - **back** → `equipBackSlot` (windsurf water-only · wings prefabs) + - **relic/mount** → paperdoll map (HUD) + +### Prefabs tab + +1. **I → Prefabs** +2. Filter category: Weapons · Armor · Back · Relics · Mounts · Class · Offhand · T0 +3. Select row → see icon, stats, skills (if any), model URL, export snapshot +4. **Equip** if weapon/tool/offhand/back → combat bar + hand/back mesh when model exists + +### Mesh appearance + +`src/equipment/meshAppearance.js` — per-item localStorage: color, scale, euler, offset, emissive. +Develop on casting → later export into prefab production JSON. +5. **Export** Warlords-style prefab JSON for that row + +Code: `gameItemCatalog.js` · `presentItem` · Inventory Prefabs panel. + +--- + +## 6. Asset pipeline (when shipping meshes) + +``` +Author GLB → grudge-asset-convert (SI scale, mesh parts) + → R2 assets.grudge-studio.com (prod/gltf | models/…) + → master-*-prefabs / catalog assets.iconUrl + modelUrl + → D1 weapon_prefabs index (optional mirror) + → Lab equip / Warlords Unity import / Open +``` + +- Weapons: family GLBs live; per-item dropPrefab keys often **declared, binary pending** +- Armor: atlas mesh_ids preferred over whole-body swap (grudge6) +- Mounts: separate mount controller, same session as map open rules + +--- + +## 7. Agent rules + +1. **Read `game-library.json` + canonical-items-manifest** before adding items. +2. **Never invent** ITEM-* / SKIL-* ids in the lab — only bind VFX/anim to existing ids. +3. Weapons with skills → `master-weapon-prefabs` + `master-weaponSkills`. +4. Armor → `master-armor` until armor-prefabs ship. +5. Relics / mounts / class → their master-* files. +6. Player bag SSOT remains **Railway**, not D1/R2. +7. Casting lab is **dev/proof**; Warlords / Open consume the same JSON. + +--- + +## 8. Pipelines (ObjectStore repo) + +```bash +npm run build:weapon-pipeline # weapons + tools prefabs +npm run build:items-pipeline # full catalog + manifest + audit +npm run generate:all +npm run validate:catalog +``` + +--- + +## Related lab docs + +- `WEAPON_EQUIP_PREFABS_SSOT.md` — weapon equip try path +- `T0_STARTERS_WEAPON_SKILLS_SSOT.md` — Apprentice Wand · Sapling Staff +- `DROP_RATES_SSOT.md` — world drop presentation +- `ELEMENT_TRANSIT_MASTERY_SSOT.md` — staff school VFX diff --git a/docs/ISLAND_STAGE_SSOT.md b/docs/ISLAND_STAGE_SSOT.md new file mode 100644 index 0000000..0dcfc98 --- /dev/null +++ b/docs/ISLAND_STAGE_SSOT.md @@ -0,0 +1,83 @@ +# Island stage SSOT — Training Room · DevIsland + +Lab is **not** a full Open sector shell. It is the **Training Room** island pad (`mapId: training_room`) with SI rules that match fleet production-world habits, dressed with harvest content for Warlords mastery. + +**Same map as** `/devnode.html` authoring. +**Code:** `worldScale.js` · `IslandHeightfield` · `StageWater` · `OpenSeaShells` · `devIslandCatalog` · `DevIslandHarvest` · `trainingRoomMap` · `trainingRoomDeploy` +**Deploy:** `docs/TRAINING_ROOM_DEPLOY_SSOT.md` (Vercel + R2/D1 promote) +**Fleet skill:** `grudge-production-world` · `grudge-world-scale` · `mine-loader-harvest-chests` (pattern) + +**Terrain note:** pad uses **heightfield** (`IslandHeightfield` + Rapier), not planar y=0 only (docs below updated for hills). + +--- + +## Hard rules (lab) + +| Rule | Value | +|------|--------| +| Units | **1 unit = 1 m** · human **1.8 m** | +| Terrain surface | **IslandHeightfield** L0 sample = mesh = feet = aim (not plane-only) | +| Island pad | `WORLD.islandRadius` dry land before shore blend | +| Shore | `shoreBand` + heightfield waterY blend | +| Water | `StageWater` + freeride; OpenSeaShells CDN backdrop | +| Physics | Rapier **heightfield** when terrain on; water freeride | +| **Content** | Training Room harvest + dummies · layout from storage/maps JSON | + +--- + +## Dev Island content + +| System | SSOT | +|--------|------| +| Mesh bake | `public/models/dev-island/rock__*.glb` (Rocks.zip → isolate) | +| Icons | `public/icons/dev-island/{minerals,inventory}/` | +| Node defs | `src/world/devIslandCatalog.js` | +| Runtime | `src/world/DevIslandHarvest.js` → `App.worldHarvest` | +| F interact | Nearest alive node within **`HARVEST_RANGE_M = 5`** · tool in hand when required | +| F priority | pickup (2.4 m) → harvest (5 m) → weapon skill | +| Tool | `t0-tool` / `weaponType=TOOL` for rock/ore; **hand** for pebbles/herbs | +| Anim | One mixer · `playWeaponAttack` / attack one-shot (sword_shield tool pack) | +| Loot | Bag + world drop splash · table family `harvest_node` | +| Combat targets | Training dummies (CombatFocus `hostile`) | +| Admin | `]` World → respawn harvest · equip tool · respawn dummies | + +Do **not** invent a second harvest engine or second AnimationMixer. + +--- + +## Visual best practices + +1. **Pad** — dark polished stone + light pool (Ground shader). +2. **Shore** — sand/moss tint + light foam noise toward water. +3. **Ocean ring** — StageWater discard under pad; storm waves + optional normal map (open-sea). +4. **Horizon islands** — `OpenSeaShells` CDN `models/worlds/small_island.glb` (backdrop only). +5. **Fog** — floor and fog share deep colour so horizon dissolves. +6. **Windsurf freeride** — board Y follows `sampleHeight`; nose = travel +Z (`artYawDeg: 0`). +7. **Harvest nodes** — DevIslandHarvest on pad; SI boulders; feet y=0. + +Do **not** raise Ground mesh for cliffs — that breaks path cast and earth paving. Production islands use heightfield shells on CDN. + +--- + +## Constants (`WORLD`) + +``` +islandRadius — dry pad radius (m) +shoreBand — shore ring width (m) +shoreTint — shore color mix 0..1 +waterSize — ocean plane extent +waterY — surface base height +floorPool* — light pool falloff +HARVEST_RANGE_M — 5 (devIslandCatalog) +``` + +Editor: `settings.environment.shoreColor`, floor colors. + +--- + +## Related + +- `docs/WINDSURF_RIDE_SSOT.md` — freeride on water ring +- `docs/CASTING_LAB_SSOT.md` — lab macro +- `docs/DROP_RATES_SSOT.md` — `harvest_node` +- Production islands: Open CDN shells + seed kits (fleet heightfield, not this pad mesh) diff --git a/docs/ISLAND_WATER_SEAFLOOR_SSOT.md b/docs/ISLAND_WATER_SEAFLOOR_SSOT.md new file mode 100644 index 0000000..c82a35f --- /dev/null +++ b/docs/ISLAND_WATER_SEAFLOOR_SSOT.md @@ -0,0 +1,37 @@ +# Island · water · seafloor · bathymetry (Casting lab) + +| Layer | Y | Notes | +|-------|---|--------| +| Water surface | **0 m** | `StageWater` only (no second water plane) | +| Dry land pad | ≥ ~0 | Heightfield FBM hills (amp ~0.85 m) | +| Shore slope | 0 → **−5 m** | Slow first, sharper later (`shoreEasePow` ≈ 1.75) | +| Island shelf weld | **−5 m** | `WORLD.seafloorY` · horizon islands plant here | +| Deep ocean floor | **−50 m** | `WORLD.oceanFloorY` · open sea beyond pad | + +## Profile (radial) + +``` +r < pad − shoreBand land FBM (dry) +pad − shoreBand → pad bathymetry: waterline → −5 (power ease) +r > pad deepen −5 → −50 over oceanDepthBand +``` + +Code: `heightAt` / `bathymetryAt` in `IslandHeightfield.js`. + +## Physics + +- **Solid walk:** Rapier heightfield = same bake (includes underwater slope + shelf) +- **Water surface:** sensor at y=0 +- **Water volume:** sensor slab from 0 down to −50 (submersion queries) +- `waterDepthAt(x,z) = max(0, waterY − landY)` — water “hits” terrain at shore + +## Visuals + +- Heightfield mesh sized past pad (covers oceanDepthBand) +- `Seafloor` plane at **−50 m** (deep sand backdrop) +- Horizon `OpenSeaShells` welded to **−5 m** shelf + +## Weapon prefab lab + +Admin Hub **F4 Prefabs**: live SI scale readout, import GLB, assign modelUrl, equip on hand. +`equipment/weaponPrefabLab.js` · appearance in `meshAppearance.js`. diff --git a/docs/JUMP_AIR_SSOT.md b/docs/JUMP_AIR_SSOT.md new file mode 100644 index 0000000..91793f3 --- /dev/null +++ b/docs/JUMP_AIR_SSOT.md @@ -0,0 +1,30 @@ +# Jump / air mobility SSOT + +**Code:** `DrcCombatController._handleJump` · `CharacterController.playJump|playFrontflip|playBackflip` · `PhysicsWorld.setGravityScale` +**Settings:** `settings.drc` (velocities, hang) · Editor **Jump / air** + **Controls** + +## Input + +| Press | Grounded | Airborne | +|-------|----------|----------| +| **Space** | Jump + blend `jump` clip (hold pose until land) | — | +| **Space** (2nd) | — | **Frontflip** (standard double jump) + slight forward push | +| **S + Space** (2nd) | — | **Hard stop** → **backflip** reverse dash (horizontal-heavy) → **hang** gravity | + +## Why hang after backflip + +`backflipHangGravity` (~0.32) + `backflipHangDuration` (~1.15 s) slow the fall so **air attacks / aim** stay usable after the flip. + +## Clip sources + +| Role | Source | +|------|--------| +| Jump | `prod:magic/standing-jump` / pack `jump` | +| Front / back flip | Procedural tilt 360° + jump clip underlay (no second mixer) | + +## Related controls + +| Setting | Default | Meaning | +|---------|---------|---------| +| `controls.sprintToggle` | **true** | Shift **press** toggles sprint | +| `controls.focusToggle` | **true** | RMB **click** toggles focus (false = hold RMB) | diff --git a/docs/LINEAR_SKILLSHOT_SSOT.md b/docs/LINEAR_SKILLSHOT_SSOT.md new file mode 100644 index 0000000..61bacb4 --- /dev/null +++ b/docs/LINEAR_SKILLSHOT_SSOT.md @@ -0,0 +1,84 @@ +# Linear skillshots — LinearAbilityCastingThreeJS → Casting lab + +**Upstream learn:** https://github.com/achrefelouafi/LinearAbiltyCastingThreeJS +**Local ref:** `Documents/_refs/LinearAbiltyCastingThreeJS` +**Integration:** `src/skillshot/*` · `LinearSkillBridge.js` +**Live:** casting.grudge.studio · casting.grudge-studio.com (same Vercel deploy) + +Does **not** replace path-cast Fire/Water/Earth/Wind staff strokes. Adds a **second** pooled skillshot system. + +--- + +## What we learned (best practices) + +| Practice | Upstream | Casting use | +|----------|----------|-------------| +| **Settings SSOT** | Every metre/colour/time sampled live each frame | `skillshot/linearSettings.js` | +| **No alloc on cast** | ObjectPool per ability type | `skillshot/abilities/AbilityManager.js` | +| **Phase machine** | travel → impact → fade → done | `Ability.js` base | +| **Constant m/s front** | Frame-rate independent line advance | base Ability | +| **MOBA aim** | Line arrow · zone circle (metres, SDF) | `AimController` + Aim/Zone indicators | +| **Procedural FX** | Geometry + GLSL, no sprite sheets | materials + `ProceduralGeometry` | +| **Variants / intensity** | global multipliers + per-ability knobs | `applyIntensity()` + editor later | +| **Ground marks** | FROST / ARC / SCORCH + fissures | extended `DecalType` + FissureSystem | +| **Post** | Grade + distortion (already in casting) | shared PostProcessing | + +--- + +## Skill map + +| Hotkey | Linear id | Shape | Product element map | +|--------|-----------|-------|---------------------| +| **Alt+Shift+Q** | ice | LINE | ice | +| **Alt+Shift+E** | thunder | LINE | storm | +| **Alt+Shift+R** | meteor | LINE | fire | +| **Alt+Shift+F** | beam | LINE | holy | +| **Alt+Shift+V** | snare | ZONE | arcane | +| **Alt+Shift+G** | glacier | ZONE | nature | + +1. Hold **Alt+Shift** + key → arm indicator +2. Aim with mouse +3. **LMB** → fire +4. **Esc** → cancel arm + +Combat path cast (draw path) and DRC skills stay on bare keys. + +--- + +## Code layout + +``` +src/skillshot/ + LinearSkillBridge.js App bridge + AimController.js + linearSettings.js full knob SSOT from upstream + ProceduralGeometry.js crystals / ribbons / asteroids + abilities/ Ice Thunder Meteor Beam Snare Glacier + materials/ Ice Lightning Meteor Beam Snare Glacier FrostField + effects/ AimIndicator ZoneIndicator GroundFissures +``` + +--- + +## Merge with weapon skills / elemental (status) + +| Step | Action | Status | +|------|--------|--------| +| 1 | Port skillshot kit + bridge | **done** | +| 2 | Wire `castToward` from DRC skill release (elemental stream/primary) | **done** — `elementalLinearCast.js` + `DrcCombatController` | +| 3 | Intensity from focus → `linearSettings.global` via `applyIntensity` | **done** | +| 4 | Texture variants for residual slash + skillshot materials | partial (`effectVariants`) | +| 5 | Editor folder for linear knobs (subset of 938) | next | +| 6 | Export effect-prefab packs for Open / Warlords | next | + +**Full map:** `docs/ELEMENTAL_LINEAR_CAST_SSOT.md` + +--- + +## Agent rules + +1. Extend `skillshot/` + bridge — do **not** fork a third AbilityManager +2. Path-cast staff and skillshots share particles/decals/lights/post +3. SI metres only +4. One AnimationMixer on hero +5. Smoke both hosts after deploy diff --git a/docs/LOADER_DRACO_KTX2_AUDIT.md b/docs/LOADER_DRACO_KTX2_AUDIT.md new file mode 100644 index 0000000..dab4b3a --- /dev/null +++ b/docs/LOADER_DRACO_KTX2_AUDIT.md @@ -0,0 +1,115 @@ +# Loader audit: Draco · Meshopt · KTX2 + +**Question:** Does having Draco + a “KTX2 gap” cause conflicts? +**Answer:** **No.** They are independent glTF extensions. Gaps and **duplicate** loaders were the real risk. + +--- + +## Verdict + +| Pair | Conflict? | Detail | +|------|-----------|--------| +| Draco geometry + Meshopt buffers | **No** | Both can exist on one GLB; each extension owns its buffers | +| Draco + KTX2 textures | **No** | Geometry vs textures | +| Missing KTX2 | **Not a conflict** | Assets without `KHR_texture_basisu` load fine (WebP/PNG/JPEG) | +| GLB with KTX2, no KTX2Loader | **Broken textures** | Silent pink/black if asset *requires* basisu | +| **Two DRACOLoader instances** | **Resource thrash** | Second WASM worker pool (projectiles vs AssetLoader) | +| Unversioned Draco CDN vs versioned | **Drift risk** | Open used `…/v1/decoders/`; Casting used `…/1.5.7/` | +| KTX2 basis path three@0.184 vs app 0.185 | **Mild mismatch** | Open was pinned 0.184; runtime three 0.185.1 | + +--- + +## What we fixed (managed edits) + +### Casting lab + +| File | Change | +|------|--------| +| `src/loaders/gltfPipeline.js` | **New** — single shared Draco · Meshopt · bindKtx2 · status | +| `src/loaders/AssetLoader.js` | Uses shared pipeline; `bindRenderer(gl)` for KTX2; sRGB textures | +| `SkillProjectileSystem.js` | Uses `sharedGltfLoader()` — **no second DRACOLoader** | +| `config/assets.js` | Documents versioned Draco + KTX2 0.185.1 paths | +| `App.load()` | Calls `assets.bindRenderer(gl)` before asset load | + +### Open (gameopen) + +| File | Change | +|------|--------| +| `artifacts/animator/src/three/loaders/gltf.ts` | Draco → **versioned 1.5.7**; KTX2 pin **0.185.1** (match package three) | + +--- + +## Correct architecture (fleet) + +``` +WebGLRenderer created + → bindKtx2(renderer) // once — detectSupport + → one DRACOLoader // process-wide WASM + → GLTFLoader + .setDRACOLoader(shared) + .setMeshoptDecoder(MeshoptDecoder) + .setKTX2Loader(sharedKtx2 if bound) + +Any other system (projectiles, camp, drops) + → sharedGltfLoader() only — never new DRACOLoader() +``` + +### When each decoder runs + +| Extension on asset | Needs | +|--------------------|--------| +| (none) | Plain GLTFLoader | +| `KHR_draco_mesh_compression` | DRACOLoader | +| `EXT_meshopt_compression` | MeshoptDecoder | +| `KHR_texture_basisu` | KTX2Loader + GPU support | + +If asset has **no** basisu textures, **missing KTX2 never breaks Draco**. + +--- + +## Pins (SSOT) + +| Decoder | Path | +|---------|------| +| Draco | `https://www.gstatic.com/draco/versioned/decoders/1.5.7/` | +| KTX2/Basis | `https://cdn.jsdelivr.net/npm/three@0.185.1/examples/jsm/libs/basis/` | +| three | `^0.185.1` | + +When upgrading three major: bump **both** package.json and `KTX2_TRANSCODER_PATH` in the same PR. + +Optional later: self-host `public/draco/` + `public/basis/` for offline / CSP. + +--- + +## Audit checklist + +``` +[x] One shared Draco worker pool (Casting) +[x] Projectiles use sharedGltfLoader +[x] KTX2 bound after renderer +[x] Versioned Draco path Open + Casting +[x] KTX2 path matches three 0.185.1 +[ ] Self-host decoders under public/ (optional) +[ ] CI smoke: load one Draco GLB + one Meshopt GLB +[ ] When first basisu atlas ships: verify bindKtx2 before load +``` + +--- + +## Symptom → cause + +| Symptom | Likely cause | +|---------|----------------| +| Geometry missing / stuck load | Draco path 404 or CORS | +| Double memory on many projectiles | Second DRACOLoader (fixed) | +| Pink/black materials on **KTX2** GLB | bindKtx2 never called | +| Pink on **WebP** atlas | colorSpace / flipY / CDN 404 — not KTX2 | +| Works in Casting, fails Open | Path pin mismatch (fixed) | + +--- + +## Related + +- Casting `AssetLoader` · `gltfPipeline.js` +- Open `three/loaders/gltf.ts` · `bindKtx2` +- Review: `FLEET_ENTRY_DEPLOY_LOADERS_RAYCAST_REVIEW.md` (KTX2 gap now closed in lab) diff --git a/docs/MAIN_PANEL_INVENTORY_SSOT.md b/docs/MAIN_PANEL_INVENTORY_SSOT.md new file mode 100644 index 0000000..b15e168 --- /dev/null +++ b/docs/MAIN_PANEL_INVENTORY_SSOT.md @@ -0,0 +1,66 @@ +# Casting Main Panel · Inventory · Equipment SSOT + +**Host:** casting.grudge.studio (production lab) +**Panel:** `I` / `Q` → `InventoryPanel` (class name historical; UI title **Main Panel**) + +## Design sources + +| Source | Use | +|--------|-----| +| [Tactical Infinity equipment](https://tactical-infinity.replit.app/equipment) | Dark paperdoll, dual columns, silhouette | +| `Sample-InventorySlotsSet.png` | Bag slot chrome skins | +| **`inventory.png`** (Documents → `public/ui/warlords-dev/inventory/`) | Full bag shell ref · Main Panel `wl-inv-shell` | +| **`miniinventory.png`** | Quick bag / DropBag 9×4 + hotbar 1–10 | +| **`button UI.png`** | Pixel atlas for chrome icons | +| **Pirate Pack Cursors** | Hover intents (harvest / loot / attack / ship…) | +| `equipment.png` screenshot | Layout ENHANCEMENTS / ENCHANT | +| [Player-Inventory-System](https://github.com/fideltfg/Player-Inventory-System) | LMB pick/place, equip from bag, slot accepts | +| https://grudgewarlords.com/craft/ | Bag materials · professions skill trees SSOT | + +Full map: `docs/WARLORDS_DEV_UI_SSOT.md` + +## Interactions (MMO quality) + +| Input | Action | +|-------|--------| +| **LMB paperdoll slot** | Open bag options that **fit** the slot | +| **RMB paperdoll slot** | Context menu: Unequip · Replace from bag… · Inspect | +| **LMB bag item** | Pick item → LMB paperdoll to equip | +| **RMB bag item** | **Equip → slot** · Split · Drop world · Deposit account · Inspect · Craft ↗ | +| **RMB DropBag item** | Equip → Main bag · Drop world · Deposit | +| **Inventory tab** | 9×3 + util row (Warlords inventory shell) | +| **API tab** | Railway ping · characters · fleet Main Panel iframe · UI asset catalog | +| **Professions tab** | WCS trees: Miner · Forester · Chef · Engineer · Mystic | +| **Slots tab / Admin F1** | Edit accept filters + labels (local admin) | + +**Code:** `itemContextMenu.js` · `uiAssetCatalog.js` · `mainPanelSlots.js` + +## Files + +| Path | Role | +|------|------| +| `src/ui/mainPanelSlots.js` | Slot SSOT, bag, equip map, profession progress | +| `src/ui/mainPanel.css` | TI look + bag chrome | +| `src/ui/itemContextMenu.js` | RMB MMO item / equipment menus | +| `src/ui/uiAssetCatalog.js` | Full UI asset inventory + prod Main Panel URLs | +| `src/ui/InventoryPanel.js` | Main Panel UI (`wl-inv-shell`) | +| `src/ui/DropBag.js` | Mini bag (harvest loot / throw) + RMB | +| `src/ui/craftpixUi.js` | CraftPix HUD textures | +| `src/ui/warlordsUiSkin.js` · `warlordsCursors.js` | Shells + pirate intents | +| `public/ui/craftpix/**` | Hotbar, unit frames, cast, panel | +| `public/ui/inventory/**` | Slot set + equipment silhouette | +| `public/ui/warlords-dev/**` | inventory / mini / buttons / cursors | + +## Data + +- Equip map + bag: `localStorage` (`casting.mainPanel.*`) for lab +- Live mesh: mesh_ids + `equipWeaponById` for weapons +- Icons: `iconResolve.js` → CDN `496_rpg_icons` + `public/icons/dev-island/minerals` +- Catalog: `loadGameItemCatalog()` (info + objectstore) warms bag icons +- Harvest loot → DropBag **and** Main Panel bag (with resolved icons) +- Account deposit: `fleetApi.depositItem` → Railway paths; fail → Craft SSOT +- API tab: fleet status bundle · inventory · catalog import T0 + +## Deploy + +Build/deploy CastingAbilitiesThreeJS to casting.grudge.studio and verify **I** → Character paperdoll + Inventory + Professions. diff --git a/docs/MASTERY_SYSTEMS_SSOT.md b/docs/MASTERY_SYSTEMS_SSOT.md new file mode 100644 index 0000000..2f4a8db --- /dev/null +++ b/docs/MASTERY_SYSTEMS_SSOT.md @@ -0,0 +1,98 @@ +# Mastery systems SSOT (Casting Warlords lab) + +**Goal:** fun → **awesome** — electric/AOE spell look, texture variants (size/speed/angle + alt texture), weapon/tool equip, mobility, GRUDOX animator camera/focus/crosshair closer parity, XState activity split. + +**Host:** `casting-abilities-threejs.vercel.app` · `casting.grudge-studio.com` +**Repo:** `CastingAbilitiesThreeJS` + +--- + +## 1. XState activity machine + +| File | Role | +|------|------| +| `src/combat/playerActivityMachine.js` | Pure states: `combat` ↔ `harvest` · hand · tool memory · loco | +| `src/combat/playerActivity.js` | Radials + tool defs + labels (SSOT data) | +| `App.js` | Side effects: equip / stow / toast | + +### Events + +| Event | Effect | +|-------|--------| +| `ENTER_HARVEST` | Stow combat weapon id → draw last tool (default **pick**) | +| `ENTER_COMBAT` | Stow tool → restore `combatWeaponId` (or t0-wand) | +| `SELECT_TOOL` | Hold-R radial pick | +| `DRAW_LAST_TOOL` | **Tap R** — last used tool, default pick | +| `SET_LOCO` | idle / walk / run / sprint / jump / harvest_swing / cast | + +Do **not** invent a second mode store — SessionState still owns cast/walk/ride gates; this machine owns **activity + hand**. + +### Input map + +| Input | Behaviour | +|-------|-----------| +| Hold Q | Mode radial ↑ combat · ↓ harvest | +| Tap Q | Toggle combat ↔ harvest | +| Hold R (harvest) | Tool radial | +| Tap R (harvest) | Draw last tool (default pick) | +| F | Harvest nearest ≤5 m (tool in hand) or combat skill | + +--- + +## 2. Camera / focus / crosshair (GRUDOX animator) + +Reference: `grudox.grudge-studio.com/animator/` · `Documents/animator/animator/src/three/Controller.ts` + `Crosshair.tsx` + `aim/AimSystem.ts`. + +| Animator concept | Casting wiring | +|------------------|----------------| +| Screen-centre aim ray | `CameraRig.aimRay()` · `MouseAim.updateFocusAim` | +| Soft lock frame (look bias, not hard snap) | `CameraRig.setSoftLock` · weight focus 0.42 | +| Crosshair bloom gap | HUD `--ch-gap` from spread 0..1 | +| Range ring close/optimal/far | `settings.aim.optimalRangeMin/Max` | +| Hit marker pulse | `setCrosshairState({ hitMarker })` | +| Sprint FOV kick | `camera.sprintFov` 78 · `setSprinting` | +| Focus tighter distance/shoulder | `focusDistance` 5.5 · `focusShoulderOffset` 0.8 | + +OrbitControls **never** writes camera in TPS (fleet rule). + +--- + +## 3. Spell mastery — variants + electric / AOE + +| File | Role | +|------|------| +| `src/vfx/effectVariants.js` | Catalog: electric + AOE size/speed/angle + textureKey / textureAlt | +| `LinearSkillBridge.applyVariant(id)` | Live apply to linearSettings block | +| `linearSettings.thunder` | Electric look: denser strands, brighter core/halo, ground arcs | +| `linearSettings.snare` / `glacier` | Larger zoneRadius AOE footprints | +| `linearSettings.meteor` | Larger radius + dual texture keys | + +**Rule:** one texture family · many casts via size / speed / angle; second texture for impact/rim/ground. + +--- + +## 4. Weapon skills + mobility (existing, extended) + +| System | File | +|--------|------| +| DRC 1–4 / F residual | `DrcCombatController.js` | +| Weapon equip | `equippedWeaponRuntime.js` | +| MM dodge / roll / slide | `motionMath.js` · CharacterController | +| Sprint FOV | CameraRig | + +Next mastery slices (not blocking this ship): intensity tiers in editor, DRC castToward for line skills on soft-lock, weapon skill tree polish per pack. + +--- + +## Confirmation gates + +``` +[ ] Tap Q harvest → pick auto-equipped (or last tool) +[ ] Tap Q combat → weapon restored +[ ] Tap R in harvest → last tool, default pick +[ ] Focus → crosshair + range ring on soft-lock +[ ] Sprint → FOV opens slightly +[ ] Alt+Shift+E thunder looks electric (strands/glow/arcs) +[ ] Snare/glacier zone footprints readable +[ ] No second mixer / second combat engine +``` diff --git a/docs/MELEE_COMBO_SSOT.md b/docs/MELEE_COMBO_SSOT.md new file mode 100644 index 0000000..0323b42 --- /dev/null +++ b/docs/MELEE_COMBO_SSOT.md @@ -0,0 +1,58 @@ +# Melee combo SSOT — sword & shield (Casting lab) + +**Code:** `settings.meleeCombo` · `CharacterController.playMeleeAttack` · `DrcCombatController.useMeleeStrike` +**Clips:** `public/anims/baked/sword_shield/*` + Open CDN Bip001 finisher +**Pack:** `ANIM_PACKS.sword_shield` · roles `attack1|attack2|attack3|attack|finisher|finisherAir` + +## Problem + +CDN `sword_shield/sword and shield attack` is a **jump / dash finisher**, not a light swing. Using it for every LMB/F made every hit look like a finisher leap. + +## Contract + +| Input | Condition | Role | Body MM | Residual | +|-------|-----------|------|---------|----------| +| LMB / F melee | Grounded, normal | `attack1` → `attack2` → `attack3` (chain) | none | light ~2.6 m +0.35/step | +| LMB / F melee | **Airborne** | `finisherAir` → `dropto-target` | air lunge ~180 MM | ~6.5 m | +| LMB / F melee | **Large MM toward target** (sprint into aim) | `finisher` / `attack` (CDN jump-dash) | lunge ~320 MM | ~5.5 m | + +- **Chain window:** `settings.meleeCombo.chainWindow` (default 0.85 s) +- **Finisher MM threshold:** `settings.meleeCombo.finisherMm` (default 280 MM ≈ 2.8 m intent) +- **100 MM = 1 m** (`motionMath.js`) + +## Reviewed source FBX (Mixamo → same-origin bake) + +All sources under `Documents\*.fbx`, skeleton **Mixamo** (`Hips`…), rematched to **Bip001** at bind (`bakeClip.rematchClipToSkeleton`). + +| File | Dur | Role in lab | +|------|-----|-------------| +| `intoout.fbx` | 1.70 s | **attack1** light (in→out slash) | +| `St1able Sword Inward Slash.fbx` | 2.23 s | **attack2** light (stable inward) | +| `11Upward Thrust.fbx` | 2.37 s | **attack3** light (upward thrust) | +| `One Hand Sword Combo.fbx` | 4.57 s | Full chain + equal thirds `one-hand-combo-hit1..3` (fallback) | +| `Two Hand Sword Combo.fbx` | 3.30 s | 2H style (baked, not primary S&S light) | +| `Dual Weapon Combo.fbx` | 3.67 s | Dual wield (baked, not primary) | +| `spear1.fbx` | 3.30 s | Spear pack candidate | +| `greataxe.fbx` | 5.47 s | Greataxe pack candidate | +| `dropto target.fbx` | 0.83 s | **finisherAir** | +| CDN `sword and shield attack` | 2.33 s | **finisher** (jump/dash) — Bip001 native | +| CDN `sword and shield slash` | 3.53 s | Fallback if local bake 404 | + +Raw GLB intermediates: `public/anims/raw/*.glb` +Baked JSON: `public/anims/baked/sword_shield/*.json` +Manifest: `public/anims/baked/sword_shield/_melee_combo_manifest.json` + +## Extend pattern + +1. Drop FBX → Blender headless GLB → Node `AnimationClip.toJSON` into `public/anims/baked/sword_shield/` +2. Add role URL list under `ANIM_PACKS.sword_shield` +3. Register `ANIM_ROLE_META` +4. Prefer same-origin `./anims/baked/…` (already first in `bakedClipUrls`) +5. Smoke: equip sword_shield, LMB×3 grounded, LMB in air, sprint+LMB toward aim + +## Hard bans + +- ❌ Binding every light hit to CDN `sword and shield attack` (finisher only) +- ❌ Second mixer for combo +- ❌ Shipping raw Mixamo FBX to browser (JSON bake only) +- ❌ Inventing `slashLeft` role names — use `attack1|2|3|finisher|finisherAir` diff --git a/docs/PISTOL_ANIM_SSOT.md b/docs/PISTOL_ANIM_SSOT.md new file mode 100644 index 0000000..3d91f1d --- /dev/null +++ b/docs/PISTOL_ANIM_SSOT.md @@ -0,0 +1,38 @@ +# T0 pistol animation SSOT + +## Sources + +| Source | Path | Use | +|--------|------|-----| +| **Bip001 bake (live)** | `open.grudge-studio.com/anims/baked/pistol/*` | grudge6 / Casting / Multiverse | +| **Mixamo author FBX** | `gameopen/.../anim/pistol/` — gunplay, drawing-gun, charged-pistol, pistol-whip | re-bake | +| **grudgepistolzio (incoming)** | `D:\Games\Models\_anim_packs\grudge6_incoming_2026-08-01\grudgepistolzio` | one-hand gun **loco** + kneel; bake next | +| **Minecraft TPS reference** | `minecraft_tps_model_1780812780503.glb` | timing + prop nodes only | +| **Lab copy** | `Casting…/public/models/reference/minecraft_tps_pistol.glb` | preview | +| **Flintlock mesh** | `public/models/weapons/t0-flintlock.glb` | `t0-gun` hand mesh | +| **Bullet** | `public/models/vfx/projectiles/bullet1.glb` | Styloo pack · see PISTOL_FLINTLOCK_SSOT | + +**Do not** retarget TPS clips onto Bip001 — rigid Minecraft nodes (`Pistol_13`, `Rightarm_14`). + +## TPS clip → fleet role + +| TPS clip | ~s | Fleet role | Bip001 clip | +|----------|-----|------------|-------------| +| fire / fireaim | 0.21 | `attack` | `pistol/gunplay` (spin) @ timeScale ~1.6 | +| draw / drawaim | 0.17–0.25 | `draw` | `pistol/drawing-gun` | +| drawnidle / drawaimidle | 1.6–2.0 | `idle` / `cast` | `pistol/idle` | +| walk | 0.88 | `walk` | `pistol/walk-forward` | +| idle | 9.1 | `idle` | `pistol/idle` | + +**gunplay** = Mixamo pistol spin flourish (primary fire / spin skill). + +## Casting wiring + +- `ANIM_PACKS.pistol` in `src/config/assets.js` +- `WEAPON_TYPE` PISTOL/GUN/HANDGUN → meshSlot `pistol` → pack `pistol` +- Equip attach profile `pistol` (~0.42 m SI handgun) +- `pistolAnimSsot.js` — timings + `pistolTimeScale()` + +## Multiverse + +`game/animPackLoader.js` pistol roles prefer `gunplay` / `drawing-gun` / `charged-pistol` over dual_wield fallbacks. diff --git a/docs/PISTOL_FLINTLOCK_SSOT.md b/docs/PISTOL_FLINTLOCK_SSOT.md new file mode 100644 index 0000000..889a193 --- /dev/null +++ b/docs/PISTOL_FLINTLOCK_SSOT.md @@ -0,0 +1,196 @@ +# T0 Flintlock pistol · grudgepistolzio · bullets + +**Catalog:** `t0-gun` · **name:** Flintlock Pistol +**Live JSON:** https://info.grudge-studio.com/api/v1/t0-weapons.json +**Lab mesh:** `public/models/weapons/t0-flintlock.glb` (from `D:\Games\Models\flintlock.glb`) +**Bullet:** `public/models/vfx/projectiles/bullet1.glb` (Styloo Guns pack) +**Code:** `src/vfx/pistolBulletVfx.js` · `SkillProjectileSystem.spawnBullet` · `weaponSkillProduction` · `DrcCombatController` + +**Equip lab:** `?t0=t0-gun` · Inventory Weapon tab + +--- + +## Weapon stats (catalog SSOT) + +| Stat | Value | +|------|-------| +| DMG | **28** | +| CRIT | **3%** | +| DEF | **3** | +| Flavor | Homemade · more loud than accurate | +| Craft T1 | starter + 2× scrap-ingot · 1× driftwood-log · 1× rough-stone @ Anywhere | +| Slots | 1–2 auto · slot 3 choose one · T0 only until T1 five-slot | + +--- + +## T0 skill kit (catalog ids — do not invent) + +| Slot | Id | Name | Role | DMG | CD | Cast | Effects | Runtime | +|------|-----|------|------|-----|----|------|---------|---------| +| 1 auto | `t0_gun_practice_shot` | Practice Shot | Starter Attack | 28 | — | Instant | Starter · bullet | 1× `spawnBullet` | +| 2 auto | `t0_gun_take_cover` | Take Cover | Starter Style | — | 5s | Instant | −dmg taken 2s | Ward · 20% DR 2s · block anim | +| 3 pick | `t0_gun_burst_fire` | Burst Fire | Choose (default) | 20 | 6s | Instant | Multi-hit · bullet | **3×** bullets · 80 ms gap · small fan | +| 3 pick | `t0_gun_suppressing_shot` | Suppressing Shot | Choose | 16 | 4s | Instant | Slow fire rate enemy · bullet | 1× bullet + **slow** status on soft-lock | + +Default slot 3: **Burst Fire**. + +**Best practices (lab):** + +- Catalog numbers only — never invent damage/CD +- `projectile: "bullet"` preserved through `normalizeSkillDef` +- Pack = **`pistol`** (not longbow) · meshSlot `pistol` +- Blood only on living · terrain = micro explosion +- Take Cover is ward (not focus) — sets `_wardUntil` / `_wardReduce` +- Burst multiHit = 3 from description “Three-round burst” + +--- + +## Anim pack review — grudgepistolzio + +**Path:** `D:\Games\Models\_anim_packs\grudge6_incoming_2026-08-01\grudgepistolzio` + +| Clip set | Role | +|----------|------| +| idle / walk / run / backward / strafe / arc | One-hand **loco** (guns + can cover crossbow-style stance) | +| jump | Jump | +| stand↔kneel / kneeling idle | Aim-down / braced idle | +| Heavy_mixamo | **Review** — likely heavy weapon, **not** flint fire default | + +**Live combat fire/draw/whip** still from Open bake: + +`open.grudge-studio.com/anims/baked/pistol/{gunplay,drawing-gun,charged-pistol,pistol-whip,idle,walk-*,run-*,strafe-*}.json` + +**Next bake:** convert zio loco FBX → Bip001 JSON on Open CDN, then point `ANIM_PACKS.pistol` candidates at new names. Do **not** invent parallel packs. + +Config: `src/config/pistolAnimSsot.js` · `GRUDGE_PISTOL_ZIO_INCOMING` + +--- + +## Weapon mesh + +| Field | Value | +|-------|--------| +| Catalog id | `t0-gun` | +| Local URL | `./models/weapons/t0-flintlock.glb` | +| Hand SI | ~0.48 m length (`WeaponMeshAttach` pistol profile) | +| Anim pack | `pistol` | +| Equip | `?t0=t0-gun` or Inventory Weapon tab | + +`T0_MODEL_CDN['t0-gun']` points local until R2 `prod/gltf/weapons/t0-flintlock.glb` is uploaded. + +**Size note:** source ~23 MB — compress with gltf-transform (Draco/WebP) before CDN promote. + +--- + +## Bullet projectile + +| Knob | Value | +|------|--------| +| Mesh | Styloo `bullet1.glb` (~2.7 cm raw) | +| Speed | **90 m/s** lab (readable ballistic; not real 300+) | +| Trail | **20%** default trail length (~0.2 m ribbon) | +| Life | 1.2 s | +| Contact | 0.12 m | + +### Impact rules + +| Target | VFX | +|--------|-----| +| Living (`hostile` · npc · player · boss · creature · unit) | Red **liquid blood** splatter + light inferno | +| Terrain · aim · props · ground | **Micro explosion** flash — **no blood** | + +`isLivingTarget()` in `pistolBulletVfx.js` — extend kinds there, not ad-hoc. + +--- + +## Charged Shot UX (hold · ticks · rest · blend) + +| Step | Behavior | +|------|----------| +| **Hold 1 / F** | Begin charge · cast bar ticks (20 Hz) · **charged-pistol** wind-up blend | +| **Levels** | Tap → Wind → Charged → Power → Full (damage mul ~1–2.05) | +| **Release** | Fire with intensity costs + damage mul · muzzle bullet | +| **Best rest** | ~0.42 s + intensity · blocks next weapon skill | +| **GCD** | 0.12 s global combat timer between attacks | +| **Cancel** | short rest · restore gait blend | +| **UI click** | tap fire (no hold) | + +Code: `weaponChargeSystem.js` · `beginWeaponCharge` / `releaseWeaponCharge` · `CharacterController.beginWeaponChargeAnim` + +--- + +## Chamber (production) — empty → **1 = Reload** + +| State | Key 1 | Fire (1 loaded / 3) | +|-------|-------|---------------------| +| **Loaded** | Practice Shot | bullet · then empty | +| **Empty** | **Reload** (baked) | blocked · toast “press 1” | +| **Reloading** | locked | blocked | + +Code: `src/combat/flintlockChamber.js` · single powder load (capacity 1). + +## Fire timing · barrel · soft-lock · reload + +| Concern | SSOT | Value | +|---------|------|--------| +| Fire timeScale | `pistolTimeScale('fire')` | gunplay → ~0.38 s wall | +| Hit frame | `FLINTLOCK_FIRE.hitFrameSec` | **0.14 s** then bullet leaves muzzle | +| Burst gap | `FLINTLOCK_FIRE.burstGapSec` | **0.09 s** | +| Muzzle | `WeaponAttach` → `WeaponMuzzle` | farthest mesh tip from grip | +| Soft-lock blend | `settings.aim.pistolSoftLockBlend` | **0.82** · max **34°** | +| Crosshair | App pistol spread | tight on lock · wider free (inaccurate) | +| **Baked reload** | `public/anims/baked/pistol/reload.json` | drawing-gun tracks · role `reload` | +| Reload pose | `PistolReloadPose` | gun → chest · L-hand barrel · tilt ~22° (post-mixer) | +| Auto after shot | `FLINTLOCK_RELOAD.afterShot` | **false** (must press 1) | + +## Open Danger pistol skill review (weapon-live-packs) + +| Open slot | Anim clip | Casting T0 | Needs load | +|-----------|-----------|------------|------------| +| `pistol_shot` | gunplay | Practice Shot | yes | +| `pistol_double` | gunplay ×3 | Burst Fire | yes | +| `pistol_fan` | charged-pistol | Suppressing Shot | yes | +| `pistol_reload` | **reload** | empty → key 1 | no | +| (review) | pistol-whip | melee whip | no | +| (review) | charged-pistol | power | yes | + +SSOT: `OPEN_DANGER_PISTOL_SLOTS` · `OPEN_PISTOL_SKILL_SLOTS` · `FLINTLOCK_ANIM_REVIEW` + +## Wiring path + +``` +Equip t0-gun (?t0=t0-gun) + → flintlock mesh + pistol anim pack + muzzle + chamber Loaded + → hotbar: Practice Shot · Take Cover · Burst|Suppress + → keys 1–3 / F (+ RMB focus) + 1 Loaded → gunplay → hit 0.14s → muzzle bullet → Empty + 1 Empty → Reload (baked pistol/reload + powder pose) → Loaded + 2 Take Cover → ward (no ammo) + 3 Burst/Suppress → needs load · empties chamber + → Showcase Anims: idle/walk/gunplay/reload/whip/charged review +``` + +--- + +## Checklist + +``` +[x] Flintlock mesh on t0-gun +[x] Bullet mesh + short trail + high speed +[x] Blood only on living +[x] Micro explode on terrain +[x] grudgepistolzio reviewed in SSOT +[x] Practice Shot / Take Cover / Burst Fire / Suppressing Shot runtime +[x] multiHit 3 for Burst · ward DR for Take Cover · slow for Suppress +[x] anim pack pistol (not longbow) in presentation +[x] Fire hit-frame timing + muzzle barrel spawn +[x] Soft-lock / crosshair assist for pistol pack +[x] Chamber: empty → key 1 Reload +[x] Baked pistol/reload.json (drawing-gun stamp) + procedural pour layer +[x] Open Danger skillSlots mapped (shot/double/fan/reload) +[ ] Promote reload.json to Open CDN anims/baked/pistol/reload.json +[ ] Dedicated Mixamo pistol reload FBX (replace drawing-gun stamp) +[ ] Upload flintlock mesh to R2 prod +[ ] Bake zio loco → Open CDN +[ ] Crossbow pack choice (longbow vs pistol) product decision +``` diff --git a/docs/PREFAB_SCAFFOLD_CONTROL_SSOT.md b/docs/PREFAB_SCAFFOLD_CONTROL_SSOT.md new file mode 100644 index 0000000..447a280 --- /dev/null +++ b/docs/PREFAB_SCAFFOLD_CONTROL_SSOT.md @@ -0,0 +1,92 @@ +# Prefab scaffold control — access + generation + +**Code:** `src/api/prefabScaffold.js` · `weaponPrefabContract.js` · Inventory **Prefabs** tab +**Goal:** Correct **control** of the fleet item scaffold (read authorities, UUID graph, validation) **and** generation packs for **icons · 3D sprites · item scripts · stats · craft formulas** — without inventing ITEM-*/SKIL-* outside ObjectStore. + +--- + +## Control (read / resolve) + +| Need | Endpoint / tool | +|------|-----------------| +| Authority map | `canonical-items-manifest.json` | +| Equipment layers | `_meta/canonical-equipment-pattern.json` | +| Weapon prefabs | `master-weapon-prefabs.json` | +| T0 skills + craft | `t0-weapons.json` | +| Skill bodies | `master-weaponSkills.json` | +| Craft formulas | `master-recipes.json` (RECP-*) + T0 `craftingRecipe` | +| UUID index | `master-registry.json` | +| Icons | `icon-registry.json` · ICON_BROWSER · assets CDN | +| Validate | `validateWeaponPrefab` · 6 layers | +| Export | `exportWarlordsWeaponPrefab` · `buildItemScaffoldPack` | + +**Hosts:** `info.grudge-studio.com/api/v1/` → `objectstore.grudge-studio.com/api/v1/` → GitHub Pages ObjectStore. + +**Binaries only:** `assets.grudge-studio.com` (never put GLB blobs in JSON). + +--- + +## What “scaffold” means + +``` +Definition (ITEM-*) + Skills (SKIL-*) + Assets (ICON-*, R2 paths) + + Recipe (RECP-* / T0 materials) + Runtime (meshSlot, animPack, jobs) +``` + +Instance (owned, durability) = **Railway** — not this scaffold. + +--- + +## Generation (drafts you control) + +| Artifact | Output | Then | +|----------|--------|------| +| **Style icon** | Prompt + palette + target CDN path | Artist / Imagine → R2 → `assets.iconUrl` + ICON-* | +| **3D sprite / mesh** | SI brief + R2 path + dropPrefab path | grudge-asset-convert → `modelUrl` / drop key | +| **Item script** | JS module stub (use, stats, skills, craft) | Reference for fleet wiring — not eval | +| **Stats** | From catalog `stats` | Edit only in ObjectStore generators | +| **Craft formula** | T0 materials or RECP-* materials[] | ObjectStore recipes pipeline | + +**Never mint GRUDGE UUIDs in the lab.** Lab produces **briefs + export JSON**; ObjectStore `build:weapon-pipeline` / `generate:master` writes catalog. + +Style families: `warlords_dark` · `toon_rts` · `starter_t0` (`ITEM_STYLE_FAMILIES`). + +--- + +## Lab UI + +### Admin Hub **F4 Prefabs** (primary tools surface) + +Hotkeys **F1–F5** open Admin Hub (`docs/ADMIN_HUB_F1_F5_SSOT.md`): + +- **F4** — weapon/armour drafts, T0 scaffold download, local save/export +- **F2** — buildables (purpose + script) +- **F3** — enemy / ally / Grudge kits + +### Inventory **Prefabs** tab (I) + +1. Select weapon / T0 row +2. **Scaffold** panel: validation score, UUID graph, craft formula +3. Download: full pack · icon brief · 3D brief · item script · prefab export +4. Equip → combat bar (catalog skills only) + +--- + +## Agent rules + +1. Access catalogs via `loadPrefabScaffold` / existing loaders — no forked JSON. +2. Generation = **briefs + contract JSON**, not silent ITEM-* invent. +3. Craft: prefer T0 `craftingRecipe`, else `master-recipes` by result. +4. Icon/3D: SI + house style; register on CDN then patch prefab in ObjectStore. +5. After generation assets land, re-run `validateWeaponPrefab` → 6/6. + +--- + +## Related + +| Doc | Role | +|-----|------| +| `WEAPON_PREFAB_UUID_SSOT.md` | UUID graph + 6 layers + 7 jobs | +| `GAME_ITEM_PREFAB_PRODUCTION_SSOT.md` | All categories | +| `WEAPON_SKILLS_API_SSOT.md` | Skill fields | +| `T0_WEAPONS_SSOT.md` | 15 starters | diff --git a/docs/PRODUCTION_CONTROLLER_SSOT.md b/docs/PRODUCTION_CONTROLLER_SSOT.md new file mode 100644 index 0000000..7f41888 --- /dev/null +++ b/docs/PRODUCTION_CONTROLLER_SSOT.md @@ -0,0 +1,129 @@ +# Production controller SSOT — Warlords era (Casting lab) + +**Live:** https://casting-abilities-threejs.vercel.app/ +**Repo:** CastingAbilitiesThreeJS — proof before grudgewarlords.com / Open + +This document is the **agent learning map** for character, weapons, skills, anim, physics, windsurf, and creating new elemental skills. + +--- + +## Stack layers (do not invent parallels) + +| Layer | SSOT | Notes | +|-------|------|--------| +| **Session** | `SessionState.js` | mode · combat/equip · ride phase · gates | +| **Tweaks** | `settings.js` | numbers/colors live in editor | +| **Character mesh** | `toonKitPlay` · Toon RTS GLB | SI, mesh_ids equip | +| **Anim packs** | `ANIM_PACKS` · `animLibrary.js` | magic / sword_shield / longbow + mobility | +| **Gait / blend** | `CharacterController.play` | gaitBlend · combatBlend | +| **Combat** | `DrcCombatController` | land loco, skills, path cast, resources | +| **Resources** | `castResources.js` | **mana + stamina**, hold intensity | +| **Weapon charge** | `weaponChargeSystem.js` | hold → ticks → release · rest · GCD · charged-pistol blend | +| **Spells** | `castingSpellKit.js` + `staffSignatureSkills.js` | 10 + Inferno/Blizzard/Warp/Quake/Tempest | +| **Elemental × linear** | `elementalLinearCast.js` · `LinearSkillBridge` | plan → castToward + path Ability + mesh | +| **T0 weapons** | `t0ApprenticeWand.js` · `t0WeaponCatalog.js` | base attacks + mana | +| **Weapon equip** | `equippedWeaponRuntime` · WeaponMeshAttach | prefabs | +| **Path place** | `pathCastClassify` · `staffCast` | aoe/spikes/wall/stream | +| **Linear skillshots** | `skillshot/*` | ice/thunder/meteor/beam/snare/glacier | +| **VFX** | Ability* · VfxDirector · effectPrefab · elementAttackVfx | isolate primitives | +| **Physics** | Rapier CCT | board parents rider | +| **Windsurf** | WalkController · HoverboardRide · RideIK | vehicle parent | + +--- + +## Dual resources (mana + stamina) + +| Resource | Use | Regen | +|----------|-----|--------| +| **Mana (MP)** | Spells, path cast, signatures | `settings.drc.manaRegen` (~12/s) | +| **Stamina (STA)** | Spells + dodge/roll/slide + melee | `settings.drc.staminaRegen` (~18/s) | + +### Hold LMB intensity + +``` +intensity = clamp(1 + holdSec * 0.42 + max(0, lengthM − 2) * 0.055, 1, 3) +manaCost = ceil(baseMana * intensity) +stamina = ceil(baseStam * (0.55 + 0.45 * intensity)) +``` + +Longer hold + longer stroke = **bigger** Inferno/Blizzard feel and cost. +Toast shows `×intensity · −N MP −N STA`. + +Code: `castResources.js` · spend in `DrcCombatController._spendResources`. + +--- + +## T0 weapons & base attacks + +| Piece | Pattern | +|-------|---------| +| T0 wand | Practice Bolt (1) · Focus (2) · Frost/Arcane choice (3) | +| Base attack | Pack `attack`/`cast` one-shot + residual F | +| Locomotion | idle/walk/run + Shift freeride-run · combat mobility | +| Equip | mesh_ids + `animPackForLoadout` | + +`?wand=1` activates T0 tree. Mana on bolts; Focus is buff. + +--- + +## Staff elemental signatures + +| Staff | Signature | Element | Path | +|-------|-----------|---------|------| +| Fire | **Inferno** | fire | stream | +| Ice | **Blizzard** | water | aoe | +| Nature | **Quake** | earth | wall | +| Storm | **Tempest** | wind | stream | +| Arcane | **Warp** | arcane | stream | + +High path intensity (≥2.4) auto-tags signature impact VFX. + +--- + +## Creating new skills / effects (AI learning loop) + +``` +1. Load casting-warlords-lab + grudge6-combat-runtime + anim library SSOT +2. Load ELEMENTAL_LINEAR_CAST_SSOT + LINEAR_SKILLSHOT_SSOT +3. Pick primitive: trail | travel | cast | impact | residual | linear_line | linear_zone +4. Study element Ability + linearSettings + materials +5. Catalog skill only (WEAPON_SKILLS / t0) — planElementalLinearCast infers layers +6. Mesh kinds → elementAttackVfx; line/zone → PRODUCT_TO_LINEAR +7. Wire equip tree / hotbar — not a third AbilityManager +8. Smoke: Q combat · focus LMB · digit skills · Alt+Shift linear arm · toast lin:* +9. Export prefab bind for WEAPON_SKILLS / ObjectStore +``` + +### Animation creation (fleet) + +- Prefer Bip001 baked JSON on open CDN (`prod:magic/…`) +- Roles only in `ANIM_PACKS` + `ANIM_ROLE_META` — no invented names +- Gait: longer crossfade; combat: short fade (`settings.character.gaitBlend`) + +### Effects creation + +- Extend existing Ability / VfxDirector catalog id +- Orbs for projectiles — never whole fireball.glb scene +- Knobs in settings.effect / residual / element blocks + +--- + +## Windsurf + +Vehicle **parents** character until dismount. Gates via SessionState. +Skills on board when staff/bow + `skillsWhileRide`. + +--- + +## Hard bans + +- ❌ Second resource bar system +- ❌ Spell costs stamina-only when kit has manaCost +- ❌ New skill list outside castingSpellKit / signatures / T0 +- ❌ Space = residual +- ❌ Second mixer for combat blends + +## Related docs + +- `SESSION_STATE_SSOT.md` · `ANIM_LIBRARY_SSOT.md` · `CASTING_10_SPELLS_SSOT.md` +- `WINDSURF_RIDE_SSOT.md` · `ISLAND_STAGE_SSOT.md` · `CASTING_LAB_SSOT.md` diff --git a/docs/ROLL_GHOST_RIDER_SSOT.md b/docs/ROLL_GHOST_RIDER_SSOT.md new file mode 100644 index 0000000..1f25c1e --- /dev/null +++ b/docs/ROLL_GHOST_RIDER_SSOT.md @@ -0,0 +1,30 @@ +# Ghost Rider rolls — clean bind (no deform) + +**Clips:** `open…/anims/baked/ghost_rider/roll_{left,right,forward,back}.json` +(Already **Bip001** rotation-only · ~0.77s · same data as `locomotion/roll_*`) + +**Code:** `CharacterController.playRoll` · `play(…, { exclusive: true })` · `bakeClip.rematchClipToSkeleton` · `DrcCombatController.roll` + +## Why rolls looked deformed + +| Cause | Fix | +|-------|-----| +| Idle/walk still weighted while roll played | **Exclusive** one-shot: fade out every other action | +| Impulse duration ≠ clip (~0.55 vs 0.77s) | Impulse **syncs to clip duration** | +| Missing `Toe0` / `Spine2` on Toon kit | Rematch Toe0→Foot, Spine2→best spine | +| Quaternion double-cover pops | `ensureQuaternionContinuity` on rematch | + +## Input + +| Key | Roll | +|-----|------| +| Ctrl+A | left | +| Ctrl+D | right | +| Ctrl+W | forward | +| Ctrl+S | back | + +## Agent rules + +- Do not invent a second mixer for rolls +- Prefer Ghost Rider pack first (already Bip001) — no Mixamo retarget at runtime +- Keep root motion as DRC impulse; clip is rotation-only (by design) diff --git a/docs/SESSION_STATE_SSOT.md b/docs/SESSION_STATE_SSOT.md new file mode 100644 index 0000000..36933fe --- /dev/null +++ b/docs/SESSION_STATE_SSOT.md @@ -0,0 +1,82 @@ +# Session state SSOT — Casting lab + +**Code:** `src/core/SessionState.js` +**Owner:** `App` constructs one instance and applies side effects on `change`. + +--- + +## Two layers (do not collapse) + +| Layer | Purpose | Source of truth | +|-------|---------|-----------------| +| **Tweaks** | Speeds, colors, VFX knobs, walk physics numbers | `settings.js` (lil-gui, presets) | +| **Session** | Interaction mode, combat/equip, ride phase, element, gates | `SessionState` | + +Presets patch **settings** only. After import, call `session.syncFromSettings()`. + +--- + +## Fields + +| Field | Values | Who writes | +|-------|--------|------------| +| `mode` | `casting` \| `walk` | App / M key / HUD | +| `drc` | `combat` \| `equip` | DrcCombatController / Q | +| `ridePhase` | `idle` \| `leap` \| `ride` \| `freeride` \| `dismount` | WalkController only | +| `element` | `fire` \| `water` \| `earth` \| `wind` | Ability select / digits | +| `paused` | bool | App P key | + +--- + +## Gates (read these — no ad-hoc chains) + +```js +session.gates.landLoco // WASD land combat +session.gates.combatSkills // 1–4 / F +session.gates.pathIsCast // stroke → AbilityManager +session.gates.pathIsRide // stroke → WalkController +session.gates.freerideDeploy +session.gates.rideParented +session.gates.combatKeys +session.gates.tpsCamera +session.gates.orbitCamera +session.gates.inventoryOk +``` + +--- + +## Pattern + +``` +1. User input / controller event +2. session.setX(...) OR walk reports setRidePhase +3. session emits 'change' (snapshot, prev, reason) +4. App._onSessionChange applies camera, inventory, HUD, combat keys once +5. Controllers in update() read session.gates / session.riding +``` + +### Hard bans + +- ❌ Second copy of mode/session on App + HUD + DRC without sync +- ❌ Walk writing camera/inventory directly +- ❌ DRC checking `walk.phase` string without session +- ❌ Inventing Redux/Zustand — EventEmitter + snapshot is enough + +--- + +## Controllers + +| System | Owns | Reports to session | +|--------|------|-------------------| +| WalkController | leap/ride/freeride/dismount machine | `setRidePhase` | +| DrcCombatController | stamina, CDs, land move | `setDrc` via setSession | +| AbilityManager | cast pools | element via App.selectElement → session | +| CharacterController | mesh, mixer, ride parent flags | (flags only; no session write) | + +--- + +## Related + +- `docs/WINDSURF_RIDE_SSOT.md` — ride parenting + phases +- `docs/CASTING_LAB_SSOT.md` — lab macro +- `settings.js` — tweak knobs diff --git a/docs/SKILL_AUTHORING_STUDIO_SSOT.md b/docs/SKILL_AUTHORING_STUDIO_SSOT.md new file mode 100644 index 0000000..59a5758 --- /dev/null +++ b/docs/SKILL_AUTHORING_STUDIO_SSOT.md @@ -0,0 +1,108 @@ +# Skill authoring + VFX Studio SSOT + +**Host:** https://casting.grudge.studio · toggle **V** / HUD **VFX** +**Shell:** `src/ui/vfxStudio/VfxStudio.js` · **SSOT map:** `src/config/skillAuthoringSSOT.js` + +Singular container. No second combat engine, no R3F, no parallel lil-gui floating outside the shell when studio is open. + +--- + +## uMMORPG → Casting map + +| uMMORPG / Unity idea | Casting lab | +|----------------------|-------------| +| ScriptableObject Skill | Catalog skill row (master-weaponSkills / T0 / production package) | +| Skill.cooldown / mana / castTime | `cooldownSec` · mana · cast time fields | +| Target type (self / enemy / area) | soft-lock · aim · ground ring · self | +| ProjectileSkill / AoESkill / Buff | `SkillDeliveryPattern` (`skillDelivery.js`) | +| Effect modules on skill | `EffectPrimitive[]` (`effectPrefab.js`) | +| Prefab / VFX reference | VfxDirector `effectId` + `meshId` + settings knobs | +| Runtime activation only | DrcCombatController · projectiles · residual · abilities | + +**Rule:** Catalog invents skill **ids**. Studio authors **delivery + VFX + knobs + export**. Never invent skill ids in the editor. + +--- + +## Authoring pipeline (tabs) + +``` +Pipeline → Skill → Delivery → VFX → Linear → Samples → Knobs → Export +``` + +| Tab | Owns | +|-----|------| +| **Pipeline** | Process map (uMMORPG ↔ Casting) | +| **Skill** | Scriptable draft fields (pull active bar skill) | +| **Delivery** | weapon · linear · over · under · around · aura · path | +| **VFX** | Primitives + VFX_CATALOG preview (sandbox effectIds) | +| **Linear** | Ice / thunder / meteor / beam / snare / glacier families | +| **Samples** | Mesh/texture swatches → `settings.effect.meshId` + color | +| **Knobs** | Existing lil-gui Editor docked (settings.js live) | +| **Export** | EffectPrefab JSON for client / Warlords | + +--- + +## File map + +| Path | Role | +|------|------| +| `src/ui/vfxStudio/VfxStudio.js` | Shell + tabs | +| `src/ui/vfxStudio/vfxStudio.css` | UI chrome | +| `src/config/skillAuthoringSSOT.js` | Tabs · delivery groups · samples · field template | +| `src/ui/Editor.js` | Live knobs (hosted in Knobs tab) | +| `src/vfx/effectPrefab.js` | Primitives + export | +| `src/vfx/vfxCatalog.js` | effectId catalog | +| `src/combat/skillDelivery.js` | Delivery patterns | +| `src/config/settings.js` | Live runtime knobs | + +--- + +## Delivery groups (user language) + +| Group | Patterns | +|-------|----------| +| Weapon | `weapon` | +| Linear | `caster_to_target` | +| Over | `over_target` | +| Under | `under_target` | +| Around | `around_caster` · `around_target` | +| Aura | `toggle_aura` | +| Place | `at_location` | +| Path | `path_stream` · `path_aoe` · `path_spikes` · `path_wall` | + +--- + +## Export contract + +```json +{ + "source": "casting-lab-vfx-studio", + "version": "1.1.0", + "skillDraft": { "id": "…", "delivery": "caster_to_target" }, + "prefabs": [ + { + "id": "prefab_…", + "primitives": [{ "kind": "travel", "intensity": 1, "meshId": "orb-fire" }] + } + ] +} +``` + +Ship JSON into Open / Warlords skill VFX arrays — do not re-author a second VFX engine client-side. + +--- + +## Hard bans + +- ❌ Second skill scripting runtime in Casting +- ❌ Second AnimationMixer for VFX +- ❌ Free-floating second editor UI outside VfxStudio +- ❌ Invented skill ids not in catalog +- ❌ Whole fireball.glb as orb travel + +--- + +## Related + +- `docs/CASTING_LAB_SSOT.md` · `docs/SKILL_DELIVERY_SSOT.md` · `docs/ENGINE_DEPLOY_SSOT.md` +- `docs/WEAPON_SKILL_PRODUCTION_SSOT.md` diff --git a/docs/SKILL_DELIVERY_SSOT.md b/docs/SKILL_DELIVERY_SSOT.md new file mode 100644 index 0000000..5023fd5 --- /dev/null +++ b/docs/SKILL_DELIVERY_SSOT.md @@ -0,0 +1,84 @@ +# Skill delivery · projectiles · physics (Casting lab) + +**Code:** `src/combat/skillDelivery.js` · `SkillProjectileSystem.js` · `hitReaction.js` · `DrcCombatController` +**Catalog skills:** `docs/WEAPON_SKILLS_API_SSOT.md` (never invent skill rows) +**Meshes:** `public/models/vfx/summons/summon-fire-fist.glb` · `summon-ice-shard.glb` +**Hit anim:** `public/anims/baked/reactions/knocked-up.json` + +--- + +## Rule: catalog first, delivery second + +| Layer | Source | Invent? | +|-------|--------|---------| +| Skill id / dmg / CD / cast / effects | `t0-weapons` · `master-weaponSkills` | **No** | +| Delivery pattern | Inferred from catalog text + style + pathMode | Heuristic only | +| Projectile mesh | Element → orbs / rocks / arrows / summons (not whole multipack) | Extract only | +| Freeze / earth / arrows | `elementAttackVfx.js` · see `ELEMENT_ATTACK_MESHES_SSOT.md` | Heuristic only | +| Contact force / knockback | SI profile by pattern | Tunable knobs | +| Hit reaction | `knocked-up` bake + MM impulse | Clip from author FBX | + +--- + +## Delivery patterns + +| Pattern | Spawn | Travel | Result | +|---------|-------|--------|--------| +| **weapon** | Weapon tip | Short residual path | Melee residual / Getsuga | +| **caster_to_target** | Cast origin (hand) | Projectile to aim / soft-lock | Impact + force | +| **over_target** | Sky above aim/target | Drop with gravity | Explosion + knockup | +| **under_target** | Ground under aim | Erupt up | Knockup heavy | +| **around_caster** | Caster feet | Pulse ring | AoE force | +| **around_target** | Target feet | Pulse ring | AoE force | +| **at_location** | Ground aim | Pulse | Zone | +| **toggle_aura** | Caster | No travel | Buff/ward on/off | +| **path_stream / aoe / spikes / wall** | Path stroke | Ability path | Existing path cast | + +Infer: `inferDeliveryPattern(skill)` · resolve: `resolveDeliveryPose(pattern, pose)`. + +--- + +## Projectiles (clear rules) + +1. **Mesh** — SI-scaled extract only (`summon-fire-fist` / `summon-ice-shard`). Never load `fire__ice_____rtj4d.glb` whole. +2. **Motion** — kinematic velocity + optional gravity (over_target). +3. **Collider** — contact **sphere** radius ≈ `0.55 × size` (m). Distance vs targets. +4. **On contact** — explode VFX · `onHit` → knockback MM + optional `vy` · hit reaction anim. +5. **Force** — `force` (impulse scale) + `knockbackMm` (100 MM = 1 m body slide) + `knockupVy` (m/s). +6. **Life** — despawn after `life` s or first hit. +7. **Targets** — soft-lock hostile + aim point (lab). + +Physics: **one Rapier CCT** for hero. Projectiles are **not** a second full physics engine; they use spheres + impulse into existing CCT dodge channel. + +--- + +## Holy + +- School still catalog `staff_holy_*` / holy element. +- Delivery often **over_target** (smite / light) or **caster_to_target**. +- Mesh: fire-fist tinted gold/emissive until dedicated holy mesh ships. +- VFX: `moon_beam` cast/travel/impact from staff school bind. + +--- + +## Assets provenance + +| Asset | Role | +|-------|------| +| `Documents/fire__ice_____rtj4d.glb` | Author multipack (Fist_Rock / Gun_Rock) — **source only** | +| `summon-fire-fist.glb` | Fire / holy projectile (~0.55 m) | +| `summon-ice-shard.glb` | Ice / frost projectile (~0.5 m) | +| `Documents/knocked up.fbx` | Mixamo hit launch — baked `reactions/knocked-up.json` | + +--- + +## Agent checklist + +``` +[ ] Skill numbers from catalog only +[ ] Delivery pattern documented / inferred, not a second skill DB +[ ] No whole multipack load for projectiles +[ ] SI scale on summons +[ ] Contact sphere + knockback MM + hitReact clip +[ ] One Rapier world / one hero mixer +``` diff --git a/docs/SSOT_INDEX.md b/docs/SSOT_INDEX.md new file mode 100644 index 0000000..b887907 --- /dev/null +++ b/docs/SSOT_INDEX.md @@ -0,0 +1,26 @@ +# Casting Lab · SSOT index + +Master index for agents. Prefer leaf docs; do not invent parallel folders. + +| Domain | Doc | +|--------|-----| +| Lab macro | CASTING_LAB_SSOT | +| **SDK export** | **CASTING_SDK_EXPORT_SSOT** · `src/sdk/castingLabSdk.js` | +| Deploy / DNS / env | CASTING_DEPLOY_ENV_SSOT · TRAINING_ROOM_DEPLOY | +| **T0 weapon play stack** | **T0_WEAPON_PLAY_STACK** · agent skill `casting-t0-weapon-play` | +| Skills production | WEAPON_SKILL_PRODUCTION · SKILL_DELIVERY · ELEMENTAL_LINEAR_CAST | +| Staff orbs / charge | STAFF_NORMAL_ORBS_CHARGE · ELEMENT_ATTACK_MESHES | +| Linear skillshots | LINEAR_SKILLSHOT_SSOT | +| Terrain L0–L2 | THREE_LAYER_TERRAIN · TERRAIN_PHYSICS · ISLAND_STAGE | +| Loaders | LOADER_DRACO_KTX2_AUDIT | +| Systems health | SYSTEMS_HEALTH_AUDIT | +| Entry / fleet review | FLEET_ENTRY_DEPLOY_LOADERS_RAYCAST_REVIEW | +| Character / anim | ANIM_LIBRARY · PRODUCTION_CONTROLLER · grudge6 | +| Pistol / flintlock | PISTOL_ANIM · **PISTOL_FLINTLOCK_SSOT** | +| T0 weapons | T0_WEAPONS · T0_STARTERS · T0_APPRENTICE_WAND · T0_TRAINING_SWORD | +| UI | CRAFTPIX_HUD · MAIN_PANEL_INVENTORY · COMBAT_CAMERA_FOCUS | +| Ride | WINDSURF_RIDE_SSOT (if present) · JUMP_AIR · ROLL | +| Prefabs | GAME_ITEM_PREFAB · PREFAB_SCAFFOLD · WEAPON_* | +| DevNode | DEVNODE_SSOT · TRAINING_ROOM | + +**Contract URL (live after deploy):** `/api/v1/casting-lab-contract.json` diff --git a/docs/STAFF_NORMAL_ORBS_CHARGE_SSOT.md b/docs/STAFF_NORMAL_ORBS_CHARGE_SSOT.md new file mode 100644 index 0000000..942d082 --- /dev/null +++ b/docs/STAFF_NORMAL_ORBS_CHARGE_SSOT.md @@ -0,0 +1,76 @@ +# Staff normal attack · orbs · charge SSOT + +**Lab:** casting.grudge-studio.com / CastingAbilitiesThreeJS +**Skills:** `grudge-vfx-orbs-strike` · `casting-warlords-lab` + +## Contract + +| Input | Behavior | +|-------|----------| +| **Hotbar 1** | Staff **normal attack** (primary / slot index 0) for **all** weapon staffs | +| **Focus + LMB** | Fires that same slot-1 normal (not melee residual) | +| **Digit 1** | Same as hotbar 1 | +| **F** | Weapon primary (same skill when equipped staff/wand) | + +Catalog skill **ids** stay per-weapon (Practice Bolt, Practice Root, Fire Bolt, …). +**Delivery** is shared: charge shell → elemental orb stream → impact. + +## Assets (split — never multipack as projectile) + +| Role | Path | Source | +|------|------|--------| +| Fire orb | `public/models/vfx/orbs/orb-fire.glb` | `gd_orbs_pack.glb` Sphere_0 + Torus_1 | +| Ice | `…/orb-ice.glb` | Sphere.001 + Torus.001 | +| Nature | `…/orb-nature.glb` | Sphere.002 + Torus.002 | +| Storm | `…/orb-storm.glb` | Sphere.003 + Torus.003 | +| Holy | `…/orb-holy.glb` | Sphere.004 + Torus.004 | +| Arcane | `…/orb-arcane.glb` | Sphere.005 + Torus.005 | +| Charge shell | `public/models/vfx/charge/staff-charge.glb` | `kamehameha_charging.glb` (SI ~0.35 m) | + +**Regen:** `node scripts/split-gd-orbs-and-charge.mjs` +Author materials are black — split bake + runtime `applyElementalOrbMaterials` apply element colors/emissive. + +**Do not** load `gd_orbs_pack_src.glb` or whole fireball.glb as a staff bolt. + +## Code map + +| Piece | File | +|-------|------| +| Orb / charge SSOT | `src/vfx/staffOrbVfx.js` | +| Element → mesh | `CASTING_ELEMENT_PHASE_VFX` · `projectileMesh` / `projectileOrb` | +| Staff bind | `src/combat/staffWeaponSkillsBind.js` | +| Delivery mesh pick | `src/combat/skillDelivery.js` `resolveSkillProjectileMesh` | +| Projectile + charge | `src/combat/SkillProjectileSystem.js` | +| Cast charge + LMB | `DrcCombatController` · `App._onLmbAttack` | +| Prefab mesh ids | `src/vfx/effectPrefab.js` | + +## Per-staff management + +Each staff attack owns: + +1. **Element** (school from catalog) → orb id +2. **Materials** tint/emissive on clone +3. **Charge** at tip during cast bar (additive pulse) +4. **Travel** mesh projectile with contact sphere +5. **Impact** existing VfxDirector effect ids + +Signatures / AoE / wards keep pathMode presentation (wall/aoe/spikes) — only **stream primary** is the shared normal. + +## Smoke + +1. Equip Apprentice Wand or Sapling Staff (I → Weapon) +2. Combat **Q** · **RMB focus** +3. **LMB** → cast charge at tip → colored orb flies to aim +4. **1** same as LMB for staff +5. Switch fire/ice/nature staffs → different orb colors + +## Checklist + +``` +[ ] No whole gd_orbs_pack / fireball as projectile +[ ] Orbs under models/vfx/orbs/orb-*.glb +[ ] Charge under models/vfx/charge/staff-charge.glb +[ ] Focus LMB = useSkill(0) for staff/wand +[ ] Slot 1 primary uses orb + charge +[ ] SI orb ~0.45 m · charge ~0.35 m +``` diff --git a/docs/SYSTEMS_HEALTH_AUDIT.md b/docs/SYSTEMS_HEALTH_AUDIT.md new file mode 100644 index 0000000..8969846 --- /dev/null +++ b/docs/SYSTEMS_HEALTH_AUDIT.md @@ -0,0 +1,116 @@ +# Systems health audit — conflicts · organization · forgetting + +**Scope:** Casting lab + fleet touchpoints (2026-08) +**Action:** audit + fix bare loaders, override wire, single decoder pins + +--- + +## 1. Conflict matrix + +| Pair | Status | Notes | +|------|--------|-------| +| Draco vs KTX2 | **OK** | Different glTF extensions — not mutually exclusive | +| Draco vs Meshopt | **OK** | Can coexist on one asset | +| Dual DRACOLoader (AssetLoader + projectiles) | **Fixed** | Projectiles → `sharedGltfLoader()` | +| Bare `GLTFLoader` (weapon / back / devnode) | **Fixed** | Now shared pipeline | +| Dual DRACO path strings (assets.js vs gltfPipeline) | **Fixed** | assets re-exports pipeline | +| Open vs Casting decoder pins | **Aligned** | versioned Draco 1.5.7 · basis 0.185.1 | +| Dual heightmap (feet vs aim) | **OK** | One `IslandHeightfield.sample` | +| Dual AnimationMixer | **OK** | One on CharacterController | +| Path Ability + linear castToward | **OK** | Planned layers (elementalLinearCast) | +| Production skill + legacy skillDefToDrc | **OK** | Production first, legacy fallback | +| SkillStatusSystem + ad-hoc freeze | **OK** | Hits go through statuses | +| Ground.js + IslandHeightfield | **OK** | Flat ground hidden when heightfield on | +| `mountTerrainLayers` vs App manual mount | **Fixed** | App uses `mountTerrainLayers` for L2 | + +--- + +## 2. Organization map (correct ownership) + +| Concern | Owner | Do not | +|---------|-------|--------| +| glTF decode | `loaders/gltfPipeline.js` | `new GLTFLoader()` elsewhere | +| Asset boot | `AssetLoader` + `bindRenderer` | Skip KTX2 bind | +| Height L0 | `IslandHeightfield.heightAt` | Second FBM in grass | +| Ground L1 | heightfield mesh | Raise Ground.js verts for hills | +| Vegetation L2 | StylizedGrass + GrowingForest | Own heightmap | +| Aim / path | `terrainGround.projectToTerrain` | Plane-only when heightfield on | +| Catalog skills | master-weaponSkills / t0 | Invent skill ids | +| Skill package | `weaponSkillProduction` | Parallel combat DB | +| Statuses | `skillStatusSystem` | Random `userData` without status sys | +| Linear spells | `LinearSkillBridge` + plan | Third AbilityManager | +| Entry hosts | Open `entryCatch` PRODUCT_STARTS | Guessed deep-links | + +--- + +## 3. Fixed this pass + +| Item | Change | +|------|--------| +| WeaponMeshAttach | `sharedGltfLoader()` | +| BackSlotEquip | `sharedGltfLoader()` | +| DevNodeEditor | `sharedGltfLoader()` | +| Decoder pins | single export from gltfPipeline | +| Production overrides | `warmProductionOverrides` on equip → cache in skillDefToDrc | + +--- + +## 4. Remaining soft gaps (not blockers) + +| Gap | Severity | Note | +|-----|----------|------| +| `mountTerrainLayers` unused by App | Low | App duplicates wiring; could consolidate later | +| Hostile full HP bar from damage | Med | Status package ready; host HP later | +| Infinite terrain stream | Low | Island pad only | +| Self-host draco/basis under public/ | Low | CDN ok | +| CI: load one Draco GLB smoke | Med | Recommended | +| Open still needs `bindKtx2` at Studio boot | Check | already had API — ensure called | + +--- + +## 5. Forget checklist (before “systems done”) + +``` +[x] One Draco pool +[x] KTX2 after renderer +[x] Weapons/back/projectiles use shared loader +[x] One height sample +[x] One mixer +[x] Production skill compile on equip +[x] Overrides warm on equip +[x] Statuses on projectile hit +[x] Linear plan on spell release +[x] Entry via PRODUCT_STARTS (Open) +[ ] Optional: App uses mountTerrainLayers only +[ ] Optional: CI decoder smoke +``` + +--- + +## 6. Quick health commands + +```bash +# Casting +cd CastingAbilitiesThreeJS +npm run build +# grep remaining bare loaders: +# new GLTFLoader should only appear in gltfPipeline.js + +# Open entry +cd gameopen/artifacts/animator +npx vitest run src/lib/entryCatch.test.ts +``` + +--- + +## 7. Verdict + +| Area | Health | +|------|--------| +| Loaders | **Healthy** after shared pipeline + bare-loader fix | +| Terrain layers | **Healthy** — one height; App manual mount is fine | +| Skills / statuses / linear | **Healthy** — one plan path | +| Entry / deploy | **Healthy** — Open SSOT | +| Overrides | **Healthy** — warm on equip | + +**No hard system conflicts remaining** in Casting after this pass. Soft debt is consolidation/CI only. diff --git a/docs/T0_APPRENTICE_WAND_SSOT.md b/docs/T0_APPRENTICE_WAND_SSOT.md new file mode 100644 index 0000000..d5b7902 --- /dev/null +++ b/docs/T0_APPRENTICE_WAND_SSOT.md @@ -0,0 +1,51 @@ +# T0 Apprentice Wand — three-slot starter + +**Weapon:** `t0-wand` · Apprentice Wand +**Catalog SSOT:** [WEAPON_SKILLS.html](https://info.grudge-studio.com/WEAPON_SKILLS.html) · [t0-weapons.json](https://info.grudge-studio.com/api/v1/t0-weapons.json) +**Lab import:** `src/api/t0WeaponCatalog.js` (preferred) · local `t0ApprenticeWand.js` only as offline mirror +**Pair:** Sapling Staff `t0-nature-staff` — see `docs/T0_STARTERS_WEAPON_SKILLS_SSOT.md` + +## Slots (product) + +| Slot | Mode | Skill | Notes | +|------|------|-------|--------| +| **1 · Starter Attack** | Auto · fixed | **Practice Bolt** | Arcane · DMG 14 · Cast 0.5s · CD short | +| **2 · Starter Style** | Auto · fixed | **Focus** | Channel · CD 5s · Cast 0.3s · **+spell dmg 3s** | +| **3 · Choose One** | Choose 1 | **Frost Spark** (default) *or* **Arcane Ping** | Frost 12 / Arcane 10 | + +### Slot 3 options + +| Skill | Type | DMG | CD | Cast | Effect | +|-------|------|-----|----|------|--------| +| Frost Spark | frost | 12 | 4s | 0.6s | Slow 1s | +| Arcane Ping | arcane | 10 | 3s | 0.4s | Low mana | + +## Lab binds + +| Skill | Ability path | Cast → Travel → Impact | +|-------|--------------|------------------------| +| Practice Bolt | wind (arcane beauty) | arcane_swirl → chain_lightning → arcane_swirl | +| Focus | buff only | arcane_swirl hand tell | +| Frost Spark | water | … → moon_beam → frost_wave | +| Arcane Ping | wind aoe | arcane_swirl pulse | + +Anim: **magic** pack · `standing 1h cast spell 01` · role **cast**. + +## Lab UX + +1. Combat **Q** +2. **I → Skills → Apprentice Wand bar** (or URL `?wand=1`) +3. Pick Slot 3: Frost Spark / Arcane Ping +4. **1** Practice Bolt · **2** Focus · **3** choice +5. Focus then Bolt → toast **FOCUSED** + boosted intensity + +## Pattern + +Same **three-slot-starter** as other T0 weapons (fixed attack · fixed style · choose ability). +Branches at T1 into fire / frost / holy / arcane / lightning staves (item description). + +## Related + +- `docs/CASTING_10_SPELLS_SSOT.md` (staff learning kit) +- `src/api/weaponSkillsCatalog.js` +- master-weaponSkills `WAND.starterSlots` diff --git a/docs/T0_STARTERS_WEAPON_SKILLS_SSOT.md b/docs/T0_STARTERS_WEAPON_SKILLS_SSOT.md new file mode 100644 index 0000000..9af1480 --- /dev/null +++ b/docs/T0_STARTERS_WEAPON_SKILLS_SSOT.md @@ -0,0 +1,73 @@ +# T0 starters — import from WEAPON_SKILLS only + +**Browse:** https://info.grudge-studio.com/WEAPON_SKILLS.html +**JSON:** https://info.grudge-studio.com/api/v1/t0-weapons.json +**Lab:** `src/api/t0WeaponCatalog.js` · Inventory **Weapon** tab + +Do **not** invent skill rows. Skills and stats come from ObjectStore / info host. + +## Two T0 magic starters (now) + +| Catalog id | Name | Weapon type | Skills | +|------------|------|-------------|--------| +| **t0-wand** | Apprentice Wand | WAND | Practice Bolt · Focus · Frost Spark \| Arcane Ping | +| **t0-nature-staff** | Sapling Staff | STAFF (nature) | Practice Root · Nature Ward · Vine Lash \| Healing Sprout | + +**Later:** Mage Wand (class item) — not this pass. + +--- + +### Apprentice Wand (`t0-wand`) + +- DMG **14** · CRIT **1%** · DEF **3** +- Slot 1 **Practice Bolt** — arcane · DMG 14 · Cast 0.5s · fixed +- Slot 2 **Focus** — CD 5s · Cast 0.3s · **+spell dmg 3s** · fixed +- Slot 3 **Frost Spark** (default) or **Arcane Ping** + - Frost Spark: frost · 12 · CD 4s · Cast 0.6s · Slow 1s + - Arcane Ping: arcane · 10 · CD 3s · Cast 0.4s · Low mana +- Branches T1 → fire / frost / holy / arcane / lightning staves + +### Sapling Staff (`t0-nature-staff`) + +- DMG **16** · CRIT **1%** · DEF **5** +- Slot 1 **Practice Root** — nature · DMG 16 · Cast 0.5s · fixed +- Slot 2 **Nature Ward** — CD 6s · Cast 0.4s · **+defense 2s** · fixed +- Slot 3 **Vine Lash** (default) or **Healing Sprout** + - Vine Lash: nature · 14 · CD 4s · Cast 0.6s · Root chance + - Healing Sprout: Heal 12 · CD 8s · Cast 1s · Self heal + +--- + +## Lab flow + +1. Open https://casting.grudge-studio.com +2. **I → Weapon** (loads `t0-weapons.json` live) +3. Equip **Apprentice Wand** or **Sapling Staff** +4. Combat **Q** · **1 / 2 / 3** = catalog slots +5. **F** = weapon **primary** skill (slot 1 / starter attack) — same cast bar + prefab path as digits +6. Slot 3: click alternate skill on list + +**Not F:** residual Getsuga default · class abilities (later). +**F resolve:** Showcase bind `f` → equipped weapon slot 0 → active tree slot 0. + +URL shortcuts: `?wand=1` · equip path always preferred. + +## Code map + +| Concern | File | +|---------|------| +| Live import | `t0WeaponCatalog.loadEquippableWeapons` | +| Skill → DRC + VFX | `skillDefToDrc` + `bindFromCatalogSkill` | +| Equip | `equippedWeaponRuntime.js` | +| Hand mesh | `WeaponMeshAttach.js` (CDN model when prefab has modelUrl) | + +## Assets (meshes) + +| Catalog id | CDN mesh (R2) | Source author | +|------------|---------------|---------------| +| **t0-wand** | `prod/gltf/weapons/t0-wand.glb` | arcane_staff_of_resonance | +| **t0-nature-staff** | `prod/gltf/weapons/t0-nature-staff.glb` | mushroom_staff | +| family | `prod/gltf/weapons/wand.glb` (alias of apprentice) | same | + +Skills import from catalog; equip uses these URLs via `T0_MODEL_CDN` + prefab patch. +Mesh nodes stay separate for later slot tint (mushroom colors). SI hand scale in `WeaponMeshAttach`. diff --git a/docs/T0_TRAINING_SWORD_SSOT.md b/docs/T0_TRAINING_SWORD_SSOT.md new file mode 100644 index 0000000..a50ecf0 --- /dev/null +++ b/docs/T0_TRAINING_SWORD_SSOT.md @@ -0,0 +1,43 @@ +# Training Sword T0 — Casting lab + +**Canon:** [WEAPON_SKILLS](https://grudge-objectstore.pages.dev/WEAPON_SKILLS) · `api/v1/t0-weapons.json` id `t0-sword` +**Code:** `t0WeaponCatalog.js` · `equippedWeaponRuntime.js` · `hotbarForWeapon` · `drcSkills` tree `equipped` + +## Product slots (production) + +| Slot | Key | Skill | Notes | +|------|-----|-------|--------| +| 1 | **1** | **Practice Slash** | Auto · light melee · `attack1` / combo | +| 2 | **2** | **Guard Stance** | Auto · block · −15% dmg 2s · `block` | +| 3 | **3** | **Choose one** | Quick Thrust **or** Wide Sweep (player picks one) | + +Craft T1 to unlock five-slot loadout. T0 has **no** tier upgrades. + +### Slot 3 options + +| Choice | DMG | CD | Anim role | Residual | +|--------|-----|----|-----------|----------| +| Quick Thrust | 16 | 3s | `attack3` | Reach ~3.6 m | +| Wide Sweep | 14 | 4s | `attack2` | Wider AoE residual | + +## No invented 4th skill + +T0 production is **3 slots only**. Do not add lab-only skill ids to the hotbar. +Keys **1–3** = catalog skills. LMB free attack = combo / finisher (anim layer, not a new skill row). + +## Lab entry + +- Inventory **Prefabs / Weapons** → equip Training Sword +- URL `?sword=1` → equip `t0-sword` + skill tree `equipped` +- Keys **1–3** fire hotbar + +## Anim bind only (not new skills) + +| Catalog skill | Anim role (presentation) | +|---------------|--------------------------| +| Practice Slash | `attack1` | +| Guard Stance | `block` | +| Quick Thrust | `attack3` | +| Wide Sweep | `attack2` | + +Numbers stay from JSON. See `docs/WEAPON_SKILLS_API_SSOT.md`. diff --git a/docs/T0_WEAPONS_SSOT.md b/docs/T0_WEAPONS_SSOT.md new file mode 100644 index 0000000..04350bf --- /dev/null +++ b/docs/T0_WEAPONS_SSOT.md @@ -0,0 +1,74 @@ +# T0 weapons — complete lab matrix + +**SSOT JSON:** https://info.grudge-studio.com/api/v1/t0-weapons.json +**Browse:** https://grudge-objectstore.pages.dev/WEAPON_SKILLS +**Code:** `t0WeaponCatalog.js` · `equippedWeaponRuntime.js` · `hotbarForWeapon` · `applyT0Presentation` +**Rule:** Catalog skills only — no invented skill rows. Anim roles = presentation. + +**Deferred (not T0):** water traversal · climb · CC recovery · farm/harvest anim loops. + +--- + +## Equip in lab + +| Entry | Action | +|-------|--------| +| Inventory → Prefabs / T0 | Click equip on any starter | +| `?t0=t0-axe1h` | Equip that id | +| `?sword=1` | `t0-sword` | +| `?wand=1` | `t0-wand` | +| `?sapling=1` | `t0-nature-staff` | + +Keys **1–3** fire hotbar. Slot 3 = chosen option (Inventory skill list). + +--- + +## All 15 starters + +| Id | Name | Pack | Slot 1 | Slot 2 | Slot 3 choices | +|----|------|------|--------|--------|----------------| +| `t0-sword` | Training Sword | sword_shield | Practice Slash | Guard Stance | Quick Thrust · Wide Sweep | +| `t0-axe1h` | Hand Axe | sword_shield | Practice Chop | Wind-Up | Cleaving Swing · Heavy Blow | +| `t0-dagger` | Training Dagger | sword_shield | Practice Stab | Evade Step | Backstab · Poison Scratch | +| `t0-hammer1h` | Training Hammer | sword_shield | Practice Smash | Brace | Shockwave · Stagger Blow | +| `t0-spear` | Hunting Spear | sword_shield | Practice Thrust | Pole Guard | Reach Strike · Sweeping Jab | +| `t0-greatsword` | Training Greatsword | sword_shield | Practice Cleave | Power Stance | Overhead Swing · Spinning Cut | +| `t0-greataxe` | Training Greataxe | sword_shield | Practice Hew | Lumber Stance | Wide Arc · Crushing Blow | +| `t0-hammer2h` | Training Warhammer | sword_shield | Practice Smash | Brace | Shockwave · Stagger Blow | +| `t0-bow` | Short Bow | longbow | Practice Shot | Take Aim | Pinning Arrow · Rapid Fire | +| `t0-crossbow` | Light Crossbow | longbow | Practice Bolt | Reload | Piercing Bolt · Knockback Shot | +| `t0-gun` | Flintlock Pistol | pistol | Practice Shot | Take Cover | Burst Fire · Suppressing Shot | +| `t0-wand` | Apprentice Wand | magic | Practice Bolt | Focus | Frost Spark · Arcane Ping | +| `t0-nature-staff` | Sapling Staff | magic | Practice Root | Nature Ward | Vine Lash · Healing Sprout | +| `t0-tool` | Crude Tool | sword_shield | Chop | Mine | Skin · Pry | +| `t0-offhand-tome` | Novice Tome | magic | Practice Cantrip | Read Page | Minor Heal · Ember Spark · Frost Wisp | + +Numbers (dmg / CD / cast / effects) = live JSON only. + +--- + +## Presentation map (anim only) + +| Kind | Anim role | +|------|-----------| +| Practice / starter melee | `attack1` | +| AoE / sweep / shockwave | `attack2` | +| Heavy / overhead / backstab / reach | `attack3` | +| Guard / brace / ward / cover / stance | `block` (or `cast` on magic) | +| Focus / aim / wind-up / power / read | buff + next-hit mul | +| Evade Step | `dodgeB` | +| Ranged shots | `attack` (longbow / **pistol** pack) | +| Take Cover (gun) | `block` + ward −dmg taken 2s | +| Burst Fire | multiHit **3** · bullets staggered | +| Spells / heals | `cast` | + +Delivery: melee residual · ranged projectile · spell path/element · see `SKILL_DELIVERY_SSOT.md`. + +--- + +## After T0 (parked) + +1. Water locomotion +2. Climb +3. Recover from loss of control +4. Farming / harvesting animation loops (tool skills already catalog-ready; full harvest system later) diff --git a/docs/T0_WEAPON_PLAY_STACK_SSOT.md b/docs/T0_WEAPON_PLAY_STACK_SSOT.md new file mode 100644 index 0000000..945dae0 --- /dev/null +++ b/docs/T0_WEAPON_PLAY_STACK_SSOT.md @@ -0,0 +1,152 @@ +# T0 weapon play stack — full production pipeline + +**Product language:** we ship **weapons**, not free “elements.” +Hotbar **1–6** staff brands (`fire`…`arcane`) are **staff weapons** with path-cast pools under the hood. +Catalog weapons (`t0-sword`, `t0-gun`, …) own skills, anim packs, meshes, and projectiles. + +**Lab:** CastingAbilitiesThreeJS · **Live:** casting.grudge.studio / casting-abilities-threejs.vercel.app +**Agent skill:** `casting-t0-weapon-play` (load after `casting-warlords-lab` + `grudge-studio`) + +--- + +## Non-negotiables + +| Rule | Detail | +|------|--------| +| **Catalog first** | Skill ids from `t0-weapons.json` / `master-weaponSkills` only — never invent | +| **Item owns skills** | Equip weapon → hotbar + anim pack + mesh (`equippedWeaponRuntime`) | +| **One mixer** | Bip001 packs; no second AnimationMixer | +| **SI** | metres; human ~1.8 m; knockback MM (100 = 1 m) | +| **No multipack projectiles** | Split orbs/rocks/arrows — never whole fireball / gd_orbs pack | +| **Extend, don’t fork** | `weaponSkillProduction` · `skillDelivery` · existing Ability pools | +| **Dual loadout** | Paperdoll Weapon 1 / Weapon 2 · combat **Tap Q** swap | + +--- + +## Layers every T0 weapon must fill + +``` +1. Identity t0-weapons / master-weapon-prefabs (id, name, tier, weaponType) +2. Mesh modelUrl · hand attach · kit mesh_ids exclusive slot +3. Icon iconUrl (CDN) +4. Anim pack magic | sword_shield | longbow | pistol (+ locomotion bind) +5. Locomotion idle / walk / run / jump / fall / roll / dodge (pack roles) +6. Traversal back slot / ride only when weapon-appropriate (not default T0) +7. Skills slot1 · slot2 · slot3 options (catalog) +8. Production animRole · VFX · physics · statuses (optional override JSON) +9. Projectiles delivery mesh (orb-*, arrow, bullet) SI split +10. Prefab Warlords export (equip → export JSON) +11. Deploy Vercel lab smoke · DO promote when ready +``` + +--- + +## Weapon families → packs (play) + +| Family | T0 examples | Anim pack | Primary delivery | +|--------|-------------|-----------|------------------| +| 1H melee | sword, axe, dagger, hammer | `sword_shield` | melee residual / tip | +| 2H melee | greatsword, greataxe, spear, hammer2h | `sword_shield` | residual / heavy roles | +| Bow / xbow | bow, crossbow | `longbow` | arrow projectile | +| Gun | flintlock `t0-gun` | `pistol` | bullet + chamber | +| Magic staff | wand, nature staff | `magic` | path stream / orbs / linear | +| Tool | `t0-tool` | `sword_shield` | harvest swing (activity harvest) | +| Offhand | tome | `magic` | cantrip path | + +Full 15 matrix: `docs/T0_WEAPONS_SSOT.md`. + +--- + +## Staff weapons (hotbar 1–6) vs catalog weapons + +| Concept | Product name | Code key | Runtime | +|---------|--------------|----------|---------| +| Fire / Storm / Ice / Nature / Holy / Arcane staff | **Staff weapon** | `ELEMENTS` / `WEAPON_STAFF_IDS` | Path cast Ability pools | +| Training Sword, Flintlock, … | **Catalog weapon** | `t0-*` equip | `equipWeaponById` + skills | + +Internal path pools remain `fire|water|earth|wind` (`ELEMENT_ABILITY` / `abilityKeyForWeapon`). +**Do not** rebrand code keys without a migration plan — product copy and HUD use **weapon** language. + +--- + +## Author loop — new catalog T0 weapon + +``` +1. Catalog exists on t0-weapons + prefab model/icon (ObjectStore) +2. Map weaponType → anim pack (weaponAnimPack.js) — do not invent packs lightly +3. Equip path works: equipWeaponById → mesh · pack · hotbar +4. For each skill id: + node scripts/scaffold-weapon-skill.mjs --id --weapon SWORD + Edit public/skills/production/.json (anim · VFX · physics · statuses) +5. Projectiles: public/models/vfx/orbs|arrows|… SI splits only +6. Smoke lab: I equip · combat · 1–4 · F · Tap Q dual set · focus LMB staff +7. Export Warlords prefab · promote DO when green +8. Deploy casting Vercel if code/assets changed +``` + +### Scaffold helpers + +```bash +# Per catalog skill (production override) +node scripts/scaffold-weapon-skill.mjs --id t0_sword_practice_slash --weapon SWORD + +# Full play-stack checklist manifest for one T0 weapon +node scripts/scaffold-t0-weapon-play.mjs --id t0-sword +# → docs/play-stack/t0-sword.play-stack.json +node scripts/scaffold-t0-weapon-play.mjs --list +``` + +--- + +## Author loop — new staff weapon brand (was “element”) + +Adding a **7th staff** or new brand is rare. Prefer bind new catalog skills to existing six staffs. + +If required: + +1. Add product id to `WEAPON_STAFF_IDS` / `ELEMENTS` (hotbar order) +2. `WEAPON_STAFF_META` / `ELEMENT_META` label + accent + `staffWeaponId` +3. `ELEMENT_ABILITY` pool map → fire|water|earth|wind (or new Ability class — last resort) +4. `settings.` presentation knobs if unique VFX +5. HUD action bar already maps `ELEMENTS` — smoke 1–6 +6. Linear skillshots / orbs: extend existing binds, don’t fork AbilityManager + +--- + +## Code SSOT map + +| Layer | File | +|-------|------| +| Equip runtime | `src/combat/equippedWeaponRuntime.js` | +| T0 catalog | `src/api/t0WeaponCatalog.js` | +| Production compile | `src/combat/weaponSkillProduction.js` | +| Delivery | `src/combat/skillDelivery.js` | +| Statuses | `src/combat/skillStatusSystem.js` | +| Linear cast | `src/combat/elementalLinearCast.js` | +| Path abilities | `src/abilities/*` · `AbilityManager` | +| Anim packs | `src/config/weaponAnimPack.js` · `animLibrary.js` | +| Staff product ids | `src/config/settings.js` `WEAPON_STAFF_*` | +| Dual loadout Q | `swapWeaponLoadout` · paperdoll `mainPanelSlots` | +| Projectiles VFX | `src/vfx/*` · `public/models/vfx/*` | +| Deploy | `docs/CASTING_DEPLOY_ENV_SSOT.md` · `WEAPON_SKILL_DO_SSOT.md` | + +--- + +## Deploy / promote + +| Surface | Action | +|---------|--------| +| Lab code/UI/assets | `npm run build` · Vercel `casting-abilities-threejs` | +| Skill DO | weapon-skills.grudge-studio.com · see WEAPON_SKILL_DO | +| Catalog JSON | ObjectStore / info.grudge-studio.com — agents do not invent rows | +| Smoke URL | https://casting-abilities-threejs.vercel.app/?t0=t0-sword | + +--- + +## Related docs + +- `T0_WEAPONS_SSOT.md` · `T0_STARTERS_WEAPON_SKILLS_SSOT.md` +- `WEAPON_SKILL_PRODUCTION_SSOT.md` · `SKILL_DELIVERY_SSOT.md` +- `WEAPON_EQUIP_PREFABS_SSOT.md` · `ANIM_LIBRARY_SSOT.md` +- `ELEMENTAL_LINEAR_CAST_SSOT.md` · `STAFF_NORMAL_ORBS_CHARGE_SSOT.md` +- `PISTOL_FLINTLOCK_SSOT.md` · `MAIN_PANEL_INVENTORY_SSOT.md` diff --git a/docs/TERRAIN_PHYSICS_SSOT.md b/docs/TERRAIN_PHYSICS_SSOT.md new file mode 100644 index 0000000..47195f7 --- /dev/null +++ b/docs/TERRAIN_PHYSICS_SSOT.md @@ -0,0 +1,82 @@ +# Terrain · land · water physics SSOT (Casting lab) + +**Deploy host:** casting-abilities-threejs.vercel.app / casting.grudge-studio.com +**Repo:** CastingAbilitiesThreeJS +**Map:** Training Room · DevIsland (`training_room`) — see `docs/TRAINING_ROOM_SSOT.md` + +## Learned references (patterns ported) + +| Source | What we take | +|--------|----------------| +| [snakey-locomotion](https://github.com/muratkamci/snakey-locomotion) | Single CPU `heightAt` = mesh = feet; multi-band FBM; trees on height | +| [three-stylized](https://github.com/Steve245270533/three-stylized) | **Grounds** + instanced grass wind, dirt/meadow, external surface sample | +| [threejs-examples ∞ terrain](https://simonstorlschulke.github.io/threejs-examples/?scene=0) | Infinite tile stream (later chunk L0–L2) | +| Desktop `forestoutline.html` | Instanced trees, leaf texture/sway, SI scatter, LOD | +| three.js `physics_rapier_terrain` | Float32 heights → Rapier heightfield collider | +| `grudge-rapier` skill | Fixed 1/60, CCT, heightfield not dynamic trimesh | + +**Full map:** `docs/THREE_LAYER_TERRAIN_SSOT.md` · `terrainLayers.js` · `StylizedGrassLayer.js` + +## Layers (three + water) + +| Layer | Visual | Physics | Sample API | +|-------|--------|---------|------------| +| **L0 height** | bake grid | Rapier **heightfield** | `islandTerrain.sample(x,z)` | +| **L1 ground** | `IslandHeightfield` mesh (meadow/dirt/shore) | same | same | +| **L2 vegetation** | `StylizedGrassLayer` + `GrowingForest` | none / harvest | roots on L0 | +| **L3 detail** | harvest rocks | static | L0 Y | +| **Water** | `StageWater` (waves) | Sensor + freeride Y | `water.sampleHeight` | + +**One height source** for mesh, Rapier, harvest Y, player feet, **aim, and path draw**. + +| Consumer | How | +|----------|-----| +| Rapier / feet | `PhysicsWorld` heightfield + `landHeightAt` | +| Aim / path | `terrainGround.projectToTerrain` via `this.terrain` | +| Harvest / forest / drops | `this.terrain.sample(x,z)` | + +Do **not** invent a second heightmap or ray-plane-only ground when terrain is on. + +## SI scaling + +| Knob | Default | Notes | +|------|---------|--------| +| `settings.terrain.amp` | 0.85 m | Peak hills — human ~1.8 m yardstick | +| `settings.terrain.flatCore` | 8 m | Spawn/combat pad stays flatter | +| `settings.terrain.grid` | 65 | Rapier verts | +| `WORLD.islandRadius` | mapScale | Shore blend to `waterY` | + +## Harvestables + +| System | Role | +|--------|------| +| `DevIslandHarvest` | Ore/rock/herb nodes on heightfield | +| `GrowingForest` | Trees grow 0→1; F chops when age≥0.55; regrow ~45 s | + +## Settings + +```js +settings.terrain = { + enabled: true, + seed: 17, + amp: 0.85, + segments: 96, + grid: 65, + flatCore: 8, + forestEnabled: true, + forestCount: 48 +}; +``` + +## Confirmation + +``` +[ ] Walk hills — feet follow heightfield (no float) +[ ] Path trail sits on hills (not y=0 plane under mesh) +[ ] Ground aim marker on terrain surface +[ ] Ocean freeride still samples StageWater +[ ] Harvest rocks at correct Y +[ ] Trees grow and F harvests wood +[ ] ?physicsDebug=1 shows heightfield +[ ] No second physics engine / no duplicate height APIs +``` diff --git a/docs/THREE_LAYER_TERRAIN_SSOT.md b/docs/THREE_LAYER_TERRAIN_SSOT.md new file mode 100644 index 0000000..1cd73f9 --- /dev/null +++ b/docs/THREE_LAYER_TERRAIN_SSOT.md @@ -0,0 +1,131 @@ +# Three-layer terrain SSOT + +**Lab:** CastingAbilitiesThreeJS · `src/world/terrainLayers.js` +**Also:** grudge-player-and-grass L0–L3 · `docs/TERRAIN_PHYSICS_SSOT.md` + +World texturing and locomotion share **one height field**. References teach patterns — we do **not** ship three parallel engines. + +--- + +## Learn from (patterns only) + +| Source | Take | +|--------|------| +| [snakey-locomotion](https://github.com/muratkamci/snakey-locomotion) | CPU `heightAt` = mesh = feet; multi-band FBM; trail-on-surface; reactive grass ideas; trees on height | +| [three-stylized](https://github.com/Steve245270533/three-stylized) | **Grounds** + instanced grass wind, meadow colors, external `surface` sample, dispose ownership | +| [threejs-examples scene=0](https://simonstorlschulke.github.io/threejs-examples/?scene=0) | Infinite / streaming terrain tiles (later chunk L0–L2) | +| Desktop `forestoutline.html` | Instanced procedural trees, leaf texture/sway, LOD cull, SI scatter, bark/leaf color | + +--- + +## Layers (production) + +| Layer | Name | Casting code | Physics | +|-------|------|--------------|---------| +| **L0** | Height field | `IslandHeightfield.heightAt` / `sample` | Rapier heightfield | +| **L1** | Surface ground | `IslandHeightfield.mesh` (meadow/dirt/shore vertex colors) | same mesh | +| **L2** | Vegetation | `StylizedGrassLayer` + `GrowingForest` | none on blades; trees harvest | +| **L3** | Detail | `DevIslandHarvest` rocks/ore | static props | +| **Water** | Sibling | `StageWater` | freeride / sensor | + +``` +L2 grass roots ──┐ +L2 forest feet ──┼──► L0 heightSample(x,z) ◄── player feet / aim / path +L1 mesh verts ───┘ +L3 harvest Y ────┘ +``` + +**Hard:** no second heightmap. Map load rebinds MapSurface only — controller/session keep (fleet rule). + +--- + +## How layers map to references + +### L0 — snakey + stylized terrain math + +- Multi-band FBM (rolling / medium / micro) from snakey +- `seed`, amp, flat core, shore blend +- Baked Float32 grid for Rapier (three.js physics_rapier_terrain pattern) + +### L1 — three-stylized grounds + +- Dirt/meadow/shore color lerp on vertices +- `groundColor` / floor settings +- Optional flat `Ground.js` pad when heightfield off + +### L2 — vegetation + +| Sub | Pattern | Code | +|-----|---------|------| +| Grass | three-stylized instanced blades + wind | `StylizedGrassLayer` | +| Forest | forestoutline + snakey simple trees + growth | `GrowingForest` | +| Texture world | leaf colors / bark from forestoutline CONFIG | materials on instances | + +### Streaming (later) + +- simonstorlschulke infinite terrain → chunk keys for L0+L1+L2 together +- Toroidal grass tiles from snakey (5×5) when open-world beyond island pad + +--- + +## Settings + +```js +settings.terrain = { + enabled: true, + seed: 17, + amp: 0.85, // L0 hill m + segments: 96, // L1 mesh + grid: 65, // L0 Rapier + flatCore: 8, + forestEnabled: true, // L2 trees + forestCount: 48, + grassEnabled: true, // L2 stylized grass + grassDensity: 28, + grassBladeMax: 0.55, // SI under 1.8 m human + grassWind: 0.22, + grassColorBottom: '#3d6b1a', + grassColorTop: '#a8d44a' +}; +``` + +--- + +## Mount API + +```js +import { mountTerrainLayers, TERRAIN_LEARNED } from './world/terrainLayers.js'; + +const layers = mountTerrainLayers({ + scene, + heightfield: islandTerrain, // or null → creates IslandHeightfield + forest: true, + grass: true +}); +// layers.heightSample(x,z) — only height API +// layers.grass.update(dt) +// layers.forest.update(dt) +``` + +App still constructs `IslandHeightfield` first for physics; forest/grass attach to same sample. + +--- + +## Locomotion note (snakey) + +Snakey’s procedural trail body is a **creature locomotion** study — not a second player controller. +Fleet player stays CCT + anim packs. Optional future: trail-follow NPCs / mount paths sample L0 the same way. + +--- + +## Checklist + +``` +[ ] One heightSample for feet, aim, forest, grass +[ ] L1 mesh matches L0 bake +[ ] L2 grass blades SI (~0.2–0.55 m) +[ ] L2 forest on dry land only +[ ] Water freeride still StageWater +[ ] No Babylon / no second terrain engine +[ ] Infinite stream not required for island pad smoke +``` diff --git a/docs/TRAINING_ROOM_DEPLOY_SSOT.md b/docs/TRAINING_ROOM_DEPLOY_SSOT.md new file mode 100644 index 0000000..91dc85b --- /dev/null +++ b/docs/TRAINING_ROOM_DEPLOY_SSOT.md @@ -0,0 +1,113 @@ +# Training Room production deploy (D1 · R2 · ObjectStore · Vercel) + +**Map:** `training_room` (= DevIsland = /devnode) +**Code:** `src/config/fleetEnv.js` · `src/world/trainingRoomDeploy.js` · `src/world/trainingRoomMap.js` +**Layout package:** `public/maps/training_room/layout.default.json` + +--- + +## Authority split (do not invent forks) + +| Concern | System | URL / path | +|---------|--------|------------| +| SPA (play + DevNode) | **Vercel** | casting.grudge.studio · casting-abilities-threejs.vercel.app | +| Player / bag / wallet | **Railway Postgres** via grudge-api | `/api/*` rewrite → Railway | +| Auth | Grudge ID | id.grudge-studio.com | +| Binary meshes / icons | **R2 CDN** | assets.grudge-studio.com | +| JSON catalogs / layouts | **info + ObjectStore** | info…/api/v1 · objectstore…/api | +| Asset **index** only | **D1** | search — never player SSOT | +| Skill drafts | CF Durable Object | weapon-skills.grudge-studio.com | + +**SPA never holds `DATABASE_URL` or CF write tokens.** + +--- + +## Best deploy path for Dev Island content + +### Today (lab-on-Vercel — correct default) + +1. Meshes live under `public/models/dev-island/*` → ship in `dist` on `vercel --prod`. +2. Default layout ships as `public/maps/training_room/layout.default.json`. +3. Play load order: + - `localStorage` (author session from DevNode export) + - same-origin `./maps/training_room/layout.default.json` + - info / objectstore / CDN (when promoted) + - built-in `createTrainingRoomLayout()` +4. Characters stay CDN Toon RTS; harvest rocks stay same-origin until promote. + +### Promote to fleet production (R2 + D1 + info) + +When content is ready for Open/client handoff (not just casting lab): + +| Step | Action | +|------|--------| +| 1 | DevNode **Export** → layout JSON + **promote package** (`grudge.trainingRoomPromote/v1`) | +| 2 | Upload binaries → R2 key prefix `lab/casting/training-room/dev-island/*` | +| 3 | Insert D1 index rows from promote package `d1Index[]` (tags: casting, training_room) | +| 4 | Publish layout JSON → `info/api/v1/maps/training_room/layout.json` + objectstore mirror + R2 layout | +| 5 | Optional: set play `preferPublished` for smoke against CDN catalog | +| 6 | Forge/Open can consume `worldMeshNodes[]` (id · meshKey · kind · position · physicsLayer · location) | + +**Promote package fields:** + +```json +{ + "contract": "grudge.trainingRoomPromote/v1", + "mapId": "training_room", + "layout": { "version": 1, "nodes": [] }, + "worldMeshNodes": [], + "d1Index": [{ "key": "lab/casting/…", "cdnUrl": "https://assets…", "tags": [] }], + "r2Prefix": "lab/casting/training-room", + "publishTargets": { "layoutJson": [], "binaries": "r2:…" } +} +``` + +--- + +## Vercel routing (multi-page) + +| Path | Serves | +|------|--------| +| `/` · SPA routes | `index.html` play lab | +| `/devnode.html` | Training Room editor (**not** rewritten to index) | +| `/models/**` `/maps/**` `/icons/**` | static public | +| `/api/**` | Railway / assets / objectstore / info rewrites | + +`vercel.json` excludes `devnode`, `maps/`, `models/`, `anims/`, `skills/`, etc. from the SPA catch-all. + +--- + +## Env (Vercel Production) + +| Name | Value | +|------|--------| +| `VITE_FLEET_API` | `same-origin` | +| `VITE_RAILWAY_API` | grudge-api Railway URL | +| `VITE_ASSETS_URL` | `https://assets.grudge-studio.com` | +| `VITE_OBJECTSTORE_URL` | `https://objectstore.grudge-studio.com` | +| `VITE_INFO_API` | `https://info.grudge-studio.com/api/v1` | +| `VITE_AUTH_URL` | `https://id.grudge-studio.com` | +| `VITE_WEAPON_SKILL_DO_URL` | `https://weapon-skills.grudge-studio.com` | + +See `.env.example` · `docs/CASTING_DEPLOY_ENV_SSOT.md`. + +--- + +## Terrain / world layers (unchanged SSOT) + +L0 `IslandHeightfield` · L1 mesh · L2 grass+forest · L3 harvest · Water StageWater +Physics: Rapier heightfield · one `terrain.sample` +@see `docs/TRAINING_ROOM_SSOT.md` · `docs/THREE_LAYER_TERRAIN_SSOT.md` + +--- + +## Confirmation + +``` +[x] fleetEnv + layout load chain (storage → published → builtin) +[x] vercel SPA rewrite keeps /devnode.html + /maps/ +[x] promote package with d1Index + worldMeshNodes + R2 keys +[x] default layout in public/maps/training_room/ +[ ] Owner: upload promote package to R2/D1 when ready for Open handoff +[ ] Smoke: https://casting.grudge.studio/ + /devnode.html after deploy +``` diff --git a/docs/TRAINING_ROOM_SSOT.md b/docs/TRAINING_ROOM_SSOT.md new file mode 100644 index 0000000..f6a2c54 --- /dev/null +++ b/docs/TRAINING_ROOM_SSOT.md @@ -0,0 +1,85 @@ +# Training Room · DevIsland · DevNode — one map + +**Map id:** `training_room` +**Aliases:** `devisland` · `dev_island` · `devnode` +**Label:** Training Room · DevIsland + +Play (`index.html`) and author (`devnode.html`) are **the same island**. Export from DevNode → localStorage → play harvest layout. + +**Code SSOT:** `src/world/trainingRoomMap.js` · `src/world/trainingRoomDeploy.js` · `src/config/fleetEnv.js` +**Production deploy:** `docs/TRAINING_ROOM_DEPLOY_SSOT.md` + +--- + +## What casting.* terrain / world is built with + +| Layer | Name | Built by | Physics / sample | +|-------|------|----------|------------------| +| **L0** | Height field | `IslandHeightfield` (FBM seed/amp/flatCore) | Rapier `PhysicsWorld.addHeightfield` · `sample(x,z)` | +| **L1** | Surface ground | Same mesh (meadow/dirt/shore verts); flat `Ground` hidden | same | +| **L2** | Vegetation | `StylizedGrassLayer` + `GrowingForest` | roots on L0 sample | +| **L3** | Detail | `DevIslandHarvest` rocks/ore/herbs + decor + dummies | static props on L0 Y | +| **Water** | Open sea | `StageWater` + `OpenSeaShells` | freeride / windsurf (sibling of land) | + +**One height API:** `this.terrain` = `terrainHandle(islandTerrain)` → aim, path, feet, harvest, forest, grass, drops. + +### Learned patterns (not parallel engines) + +| Source | Used for | +|--------|----------| +| snakey-locomotion | multi-band heightAt, feet on surface | +| three-stylized | grounds colors, instanced grass wind | +| forestoutline.html | procedural tree / leaf ideas → GrowingForest | +| three.js Rapier terrain | Float32 → heightfield collider | + +### Settings + +`settings.terrain` in `src/config/settings.js`: + +```js +{ enabled, seed: 17, amp: 0.85, segments: 96, grid: 65, flatCore: 8, + forestEnabled, forestCount: 48, grassEnabled, grassDensity: 28, … } +``` + +Pad size: `WORLD.islandRadius` · `WORLD.shoreBand` · `WORLD.waterY` (`worldScale.js`). + +--- + +## One product surface + +| Surface | Role | +|---------|------| +| **Play lab** `index.html` | Combat · harvest F · Training Room map boot | +| **DevNode** `devnode.html` | Place nodes · export layout · same terrain knobs | +| **Admin World** | Link → Training Room editor | +| **Catalog** `devIslandCatalog.js` | Mesh pools + default polar layouts | +| **Palette** `nodePalette.js` | Editor families (cliff/tree/rock/ore/flora/PvE) | + +Storage keys (synced): + +- `grudge.casting.training_room.layout.v1` (canonical) +- `grudge.casting.devnode.layout.v1` (legacy, still written) + +--- + +## Flow + +``` +DevNode place/export ──► localStorage (Training Room layout) + │ +Play App.init ───────────────► DevIslandHarvest.applyNodeLayout + │ + default if empty: DEFAULT_HARVEST + DECOR + DUMMIES +``` + +--- + +## Confirmation + +``` +[x] Map stamped training_room on play + editor +[x] Layers L0–L3 + water documented as current builders +[x] DevNode export/import ↔ play harvest layout +[x] No second island / second heightfield for DevNode vs play +[ ] Redeploy casting.* and smoke index + devnode.html +``` diff --git a/docs/WARLORDS_DEV_UI_SSOT.md b/docs/WARLORDS_DEV_UI_SSOT.md new file mode 100644 index 0000000..9808b6a --- /dev/null +++ b/docs/WARLORDS_DEV_UI_SSOT.md @@ -0,0 +1,112 @@ +# Warlords Dev Island UI SSOT (casting.*) + +**Host:** casting.grudge-studio.com · casting-abilities-threejs.vercel.app +**Repo:** CastingAbilitiesThreeJS + +Gameplay chrome for the **Warlords-era dev island** lab: bag shells, button atlas, pirate cursors. +Does **not** replace CraftPix unit frames / hotbar (`craftpixUi.js`) or invent a second inventory stack. + +--- + +## Author sources (Documents) + +| File | Role on casting | +|------|-----------------| +| `inventory.png` (201×188) | Full bag layout ref: armor doll · 2×2 craft + result · 9×3 bag · 9 hotbar · side tabs | +| `miniinventory.png` (800×512) | Fantasy wood **mini bag** 9×4 + side tabs + hotbar **1–10** | +| `button UI.png` (192×192) | Pixel **icon atlas** (8×8 @ 24px) for chrome toggles | +| `Pirate Pack - Cursors.zip` | **Mouse intents** (board / attack / loot / harvest / door / sail…) | + +Shipped under: + +``` +public/ui/warlords-dev/ + inventory/inventory.png + inventory/miniinventory.png + buttons/button-ui.png + cursors/pirate/MouseIcon1.png … MouseIcon10.png +``` + +Optional fleet mirror: GrudgeBuilder `client/public/ui/warlords-dev/`. + +--- + +## Code + +| Module | Job | +|--------|-----| +| `src/ui/warlordsUiSkin.js` | Paths, CSS vars, mini/full grid contract, button atlas helpers | +| `src/ui/warlordsCursors.js` | `configureWarlordsCursors` · `setCursorIntent` · interact→intent map | +| `src/ui/warlords-dev-ui.css` | Mini bag skin · Main Panel shell accent · button chrome | +| `src/ui/DropBag.js` | Quick bag uses **miniinventory** 9×4 + hotbar strip | +| `src/ui/InventoryPanel.js` | Main Panel gets `wl-inv-shell` class | +| `src/core/App.js` | Boot skin + cursors; harvest proximity → harvest cursor | + +--- + +## Systems map (what uses what) + +| Gameplay | UI skin | +|----------|---------| +| CraftPix HUD frames / DRC bar | Existing `craftpix/*` | +| **I** Main Panel equipment + bag | `inventory.png` shell accent + existing slot chrome | +| **Bag** / harvest loot throw-pickup | `miniinventory.png` DropBag | +| Menu / admin chrome icons | `button-ui.png` atlas (`.wl-btn-icon`) | +| Hover harvest node / dummy / loot | Pirate **cursor intent** | +| Focus combat (RMB) | Cursor **none** + screen crosshair (unchanged) | + +--- + +## Cursor intents (Pirate Pack) + +| Intent | Icon (pack) | Use | +|--------|-------------|-----| +| `default` | MouseIcon2 cutlass | Free roam | +| `attack` | MouseIcon2 | Hostile / dummy | +| `slash` | MouseIcon3 | Skill-ready slash | +| `enter_ship` | MouseIcon1 anchor | Board ship (not sail) | +| `sail` | MouseIcon6 | Helm / windsurf sail | +| `door` | MouseIcon4 | Portal / door | +| `loot` / `pickup` | MouseIcon7 coin | Chest / world drop | +| `look` | MouseIcon5 telescope | Inspect | +| `harvest` | MouseIcon8 | Resource node in range | +| `talk` | MouseIcon9 | NPC | +| `use` | MouseIcon10 | Generic interact | + +```js +import { setCursorIntent, intentFromInteractKind } from './ui/warlordsCursors.js'; +setCursorIntent('harvest'); +setCursorIntent(intentFromInteractKind('ore')); +``` + +**Hard split:** `enter_ship` ≠ `sail`. Do not collapse boarding and helm. + +--- + +## Bag contracts + +| Panel | Grid | Hotbar | +|-------|------|--------| +| Mini (`DropBag`) | **9×4 = 36** | 1–10 labels (visual; combat hotbar still DRC) | +| Full (`inventory.png` ref) | **9×3 = 27** + craft 2×2 | 9 | +| Main Panel bag tab | Existing `mainPanelSlots` SSOT | — | + +Production bag SSOT remains Railway / fleet craft — lab uses local DropBag + Main Panel storage. + +--- + +## Boot order + +1. `applyCraftpixCssVars` (HUD) +2. `applyWarlordsUiCssVars` + `configureWarlordsCursors({ theme: 'pirate', target: canvas })` +3. Preload shells + cursors (non-blocking) +4. Frame: if unlocked cursor and nearest harvest ≤5 m → `harvest` intent + +--- + +## Related + +- `docs/MAIN_PANEL_INVENTORY_SSOT.md` +- `docs/CRAFTPIX_HUD_SSOT.md` +- `docs/ISLAND_STAGE_SSOT.md` · harvest F +- Skill: `craftpix-rpg-mmo-ui` (HUD only — bag shells are this doc) diff --git a/docs/WEAPON_EQUIP_PREFABS_SSOT.md b/docs/WEAPON_EQUIP_PREFABS_SSOT.md new file mode 100644 index 0000000..2aa676f --- /dev/null +++ b/docs/WEAPON_EQUIP_PREFABS_SSOT.md @@ -0,0 +1,71 @@ +# Equip weapons → skills + assets → Warlords prefabs + +**Live lab:** https://casting-abilities-threejs.vercel.app/ +**Catalog UI:** https://info.grudge-studio.com/WEAPON_SKILLS.html +**Runtime:** `src/combat/equippedWeaponRuntime.js` · `src/api/t0WeaponCatalog.js` + +## Product rule + +A weapon is **tried by equipping it**. Equip loads: + +| Layer | Source | +|-------|--------| +| **Icon** (UI + world sprite) | `master-weapon-prefabs` `assets.iconUrl` | +| **3D model** (hand attach + world) | `modelUrl` / `prod/gltf/weapons/*.glb` | +| **Drop prefab key** | `assets.dropPrefabR2Key` | +| **Weapon skills** (3-slot starter) | `t0-weapons` + `master-weaponSkills` starters | +| **Anim pack** | weaponType → magic / sword_shield / longbow | +| **Kit mesh_ids** | exclusive sword/axe/staff/bow slot on Toon kit | + +Not a free skill tree switch alone — **the item owns the skills**. + +## Lab UX + +1. **I → Weapon** +2. Click any **T0** card (Training Sword, Apprentice Wand, …) +3. See icon banner + model path + skill list +4. Combat **Q** → **1 / 2 / 3** fire that weapon’s skills +5. Slot 3 choices: click alternate ability on the list +6. **Export Warlords prefab JSON** / Copy + +## Prefab export shape + +```json +{ + "kind": "warlords-weapon-prefab", + "id": "t0-wand", + "assets": { "iconUrl", "modelUrl", "dropPrefabUrl", "animPack", "meshSlot" }, + "presentation": { "iconUrl", "modelUrl", "borderColor", "glowColor" }, + "skills": { "slot1", "slot2", "slot3Options", "hotbar" }, + "lab": { "meshSlot", "animPack", "labStyle" } +} +``` + +Use this to seed Warlords entity/weapon prefabs (icon + mesh + skill binds). + +## SSOT files + +| File | Role | +|------|------| +| `api/v1/master-weapon-prefabs.json` | 877 prefabs · icon · model · tier | +| `api/v1/t0-weapons.json` | 15 starters · three-slot skill bodies | +| `api/v1/master-weaponSkills.json` | Full skill defs / WAND STAFF … | +| `src/character/WeaponMeshAttach.js` | Hand GLB attach | +| `src/loot/prefabAssets.js` | presentPrefab icon/model | + +## All weapons pattern + +Same pipeline for every `weaponType` (SWORD … WAND … BOW): + +1. Resolve prefab presentation +2. Resolve 3-slot (or 4) skills +3. Map to lab mesh + anim pack +4. Equip → playtest → export + +Higher tiers: same equip path when prefab has `skills.slots` + catalog skill ids. + +## Related + +- `docs/T0_APPRENTICE_WAND_SSOT.md` +- `docs/CASTING_10_SPELLS_SSOT.md` +- `docs/WORLD_DROP_PRESENTATION.md` diff --git a/docs/WEAPON_HAND_SHEATH_SSOT.md b/docs/WEAPON_HAND_SHEATH_SSOT.md new file mode 100644 index 0000000..669e2bb --- /dev/null +++ b/docs/WEAPON_HAND_SHEATH_SSOT.md @@ -0,0 +1,72 @@ +# Weapon hand bones · hold pose · sheath / unsheath (Casting lab) + +**Live:** https://casting.grudge.studio +**Code:** `WeaponMeshAttach` · `weaponHoldPose` · `WeaponSheathRuntime` · `CharacterController` + +## Hand-bone best practices + +| Rule | Detail | +|------|--------| +| Prefer kit containers | `R_hand_container` / `L_hand_container` (Toon RTS) over raw finger bones | +| Fallback bones | `Bip001 R Hand` · `mixamorig:RightHand` (same order as hold-pose SSOT) | +| One attach group | `WeaponAttach` under hand — never merge into skinned mesh | +| SI scale first | Normalize longest axis to profile max length, then soft-cap width | +| Grip orientation | Mesh long axis → local +Y; pitch ≈ −90° for melee/pistol (bow −75°) | +| Muzzle / tip | Marker on attach after parent (world-farthest from grip) | +| Post-mixer residual | `applyWeaponHoldPose` **after** `mixer.update` — never bake into clip | +| Hold only when drawn | Skip residual while sheathed (stow owns local TRS) | + +### Stack (do not fork) + +``` +1. attachWeaponModel(handBone, url, { profile }) // equip +2. static mesh appearance (optional lab tint/scale) +3. AnimationMixer.update(dt) // locomotion / combat +4. WeaponSheathRuntime.update(policy) // hand ↔ hip/back +5. applyWeaponHoldPose(mixer, gait, kind) // drawn only +6. optional HandIK / RideIK / pistol reload overlay +``` + +## Auto sheath / unsheath policy + +| Event | Action | Reason | +|-------|--------|--------| +| Walk / run / sprint gait | **Sheath** | Traversal — free arms, no blade in face | +| Jump / fall / dodge / roll / slide | **Sheath** | Air + mobility | +| Mount / freeride / ride parented | **Sheath** | Hands for boom/rail IK; no weapon on deck | +| Idle (gait 0) | **Unsheath** | Combat ready | +| Attack / cast / charge / parry / reload | **Unsheath** | Combat needs hand mesh | +| Equip weapon | **Unsheath** | Fresh attach on hand | +| Dismount | **Unsheath** | Land combat ready | + +Policy knobs: `WEAPON_SHEATH_POLICY` in `WeaponSheathRuntime.js` +(`sheathOnWalk`, `sheathOnRun`, `sheathOnSprint`, `sheathOnMount`, `sheathOnAir`, `sheathOnMobility`, debounce). + +Combat always wins over gait (attack while walking keeps weapon drawn for the one-shot window). + +## Stow sockets + +| Profile | Parent | Pose | +|---------|--------|------| +| sword / melee / dagger | `Bip001 Pelvis` (hip) | blade along hip | +| staff | spine / quiver family | diagonal back | +| bow | spine / quiver family | flat on back | +| pistol | pelvis | hip holster | +| shield | **none** | stays on arm | + +Back bone resolution shares `findBackBone()` with windsurf stow (quiver-family). + +## API + +```js +character.isWeaponDrawn +character.setWeaponSheathed(true|false|null) // null = policy +character.weaponSheath.sheath('manual') +character.weaponSheath.unsheath('manual') +``` + +## Related + +- `docs/WINDSURF_RIDE_SSOT.md` — mount parent + RideIK (hands on sockets) +- `docs/PISTOL_ANIM_SSOT.md` — draw / fire / reload (always drawn path) +- ObjectStore `grudge6-weapon-hold-pose.js` — residual tables (keep in sync) diff --git a/docs/WEAPON_MESH_COLLIDER_SSOT.md b/docs/WEAPON_MESH_COLLIDER_SSOT.md new file mode 100644 index 0000000..3cf1ce6 --- /dev/null +++ b/docs/WEAPON_MESH_COLLIDER_SSOT.md @@ -0,0 +1,100 @@ +# Weapon mesh collider SSOT (Casting lab) + +**Code:** `src/character/weaponMeshCollider.js` +**Consumers:** `CharacterController` · `equippedWeaponRuntime` · `weaponTipTrail` · parry / residual / IK + +## Goal + +From the **actual weapon mesh** (catalog `WeaponAttach` or kit mesh_ids): + +1. Fit an **oriented cylinder** along the blade/barrel (principal axis of vertices) +2. **Pad radius +0.02 m** around the mesh (`WEAPON_COLLIDER_PAD_M`) +3. Parent tip / grip / mid markers to the mesh so they **ride the anim** +4. Drive **melee residual, tip trail, projectiles, IK grip, effects, sounds, parry** from that one volume + +No free-floating Y-stick on the hand. No second collider stack. + +## Pipeline + +``` +equipWeaponById / mesh_ids change + → resolveWeaponMesh (attach child or kit sword/axe/…) + → sample vertices (mesh-local) + → principal axis (power iteration on covariance) + → cylinder length + max radial extent + 0.02 m pad + → orient grip end toward R_hand + → attach WeaponVolume markers (Tip / Grip / Mid) + → character.weaponVolume = vol +``` + +## API + +| Function | Use | +|----------|-----| +| `buildWeaponMeshVolume(root, { weaponAttach, handBone, padM, debug })` | Full rebuild | +| `getVolumeTipWorld(vol)` | Residual / projectile / trail sample | +| `getVolumeGripWorld(vol)` | IK off-hand / whoosh origin | +| `getVolumeAxisWorld(vol)` | Blade forward for residual path | +| `getVolumeContactRadius(vol)` | Physics residual contact | +| `pointHitsWeaponVolume(vol, point, r)` | Parry / block tests | +| `weaponVolumeToJSON(vol)` | Prefab export | + +## Character hooks + +```js +character.rebuildWeaponVolume({ debug: false }); // after equip +character.getWeaponTip(out); // prefers weaponVolume tip +character.getWeaponForward(out); // prefers volume axis +character.weaponVolume.radiusM; // cylinder radius incl. pad +``` + +## Systems fed by volume + +| System | How | +|--------|-----| +| **Tip trail** | Samples `getWeaponTip` each frame in swing | +| **Apex residual** | Tip + `beyondBladeM` · contact = max(settings, volume.radius) | +| **Physics projectile** | Spawn along axis, radius from volume | +| **Fire blur path** | Fire Ability along residual curve | +| **IK** | Grip marker for off-hand / reload reach | +| **Parry** | C → `_parryUntil` · `tryParryBlock(point)` on player contact · cancel dmg/status/push | +| **SFX** | Tip velocity / impact at tip (use tip world) | + +### Parry hit path (P0) + +``` +incoming projectile / residual contact + → _onProjectileHit (applyToPlayer + point) + · isInvincible → toast + return + · tryParryBlock(point, contactRadius) → toast "Parried!" + return + → SkillStatusSystem.applyHit (same gate for any spatial player hit) + · no point (ward/heal buffs) → pass through +``` + +| API | Role | +|-----|------| +| `parry()` | Spend stamina · rebuild weapon volume · open window | +| `weaponVolumeBlocks(pt, r)` | Cylinder test (mesh-fit + 0.02 m pad) | +| `tryParryBlock(pt, r)` | Window + volume · consume on success · VFX | +| `isInvincible` / `invuln` | Dodge i-frames also cancel player contact damage | + +## Defaults + +| Knob | Value | +|------|-------| +| Pad | **0.02 m** | +| Min radius | 0.015 m + pad | +| Min length | 0.08 m | +| Shape | cylinder (convex hull optional later) | + +## Hard bans + +- ❌ Hand-bone Y-stick as blade collider +- ❌ Second tip measure that ignores `weaponVolume` after rebuild +- ❌ Whole-body GLB swap for weapon change (mesh_ids / attach only) + +## Related + +- Open `/space` `fitCollidersToMeshes` — same “parent to weapon mesh” rule +- `WeaponMeshAttach.placeMuzzleMarker` — tip heuristic for catalog guns +- `docs/CASTING_LAB_SSOT.md` · melee residual diff --git a/docs/WEAPON_PREFAB_UUID_SSOT.md b/docs/WEAPON_PREFAB_UUID_SSOT.md new file mode 100644 index 0000000..66a5eec --- /dev/null +++ b/docs/WEAPON_PREFAB_UUID_SSOT.md @@ -0,0 +1,130 @@ +# Weapon prefab + GRUDGE UUID SSOT + +**Purpose:** One pattern for **what a weapon prefab is**, **which UUIDs link it**, and **what it must do** in bag / equip / combat / craft / export. + +**Code:** `src/api/weaponPrefabContract.js` · `exportWarlordsWeaponPrefab` · `presentPrefab` +**Catalog:** `master-weapon-prefabs.json` (~877) · `t0-weapons.json` (15) · `master-weaponSkills.json` (268) + +--- + +## 1. GRUDGE UUID prefixes + +| Prefix | Entity | Example | +|--------|--------|---------| +| **ITEM-** | Weapon / tool / equipment definition | `ITEM-20260423030000-000001-4D4C1056` (Training Sword) | +| **SKIL-** | Weapon skill definition | `SKIL-T0-AAB723-6F43B6` (Practice Slash) | +| **ICON-** | Icon registry entry | (often null on T0 — path still works) | +| **RECP-** | Crafting recipe | T1+ usually | +| **ATTR-** | Attribute affinity links | stat graph | +| **MATL-** | Material | scrap-ingot, etc. | +| **RELC- / MNT- / ARTF- / ENCH-** | Relic / mount / artifact / enchant | non-weapon prefabs | + +**Rule:** Runtime prefers **UUID** for identity and skill binds; human `id` (`t0-sword`) is the slug for URLs and loadouts. + +``` +ITEM-* ──skills.skillUuids──► SKIL-* ──prefab.vfxRef──► VFX / effect + │ │ + ├──assets.iconUuid──► ICON-* └── skill body (dmg, CD, cast, effects) + ├──recipeUuid──► RECP-* + └── instance (Railway bag) ── not ObjectStore +``` + +--- + +## 2. What a weapon prefab must **have** (6 layers) + +| Layer | Required fields | Source | +|-------|-----------------|--------| +| **identity** | `uuid` ITEM-*, `id`, `name`, `tier`, `weaponType` | master-weapon-prefabs / t0 | +| **stats** | `stats.damage`, speed, crit, block, defense… | same | +| **skills** | `skills.slots[]` with `skillIds` + **`skillUuids`**, `slotPattern` | prefab + t0-weapons skill bodies | +| **assets** | `assets.iconUrl` (ICON-* when known), `modelUrl` / `prodGltfUrl` when wired, drop keys | CDN | +| **runtime** | derived: `meshSlot`, `animPack`, `labStyle` | weaponType map | +| **loadout** | `three-slot-starter` (T0) or `five-slot` (T1+) | prefab.loadout / skills.slotPattern | + +Validate: `validateWeaponPrefab(prefab)` → score 6/6. + +### T0 skill slots (product) + +| Slot | type | fixed | choice | UUID count | +|------|------|-------|--------|------------| +| 1 | primary | yes | no | 1 SKIL-* | +| 2 | secondary | yes | no | 1 SKIL-* | +| 3 | ability | no | **yes** | 2–3 SKIL-* options, player picks one | + +T1+ five-slot: primary · secondary · ability · ability · ultimate (see catalog `loadoutPattern`). + +--- + +## 3. What a weapon prefab must **do** (runtime jobs) + +| Job | Consumer | Contract field | +|-----|----------|----------------| +| **Bag / world drop** | WorldDrops, bag UI | iconUrl, tier border/glow, optional dropPrefab | +| **Equip** | EquipmentManager · WeaponMeshAttach | meshSlot · modelUrl · SI scale | +| **Controller** | CharacterController | animPack (sword_shield / magic / longbow) | +| **Hotbar** | DRC 1–3 (T0) / 1–5 (T1+) | skillIds + skillUuids → skill bodies | +| **Combat** | DrcCombatController | style · delivery · residual / cast / projectile | +| **Craft** | Craft UI | recipeUuid · craftsInto (T0 → T1) | +| **Export** | Unity / Warlords SO | full export JSON with `uuids` graph | + +Lab export: Inventory equip → **Export prefab** → includes `uuids.item` + `uuids.skills[]`. + +--- + +## 4. Example: Training Sword + +| Field | Value | +|-------|--------| +| ITEM | `ITEM-20260423030000-000001-4D4C1056` | +| id | `t0-sword` | +| Slot 1 SKIL | `SKIL-T0-AAB723-6F43B6` Practice Slash | +| Slot 2 SKIL | `SKIL-T0-6228E2-DDBEA0` Guard Stance | +| Slot 3 SKIL options | `SKIL-T0-C0DE2D-41913A` Quick Thrust · `SKIL-T0-8A519A-906D3B` Wide Sweep | +| icon | CDN Sword_01.png | +| model | family / prod gltf when wired | +| pattern | `three-slot-starter` | + +--- + +## 5. R2 layout (catalog) + +``` +models/weapons/{category}/{baseName}.glb +game-assets/icons/weapons/{slug}.png +prefabs/items/weapons/{weaponType}/{tier}/{baseName}.prefab.glb +effects/3d/loot/weapon-{rarity}.glb +``` + +CDN base: `https://assets.grudge-studio.com` + +--- + +## 6. Agent / code rules + +1. **Never invent ITEM-* or SKIL-*** — mint only in ObjectStore generators. +2. **Resolve skills by UUID when present**, fall back to skill id. +3. **T0 skill bodies** from `t0-weapons.json`; prefab row holds slot **UUID graph** + assets. +4. **Instance state** (owned, durability, enchants) = Railway — not ObjectStore JSON. +5. Incomplete model/iconUuid = **warning**, not a second catalog. +6. Use `normalizeWeaponPrefabContract` / `exportWarlordsWeaponPrefab` for handoffs. + +```js +import { + validateWeaponPrefab, + normalizeWeaponPrefabContract, + summarizePrefabCompleteness, + GRUDGE_UUID_PREFIX +} from './weaponPrefabContract.js'; +``` + +--- + +## 7. Related + +| Doc | Role | +|-----|------| +| `GAME_ITEM_PREFAB_PRODUCTION_SSOT.md` | All item categories | +| `WEAPON_SKILLS_API_SSOT.md` | Skill field sources | +| `T0_WEAPONS_SSOT.md` | 15 starters matrix | +| `SKILL_DELIVERY_SSOT.md` | How skills deploy in space | diff --git a/docs/WEAPON_SKILLS_API_SSOT.md b/docs/WEAPON_SKILLS_API_SSOT.md new file mode 100644 index 0000000..2101265 --- /dev/null +++ b/docs/WEAPON_SKILLS_API_SSOT.md @@ -0,0 +1,77 @@ +# Weapon skills API SSOT — do not invent skill rows + +**Rule:** Prefer ObjectStore / info.grudge-studio.com catalog JSON for **every** skill field (name, dmg, CD, cast, range, effects). Only invent systems when a field is **missing** in all endpoints below. + +Browse UI (same data): +https://grudge-objectstore.pages.dev/WEAPON_SKILLS · https://info.grudge-studio.com/WEAPON_SKILLS.html + +Docs hub: https://info.grudge-studio.com/docs + +--- + +## Easiest path (what Casting uses) + +| Need | Endpoint (try in order) | Notes | +|------|-------------------------|--------| +| **T0 starter 3-slot skills** | `GET …/api/v1/t0-weapons.json` | `weapons[].weaponSkills.slot1 / slot2 / slot3Options` — **Practice Slash, Guard Stance, Quick Thrust, Wide Sweep** for `t0-sword` | +| **All weapon skills (268)** | `GET …/api/v1/master-weaponSkills.json` | v3.1 · by weapon type · slots · damage · CD · cast · effects · prefab | +| **Prefab ↔ skill ids** | `GET …/api/v1/master-weapon-prefabs.json` | mesh/icon + skill slot ids for equip | +| **Older skills dump** | `GET …/api/v1/weaponSkills.json` | docs list; prefer **master-weaponSkills** + **t0-weapons** | + +**Hosts (same JSON, CORS):** + +1. `https://info.grudge-studio.com/api/v1/…` +2. `https://objectstore.grudge-studio.com/api/v1/…` +3. `https://grudge-objectstore.pages.dev/api/v1/…` + +Base URL from docs: `https://info.grudge-studio.com/api/v1/` + +--- + +## Training Sword T0 (canonical — do not rename) + +From live `t0-weapons.json` id **`t0-sword`**: + +| Slot | Skill id | Name | DMG | CD | Cast | Effects | +|------|----------|------|-----|----|------|---------| +| 1 fixed | `t0_sword_practice_slash` | Practice Slash | 18 | 0 | Instant (`null`) | Starter | +| 2 fixed | `t0_sword_guard_stance` | Guard Stance | 0 | 6s | Instant | −15% damage 2s | +| 3 choose | `t0_sword_quick_thrust` | Quick Thrust | 16 | 3s | Instant | Extended reach | +| 3 choose | `t0_sword_wide_sweep` | Wide Sweep | 14 | 4s | Instant | Small AoE | + +Production: player picks **one** of the two slot-3 options. +Lab must **not** invent a 4th catalog skill; keys 1–3 only for T0. + +--- + +## Code map (Casting lab) + +| Concern | File | +|---------|------| +| Fetch T0 + prefabs + merge | `src/api/t0WeaponCatalog.js` | +| Fetch master skills | `src/api/weaponSkillsCatalog.js` | +| Equip → hotbar | `src/combat/equippedWeaponRuntime.js` · `hotbarForWeapon` | +| Runtime cast | `skillDefToDrc` → `DrcCombatController.useSkill` | + +**Allowed lab-only layer:** map skill **id/name → anim role** (`attack1` / `block` / …) and interpret effect **strings** (e.g. “Small AoE”) for residual. +**Forbidden:** invent skill ids, damage, CDs, or fake cast times when catalog has Instant/`null`. + +--- + +## Field mapping + +| Catalog field | Runtime | +|---------------|---------| +| `damage` | `skill.damage` | +| `cooldown` (0 / number) | `skill.cooldown` (0 = no CD / GCD only) | +| `castTime` `null` | `castDuration = 0` (Instant) | +| `range` `null` | melee default range only if style melee | +| `effects[]` | buff parse / residual hints | +| `resourceCost` | mana / stamina | +| `prefab` / `animation` | optional VFX / clip when present | + +--- + +## When to invent systems + +Only if **after** fetching t0-weapons + master-weaponSkills + prefabs a required runtime field is still empty (e.g. no VFX ref → lab residual default). Never invent **skill definitions** that contradict WEAPON_SKILLS. diff --git a/docs/WEAPON_SKILL_DO_SSOT.md b/docs/WEAPON_SKILL_DO_SSOT.md new file mode 100644 index 0000000..0e8c64d --- /dev/null +++ b/docs/WEAPON_SKILL_DO_SSOT.md @@ -0,0 +1,78 @@ +# Weapon skill Durable Object SSOT + +**Worker:** `grudge-weapon-skill-drafts` +**Repo path:** `CastingAbilitiesThreeJS/worker` +**DO class:** `WeaponSkillDrafts` (SQLite-backed, instance name `production`) + +## Hosts + +| Host | Role | +|------|------| +| **https://casting.grudge.studio** | **Control plane** — Casting lab, dev → production promote / equip mirror push | +| **https://weapon-skills.grudge-studio.com** | **Public DO API** (CNAME → Worker; production route) | +| `https://grudge-weapon-skill-drafts.grudge.workers.dev` | Fallback Worker URL | +| `https://casting.grudge-studio.com` | Legacy alias only (NXDOMAIN unless re-added) | +| `https://casting-abilities-threejs.vercel.app` | Vercel project hostname | + +## Dev → production flow + +``` +Multiverse F4 drafts + → PUT https://weapon-skills.grudge-studio.com/api/v1/bundle +Casting lab (casting.grudge.studio) + → GET equip-catalog (merge into equipWeaponById) + → PUT equip-catalog (pushLocalEquipMirror) + → POST /api/v1/promote (stamp production) +Fleet consumers + → GET equip-catalog / bundle from weapon-skills.* +``` + +## Contracts + +| Contract | Direction | +|----------|-----------| +| `grudge.weaponSkillPrefabBundle/v1` | Multiverse `exportDurableBundle()` → `PUT /api/v1/bundle` | +| `grudge.equipWeaponCatalog/v1` | Mirrors Casting `equipWeaponById` → `GET|PUT /api/v1/equip-catalog` | +| `grudge.weaponSkillPrefab/v1` | Single skill rows | + +**Not** player SSOT (Railway). **Not** mesh binaries (R2). + +## API + +``` +GET /api/health +GET|PUT /api/v1/bundle +GET /api/v1/skills +GET|PUT|DELETE /api/v1/skills/:id +GET|PUT /api/v1/equip-catalog +POST /api/v1/equip-catalog/weapon +POST /api/v1/promote +GET /api/v1/meta +``` + +CORS: `casting.grudge.studio`, `*.grudge.studio`, `*.grudge-studio.com`, `*.vercel.app`, localhost Vite. + +## Client wiring + +| Surface | Module | Behavior | +|---------|--------|----------| +| Multiverse F4 | `pushDurableBundleToDo` | Push → `weapon-skills.grudge-studio.com` (fallback workers.dev) | +| Casting equip | `weaponSkillDoApi` + `t0WeaponCatalog` | Merge DO; **t0 wins on id** | +| Casting promote | `promoteCatalog` / `pushLocalEquipMirror` | Lab control plane → DO | + +Env: `VITE_WEAPON_SKILL_DO_URL=https://weapon-skills.grudge-studio.com` + +## Deploy + +```bash +cd worker +npx wrangler deploy --env production +# route: weapon-skills.grudge-studio.com/* +``` + +## DNS + +| Type | Name | Target | +|------|------|--------| +| CNAME | `weapon-skills` on `grudge-studio.com` | Worker / CF (live) | +| CNAME | `casting` on `grudge.studio` | Vercel (control plane) | diff --git a/docs/WEAPON_SKILL_PRODUCTION_SSOT.md b/docs/WEAPON_SKILL_PRODUCTION_SSOT.md new file mode 100644 index 0000000..1866f05 --- /dev/null +++ b/docs/WEAPON_SKILL_PRODUCTION_SSOT.md @@ -0,0 +1,193 @@ +# Weapon skill production — scriptable pattern (SSOT) + +**Goal:** Every weapon skill is production-ready: **anim · effects · physics · statuses · damage · delivery**, with a **repeatable scriptable** author path for individual skills. + +**Lab:** CastingAbilitiesThreeJS +**Catalog (never invent skill rows):** [WEAPON_SKILLS](https://info.grudge-studio.com/WEAPON_SKILLS.html) · `master-weaponSkills.json` · `t0-weapons.json` +**Fleet shape:** `gameopen/lib/epicfight/.../weaponSkill.ts` `FleetWeaponSkill` +**Code:** `src/combat/weaponSkillProduction.js` · `skillStatusSystem.js` · `skillDelivery.js` · `elementalLinearCast.js` + +--- + +## Non-negotiables + +| Rule | Detail | +|------|--------| +| Catalog first | Skill **id / dmg / CD / cast / range / effects** from ObjectStore only | +| One mixer | Character anim packs only — no second AnimationMixer for skills | +| SI physics | metres, knockback **MM** (100 MM = 1 m), force impulse scale | +| No multipack projectiles | Split orbs/rocks/arrows — never whole fireball / gd_orbs / rock pack | +| Extend, don’t fork | Compile into DRC + FleetWeaponSkill shape — no parallel combat tree | + +--- + +## Production package (every skill) + +`compileProductionWeaponSkill(catalogRow, weaponCtx)` →: + +| Layer | Fields | Source | +|-------|--------|--------| +| **Identity** | id, label, weaponTypeId, slot, slotType | catalog | +| **Economy** | damage, cooldown, castDuration, mana, stamina | catalog | +| **Anim** | pack, role, clip, hitFrameDelay, comboStages | pack map + override | +| **VFX** | cast / travel / impact effectIds, mesh, charge | staff bind + catalog prefab | +| **Delivery** | path_stream / caster_to_target / around_caster / weapon / … | skillDelivery infer | +| **Linear** | castPlan (ice/thunder/meteor/…) | elementalLinearCast | +| **Mesh kind** | freeze_nova / earth_rocks / orbs / arrows | elementAttackVfx | +| **Physics** | force, knockbackMm, knockupVy, contactRadius, aoe, speed, collider | defaults by style + override | +| **Statuses** | freeze, stun, slow, burn, root, push, knockup, silence, ward, focus | parse `effects[]` + override | + +### Status IDs (runtime) + +| Status | Effect | +|--------|--------| +| **push** | Knockback MM + optional hit react | +| **freeze** | Move lock + cyan emissive · `frozenUntil` | +| **stun** | Move lock · yellow flash | +| **slow** | moveMul < 1 | +| **root** | Move lock (nature) | +| **burn** | DoT magnitude (HP host later) | +| **knockup** | vy kick | +| **silence** | Cast lock flag | +| **shield_break** | Flag for guard systems | +| **focus_buff** / **ward** | Self buffs | + +Runtime: `SkillStatusSystem.applyHit` on projectile / residual contact. + +--- + +## Author loop (individual skill) + +``` +1. Catalog id exists on WEAPON_SKILLS / t0 (never invent) +2. node scripts/scaffold-weapon-skill.mjs --id --weapon STAFF +3. Edit skills/production/.json (overrides only) +4. compileProductionWeaponSkill(row, { overrides: json }) +5. assessProductionReadiness → green +6. Smoke casting lab: equip · Q · 1–4 / F / focus LMB +7. Promote prefab.vfxRef / animation via ObjectStore when ready +``` + +### Scaffold + +```bash +node scripts/scaffold-weapon-skill.mjs --id staff_fire_bolt --weapon STAFF +node scripts/scaffold-weapon-skill.mjs --id t0_sword_practice_slash --weapon SWORD +node scripts/scaffold-weapon-skill.mjs --list-ready +``` + +### Override JSON (optional fields only) + +```json +{ + "id": "staff_fire_bolt", + "animRole": "cast", + "animClip": "magic/standing 1h cast spell 01", + "hitFrameDelay": 0.35, + "castEffectId": "fire_hand", + "travelEffectId": "fireball", + "impactEffectId": "inferno", + "projectileMeshUrl": "./models/vfx/orbs/orb-fire.glb", + "force": 9, + "knockbackMm": 200, + "aoeM": 1.2, + "statuses": [ + { "id": "burn", "durationSec": 3, "magnitude": 4 }, + { "id": "push", "durationSec": 0.35, "magnitude": 180 } + ] +} +``` + +--- + +## Runtime path (all weapons) + +``` +Equip weapon (t0 / prefab) + → skillDefToDrc = productionToDrcSkill(compileProductionWeaponSkill(…)) + → DRC hotbar 1–4 / F / focus LMB + → useSkill + → cast bar + anim role (pack) + → planElementalLinearCast → linear skillshot when stream/primary + → _deploySkillDelivery → orbs / rocks / freeze / arrows / residual + → path Ability presentation + → on hit → statuses.applyHit (dmg + push + freeze + stun + …) +``` + +--- + +## Readiness scores + +`assessProductionReadiness(prod)`: + +| Score | Meaning | +|-------|---------| +| **green** | id, dmg, cd, anim, resources; no critical gaps | +| **yellow** | ok but warnings (no VFX, no statuses, buff-only) | +| **red** | missing id/label/cooldown/damage/anim | + +Layers reported: anim · vfx · physics · statuses · delivery · linear · meshKind. + +--- + +## Weapon families (same pattern) + +| Family | Pack | Style | Default delivery | +|--------|------|-------|------------------| +| Sword / axe / hammer / spear | sword_shield | melee | weapon residual | +| Bow / crossbow | longbow | ranged | caster_to_target | +| Pistol | pistol | ranged | caster_to_target | +| Staff / wand / tome | magic | spell | stream + orbs / linear | + +Staff schools fill VFX via `staffWeaponSkillsBind`. Melee uses residual Getsuga profiles. All share **status + physics** package. + +--- + +## Perfect one skill (checklist) + +``` +[ ] Catalog id verified on WEAPON_SKILLS.html +[ ] anim pack + role + optional clip path +[ ] hitFrameDelay feels right on cast +[ ] cast / travel / impact effect ids fire +[ ] projectile mesh SI (if any) +[ ] force / knockbackMm / aoe SI tuned +[ ] effects[] → statuses correct (freeze/stun/push/…) +[ ] assessProductionReadiness green +[ ] Smoke: casting lab host with that weapon equipped +[ ] Prefab fields promoted to ObjectStore when shipping fleet +``` + +--- + +## Code map + +| File | Role | +|------|------| +| `weaponSkillProduction.js` | compile · readiness · DRC flatten | +| `skillStatusSystem.js` | parse effects · apply statuses | +| `skillDelivery.js` | delivery patterns | +| `elementalLinearCast.js` | linear LINE/ZONE merge | +| `staffWeaponSkillsBind.js` | staff VFX from catalog | +| `t0WeaponCatalog.skillDefToDrc` | equip → production package | +| `DrcCombatController` | cast + hit → statuses | +| `scripts/scaffold-weapon-skill.mjs` | override scaffold | +| `skills/production/*.json` | per-skill overrides | + +--- + +## Fleet handoff + +Production package maps to `FleetWeaponSkill`: + +| Production | FleetWeaponSkill | +|------------|------------------| +| anim.clip / role | animClip / animRole | +| physics.collider | collider | +| vfx.cast/impact | castEffectId / impactEffectId | +| physics + projectile mesh | projectile | +| cooldown / castDuration / stamina | same | +| damage / force | same | +| statuses | tags[] + host status layer | + +Open / Warlords hosts implement HP from `damage` and respect `frozenUntil` / `stunnedUntil` on units. diff --git a/docs/WINDSURF_RIDE_SSOT.md b/docs/WINDSURF_RIDE_SSOT.md new file mode 100644 index 0000000..a89956d --- /dev/null +++ b/docs/WINDSURF_RIDE_SSOT.md @@ -0,0 +1,209 @@ +# Windsurf ride SSOT — Casting lab + +**Asset (ride):** `public/models/ride/windsurf_package.glb` + `ride.manifest.json` +**Asset (back stow):** `public/models/ride/back_fly_windsurf.glb` — quiver-scale spine attach +**Code:** `WalkController` · `HoverboardRide` · `RideIK` · `BackSlotEquip` · `SailCloth` +**Feel ref:** [Robpayot/tslda](https://github.com/Robpayot/tslda) (Wind Waker boat / sail) +**Back slot:** `settings.walk.backSlot = 'windsurf'` — deployable utility, not a second vehicle engine + +--- + +## Product intent + +**Windsurf is a vehicle when deployed** (not a free-floating prop). +**Domain: water only** — not a land mobility tool. Dry island → deploy rejected. + +| State | Rule | +|-------|------| +| **Deploy** | Space (walk mode) **on wet shore/ocean** → frontflip · board from back · vehicle | +| **Blocked** | Dry land (`isDryLand`) → toast “water-only” | +| **Mounted** | Character **parented under** `deckCenter` · **RideIK** feet→deck, hands→boom · stay until get-off | +| **Get off** | **E** (or mode leave / cancel) → unparent · **board removed** from scene · land character controller | +| **Land** | `_rideActive` / `_rideParented` false · gait free · Rapier feet at land pose | + +### Parts (deployed) — logical sockets + +Mesh is one Sketchfab Tube / Para_Coat — identify by sockets: + +| Part | Socket(s) | Debug color | +|------|-----------|-------------| +| **Board** | deckCenter, footL/R, mastBase | blue | +| **Front** | bowTip | green | +| **Sail** | sailPeak, sailRail, sailBoomL/R | amber | +| **Engine** | engineMount (stern −Z) | red | + +`settings.walk.debugParts = true` · `scooter.describeParts()` + +Treat the board as a **tiny boat**: + +| Socket | Role | +|--------|------| +| `footL` / `footR` | Feet planted on deck (IK) | +| `sailRail` / `sailBoomL/R` | Hands on sail bar (boom IK) | +| `deckCenter` | **Vehicle seat** — character reparented until dismount | +| `mastBase` / sail mesh | Sail deploys from **back** mid-frontflip | + +**Body:** Bip001 stays **rigged** (one mixer). **Ragdoll-lite** = RideIK + soft hip/wave lean (not a second physics body). Full Rapier ragdoll is out of scope for lab. + +**Back slot:** same inventory family as glider/parachute (`settings.walk.backSlot = 'windsurf'`) — deployable utility vehicle. + +--- + +## Stroke semantics + +`settings.mode` decides what a finished PathDrawer stroke means: + +| mode | Receiver | +|------|----------| +| `casting` | `AbilityManager` (staff element path) | +| `walk` | `WalkController` four-phase sequence over the same curve | + +### Four-phase sequence + +| Phase | What happens | +|-------|----------------| +| **leap** | Parabolic / frontflip arc to path head. At `walk.tuck` IK weight ramps so hands/feet seek board sockets before landing. Sail deploys mid-flip (`sailDeployAt`). | +| **ride** | Board + mast + boom + sail live. Rider on deck; path follow by arc length; bank with lean. | +| **freeride** | WASD boat: **W thrust**, release W = **water coast** (low drag), A/D turn, **Space hop**, soft wave body. | +| **ranged freeride** | Staff/bow/wand: **non-focus** LMB path cast (unlocked cursor) on board. | +| **dismount** | **E** · IK off · board removed · land loco. | + +### Apply order (hard) + +``` +walk.update → character.update (mixer) → walk.applyRiderIk +``` + +### Parenting (hard) + +While mounted, **windsurf vehicle owns the player transform**: + +``` +HoverboardRide.group (world XZ/Y + yaw) + ├ boardRoot (bank / deck height / birth SCALE — visual only) + │ ├ mesh + │ └ socketGroup (footL/R, sail*, deckCenter for IK targets) + └ seatRoot RideSeat (bank match, scale ALWAYS 1) + └ character.root (local stand only — until dismount) +``` + +**Why seat is not under boardRoot:** birth/death sets `boardRoot.scale` from 0.01→1. +Parenting the hero under that tree multiplies SI body by scale → micro hero / “ripped off screen”. + +- `forceFullSize()` before mount; `seatRoot.attach(character.root)` +- **Do not** write world XZ to `character.root.position` while `_rideParented` +- Camera / dust use `character.getWorldPosition()` / `position` getter (world) +- Dismount: `scene.attach(root)` once, then restore world feet +- **RideIK hip drop is absolute vs bind Y** — never `hips.position.y -=` each frame + (rotation-only packs do not restore bone.position) + +Sockets (manifest SI, **travel frame +Z forward**): + +| Socket | Stance | +|--------|--------| +| `footL` | Port aft (staggered — no foot cross) | +| `footR` | Starboard forward | +| `sailBoomL` / `sailBoomR` | Hand grips ~1.05 m height (elbows out) | +| `sailRail` | optional primary · prefer BoomR for R hand | +| `deckCenter` | Seat / hip pad | + +**Art yaw:** `artYawDeg: 0` on mesh only (travel +Z = package nose). Sockets stay travel-frame. +**Spawn:** `HoverboardRide.spawn(pos, yaw)` sets `group.rotation.y` immediately so land is not stern-first. + +### Back-slot equip (equipment pattern) + +| State | Mesh | Code | +|-------|------|------| +| Land + equipped | **Shrunk** `back_fly_windsurf.glb` on **quiver bone** (Spine1 / Xtra_quiver parent) | `BackSlotEquip` | +| Stow size | **~0.58 m** longest axis (quiver-class back item, not full board) | `stowLengthM` | +| Sail look | Cloth PBR + **vertex wind** (`SailCloth`) — no Rapier soft-body | `applySailClothMaterials` | +| Deployed vehicle | Stow **hidden** · full `windsurf_package.glb` vehicle | `setBackSlotDeployed(true)` | +| Get-off | Vehicle gone · stow **shown** + cloth wind resumes | `setBackSlotDeployed(false)` | + +Same attach family as `WeaponMeshAttach` (hands) — **quiver-family** back bone, SI length cap, catalog slot **Back**. +Settings: `settings.walk.backSlot = 'windsurf'`. + +### Ocean wind indications (soft ambient) + +Reuses **wind attack** primitives (`WindRibbonMaterial` silk + residual motes), not a new VFX stack. + +| Setting | Default | Role | +|---------|---------|------| +| `walk.oceanWindIndicators` | true | Enable ambient gusts over sea | +| `walk.oceanWindYaw` | 0.75 rad | Prevailing direction | +| `walk.oceanWindSpeed` | 4 m/s | Soft advection | +| `walk.oceanWindMoteRate` | 8 /s | Nearly invisible dust | + +Opacity ~**0.07**, low glow — readable as air over water, not a cast. Code: `effects/OceanWindIndicators.js`. + +### Materials / textures (practice rules) + +| Rule | Value | +|------|--------| +| baseColor map | `colorSpace = SRGBColorSpace`, `flipY = false` (glTF) | +| metal/rough / normal | linear data maps, flipY false | +| Sail / cloth | `metalness ≤ 0.08`, `roughness ≥ 0.72`, **doubleSide** | +| Cloth motion | shader vertex displace along normal (UV.y stronger at top) | +| Physics | **visual only** — not a second cloth/soft-body system | +| Scale | SI from human 1.8 m; stow never leaves author cm as metres | + +## Deploy sequence + +1. **Space** in Walk/Surf mode (or path start) → **frontflip** (`playFrontflip`) +2. Mid-flip (`sailDeployAt`) → board/sail **spawns from back** (`HoverboardRide.spawn`) +3. Land on deck → reparent to seat · RideIK feet + hands +4. **Path ride** if curve drawn, else **freeride** WASD + +--- + +## Controls (freeride) + +| Input | Action | +|-------|--------| +| **Space** (not riding) | Deploy vehicle (frontflip + board) | +| **WASD** | Boat thrust / turn (tslda-like) while mounted | +| **Space** (mounted) | Wave hop (board stays parented) | +| **E** | **Get off** — unparent, remove windsurfer, restore land controller | +| **1–4 / F** | Skills if staff or bow equipped (`skillsWhileRide`) | +| **M** | Leave walk mode → hard cancel (board removed) | +| **Draw path** | Path-follow course; end → freeride if `freerideAfterPath` | + +### Get-off contract (hard) + +``` +requestDismount / cancel: + 1. seat unparent → scene.attach(character.root) [world feet kept] + 2. setRideActive(false) · RideIK weight → 0 + 3. HoverboardRide.release() fade OR cancel() instant hide + 4. character.restoreFromRide({ x,y,z,yaw }) [land loco normal] + 5. physics.setPlayerFeet(land) + 6. phase = idle · vehicle not in scene +``` + +--- + +## Soft ocean body + +- `StageWater.sampleHeight(x, z, t)` — CPU wave sample +- Board Y follows water + Space jump ballistic +- Soft hip drop + lean from turn rate + wave slope + +--- + +## Extend pattern + +``` +1. Sockets only in ride.manifest.json (no hardcode positions in IK) +2. Freeride physics knobs in settings.walk.freeride* +3. Keep one mixer + RideIK post-mixer only +4. Skills while ride: DrcCombatController._allowRideSkill (pack gate) +5. Do not invent a second boat controller package +``` + +--- + +## Related + +- `docs/CASTING_LAB_SSOT.md` +- `docs/ANIM_LIBRARY_SSOT.md` (jump / flip channels) +- Animator ride / tslda boat velocity model diff --git a/docs/play-stack/t0-gun.play-stack.json b/docs/play-stack/t0-gun.play-stack.json new file mode 100644 index 0000000..af957c0 --- /dev/null +++ b/docs/play-stack/t0-gun.play-stack.json @@ -0,0 +1,93 @@ +{ + "$schema": "casting-t0-weapon-play-stack", + "id": "t0-gun", + "product": "weapon", + "note": "Checklist only. Catalog rows + skill ids stay ObjectStore / t0-weapons SSOT. Do not invent skills.", + "docs": [ + "docs/T0_WEAPON_PLAY_STACK_SSOT.md", + "docs/T0_WEAPONS_SSOT.md", + "docs/WEAPON_SKILL_PRODUCTION_SSOT.md" + ], + "catalog": { + "t0Url": "https://info.grudge-studio.com/api/v1/t0-weapons.json", + "skillsBrowse": "https://info.grudge-studio.com/WEAPON_SKILLS.html", + "verified": false + }, + "layers": { + "identity": { + "done": false, + "weaponType": null, + "tier": 0 + }, + "mesh": { + "done": false, + "modelUrl": null, + "meshSlot": null + }, + "icon": { + "done": false, + "iconUrl": null + }, + "animPack": { + "done": false, + "pack": "pistol" + }, + "locomotion": { + "done": false, + "roles": [ + "idle", + "walk", + "run", + "jump", + "fall", + "roll", + "dodge" + ] + }, + "traversal": { + "done": false, + "note": "Usually N/A for T0; back/ride separate (windsurf / wings)" + }, + "skills": { + "done": false, + "slot1": null, + "slot2": null, + "slot3Options": [], + "scaffoldCmd": "node scripts/scaffold-weapon-skill.mjs --id --weapon TYPE" + }, + "productionOverrides": { + "done": false, + "paths": [] + }, + "projectiles": { + "done": false, + "meshUrls": [] + }, + "dualLoadout": { + "done": false, + "note": "Weapon 1/2 paperdoll · combat Tap Q swap" + }, + "prefabExport": { + "done": false + }, + "smoke": { + "done": false, + "url": "https://casting-abilities-threejs.vercel.app/?t0=t0-gun" + }, + "deploy": { + "done": false, + "vercel": false, + "skillDo": false + } + }, + "checklist": { + "catalogIdVerified": false, + "equipLive": false, + "animPackBound": false, + "skillsFire": false, + "projectilesSI": false, + "dualQSwap": false, + "readinessGreen": false, + "smokeCastingLab": false + } +} diff --git a/docs/play-stack/t0-sword.play-stack.json b/docs/play-stack/t0-sword.play-stack.json new file mode 100644 index 0000000..a5bdfdd --- /dev/null +++ b/docs/play-stack/t0-sword.play-stack.json @@ -0,0 +1,93 @@ +{ + "$schema": "casting-t0-weapon-play-stack", + "id": "t0-sword", + "product": "weapon", + "note": "Checklist only. Catalog rows + skill ids stay ObjectStore / t0-weapons SSOT. Do not invent skills.", + "docs": [ + "docs/T0_WEAPON_PLAY_STACK_SSOT.md", + "docs/T0_WEAPONS_SSOT.md", + "docs/WEAPON_SKILL_PRODUCTION_SSOT.md" + ], + "catalog": { + "t0Url": "https://info.grudge-studio.com/api/v1/t0-weapons.json", + "skillsBrowse": "https://info.grudge-studio.com/WEAPON_SKILLS.html", + "verified": false + }, + "layers": { + "identity": { + "done": false, + "weaponType": null, + "tier": 0 + }, + "mesh": { + "done": false, + "modelUrl": null, + "meshSlot": null + }, + "icon": { + "done": false, + "iconUrl": null + }, + "animPack": { + "done": false, + "pack": "sword_shield" + }, + "locomotion": { + "done": false, + "roles": [ + "idle", + "walk", + "run", + "jump", + "fall", + "roll", + "dodge" + ] + }, + "traversal": { + "done": false, + "note": "Usually N/A for T0; back/ride separate (windsurf / wings)" + }, + "skills": { + "done": false, + "slot1": null, + "slot2": null, + "slot3Options": [], + "scaffoldCmd": "node scripts/scaffold-weapon-skill.mjs --id --weapon TYPE" + }, + "productionOverrides": { + "done": false, + "paths": [] + }, + "projectiles": { + "done": false, + "meshUrls": [] + }, + "dualLoadout": { + "done": false, + "note": "Weapon 1/2 paperdoll · combat Tap Q swap" + }, + "prefabExport": { + "done": false + }, + "smoke": { + "done": false, + "url": "https://casting-abilities-threejs.vercel.app/?t0=t0-sword" + }, + "deploy": { + "done": false, + "vercel": false, + "skillDo": false + } + }, + "checklist": { + "catalogIdVerified": false, + "equipLive": false, + "animPackBound": false, + "skillsFire": false, + "projectilesSI": false, + "dualQSwap": false, + "readinessGreen": false, + "smokeCastingLab": false + } +} diff --git a/docs/play-stack/t0-wand.play-stack.json b/docs/play-stack/t0-wand.play-stack.json new file mode 100644 index 0000000..c4105a1 --- /dev/null +++ b/docs/play-stack/t0-wand.play-stack.json @@ -0,0 +1,93 @@ +{ + "$schema": "casting-t0-weapon-play-stack", + "id": "t0-wand", + "product": "weapon", + "note": "Checklist only. Catalog rows + skill ids stay ObjectStore / t0-weapons SSOT. Do not invent skills.", + "docs": [ + "docs/T0_WEAPON_PLAY_STACK_SSOT.md", + "docs/T0_WEAPONS_SSOT.md", + "docs/WEAPON_SKILL_PRODUCTION_SSOT.md" + ], + "catalog": { + "t0Url": "https://info.grudge-studio.com/api/v1/t0-weapons.json", + "skillsBrowse": "https://info.grudge-studio.com/WEAPON_SKILLS.html", + "verified": false + }, + "layers": { + "identity": { + "done": false, + "weaponType": null, + "tier": 0 + }, + "mesh": { + "done": false, + "modelUrl": null, + "meshSlot": null + }, + "icon": { + "done": false, + "iconUrl": null + }, + "animPack": { + "done": false, + "pack": "magic" + }, + "locomotion": { + "done": false, + "roles": [ + "idle", + "walk", + "run", + "jump", + "fall", + "roll", + "dodge" + ] + }, + "traversal": { + "done": false, + "note": "Usually N/A for T0; back/ride separate (windsurf / wings)" + }, + "skills": { + "done": false, + "slot1": null, + "slot2": null, + "slot3Options": [], + "scaffoldCmd": "node scripts/scaffold-weapon-skill.mjs --id --weapon TYPE" + }, + "productionOverrides": { + "done": false, + "paths": [] + }, + "projectiles": { + "done": false, + "meshUrls": [] + }, + "dualLoadout": { + "done": false, + "note": "Weapon 1/2 paperdoll · combat Tap Q swap" + }, + "prefabExport": { + "done": false + }, + "smoke": { + "done": false, + "url": "https://casting-abilities-threejs.vercel.app/?t0=t0-wand" + }, + "deploy": { + "done": false, + "vercel": false, + "skillDo": false + } + }, + "checklist": { + "catalogIdVerified": false, + "equipLive": false, + "animPackBound": false, + "skillsFire": false, + "projectilesSI": false, + "dualQSwap": false, + "readinessGreen": false, + "smokeCastingLab": false + } +} diff --git a/index.html b/index.html index 6cbe650..cff68c7 100644 --- a/index.html +++ b/index.html @@ -3,8 +3,13 @@ - Bending Sandbox — Elemental VFX Playground - + Grudge Warlords · Casting + + + + + + @@ -19,9 +24,10 @@ -

Bending Sandbox

+

Grudge Warlords

+

Casting

-

Summoning the elements…

+

Loading Toon RTS play kit…

diff --git a/package-lock.json b/package-lock.json index 4d96db2..a5d1dbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,28 @@ { - "name": "CastingAbilities", + "name": "casting-abilities", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "casting-abilities", + "version": "1.0.0", "dependencies": { + "@dimforge/rapier3d-compat": "^0.19.3", "lil-gui": "^0.21.0", - "three": "^0.185.1" + "three": "^0.185.1", + "xstate": "^5.32.5" }, "devDependencies": { "vite": "^8.1.5" } }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.19.3", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.19.3.tgz", + "integrity": "sha512-mMVdSj1PRTT108s9Swbu2GQOmHbn8kbJANRV5xfczL3s0T4vkgZAuoMRgvBzQcHanpKusbC0ZJj6z3mC3aj3vg==", + "license": "Apache-2.0" + }, "node_modules/@emnapi/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", @@ -880,6 +891,16 @@ "optional": true } } + }, + "node_modules/xstate": { + "version": "5.32.5", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.32.5.tgz", + "integrity": "sha512-ULazi1oe6wGrXl0Frb6otSlkm5HLifbbVTkMk5kkSKqz4TkxJaVpnl6jOJwKeid3ORPxYyZQgNLUSYX9q65SIA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/xstate" + } } } } diff --git a/package.json b/package.json index e01f0a3..336665f 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,59 @@ { "name": "casting-abilities", - "version": "1.0.0", + "version": "1.2.0", "private": true, - "description": "Avatar-inspired elemental bending sandbox built with Three.js, custom GLSL and a real-time VFX editor.", + "description": "Grudge Studio casting + DRC combat — three@0.185, Rapier CCT, production weapon skills, three-layer terrain, shared glTF pipeline. · casting master merge 1.2.0", "type": "module", + "exports": { + ".": "./src/sdk/castingLabSdk.js", + "./sdk": "./src/sdk/castingLabSdk.js", + "./contract": "./public/api/v1/casting-lab-contract.json" + }, + "files": [ + "src/sdk", + "src/loaders/gltfPipeline.js", + "src/combat/weaponSkillProduction.js", + "src/combat/skillStatusSystem.js", + "src/combat/elementalLinearCast.js", + "src/combat/skillDelivery.js", + "src/combat/staffWeaponSkillsBind.js", + "src/combat/elementWeaponSkills.js", + "src/vfx/staffOrbVfx.js", + "src/vfx/elementAttackVfx.js", + "src/world/terrainLayers.js", + "src/world/IslandHeightfield.js", + "src/world/terrainGround.js", + "src/world/StylizedGrassLayer.js", + "src/world/GrowingForest.js", + "src/config/worldScale.js", + "public/api/v1/casting-lab-contract.json", + "docs/CASTING_SDK_EXPORT_SSOT.md", + "docs/SSOT_INDEX.md" + ], "scripts": { "dev": "vite", "build": "vite build", - "preview": "vite preview" + "preview": "vite preview", + "deploy:prod": "npm run build && npx vercel --prod --yes", + "split:orbs": "node scripts/split-gd-orbs-and-charge.mjs", + "split:elements": "node scripts/split-element-attack-meshes.mjs", + "scaffold:skill": "node scripts/scaffold-weapon-skill.mjs" }, "dependencies": { + "@dimforge/rapier3d-compat": "^0.19.3", "lil-gui": "^0.21.0", - "three": "^0.185.1" + "three": "^0.185.1", + "xstate": "^5.32.5" }, "devDependencies": { "vite": "^8.1.5" + }, + "peerDependencies": { + "three": "^0.185.0" + }, + "peerDependenciesMeta": { + "three": { + "optional": false + } } } diff --git a/public/angtexture.png b/public/angtexture.png deleted file mode 100644 index 649cbb5..0000000 Binary files a/public/angtexture.png and /dev/null differ diff --git a/public/anim/locomotion/fall/fall-idle.fbx b/public/anim/locomotion/fall/fall-idle.fbx new file mode 100644 index 0000000..ae7c71c Binary files /dev/null and b/public/anim/locomotion/fall/fall-idle.fbx differ diff --git a/public/anim/locomotion/fall/fall-loop.fbx b/public/anim/locomotion/fall/fall-loop.fbx new file mode 100644 index 0000000..0818559 Binary files /dev/null and b/public/anim/locomotion/fall/fall-loop.fbx differ diff --git a/public/anim/locomotion/fall/fall-to-landing.fbx b/public/anim/locomotion/fall/fall-to-landing.fbx new file mode 100644 index 0000000..dd344f8 Binary files /dev/null and b/public/anim/locomotion/fall/fall-to-landing.fbx differ diff --git a/public/anim/locomotion/fall/fall-to-roll.fbx b/public/anim/locomotion/fall/fall-to-roll.fbx new file mode 100644 index 0000000..c1548ec Binary files /dev/null and b/public/anim/locomotion/fall/fall-to-roll.fbx differ diff --git a/public/anim/locomotion/fall/falling.fbx b/public/anim/locomotion/fall/falling.fbx new file mode 100644 index 0000000..e4adda8 Binary files /dev/null and b/public/anim/locomotion/fall/falling.fbx differ diff --git a/public/anim/melee/2hand-idle.fbx b/public/anim/melee/2hand-idle.fbx new file mode 100644 index 0000000..757afcd Binary files /dev/null and b/public/anim/melee/2hand-idle.fbx differ diff --git a/public/anims/baked/pistol/drawing-gun.json b/public/anims/baked/pistol/drawing-gun.json new file mode 100644 index 0000000..5928698 --- /dev/null +++ b/public/anims/baked/pistol/drawing-gun.json @@ -0,0 +1 @@ +{"name":"pistol/drawing-gun","duration":2,"tracks":[{"name":"Bip001 Pelvis.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.057442326098680496,-0.15881440043449402,-0.005046594887971878,0.9856231212615967,-0.05719399452209473,-0.15994738042354584,-0.0053848098032176495,0.9854525327682495,-0.05633072182536125,-0.16105574369430542,-0.005669484380632639,0.9853201508522034,-0.05508367717266083,-0.1614990383386612,-0.005913667846471071,0.9853166341781616,-0.053968507796525955,-0.16104978322982788,-0.006068362854421139,0.9854509234428406,-0.05337166041135788,-0.1599973440170288,-0.006165702827274799,0.9856542348861694,-0.053246598690748215,-0.15902911126613617,-0.0064057838171720505,0.9858161807060242,-0.05326816439628601,-0.15901513397693634,-0.00695612421259284,0.9858134984970093,-0.05319353565573692,-0.16043972969055176,-0.007783001754432917,0.9855805039405823,-0.05309281125664711,-0.16314345598220825,-0.008737594820559025,0.9851340055465698,-0.0531952939927578,-0.1668645143508911,-0.009816509671509266,0.9844948649406433,-0.05314745754003525,-0.17178753018379211,-0.011343736201524734,0.9836339354515076,-0.05173134058713913,-0.17844147980213165,-0.013737872242927551,0.9824936389923096,-0.04845954477787018,-0.18686893582344055,-0.016980044543743134,0.9810419678688049,-0.04525947570800781,-0.19567371904850006,-0.02025480754673481,0.979414701461792,-0.044774219393730164,-0.20257781445980072,-0.022559821605682373,0.9779818654060364,-0.0467921644449234,-0.20686937868595123,-0.02406252734363079,0.9769526720046997,-0.04802234470844269,-0.2104620784521103,-0.026158951222896576,0.9760713577270508,-0.045754846185445786,-0.21494439244270325,-0.029707083478569984,0.9751014709472656,-0.04034919664263725,-0.2189306914806366,-0.03393016383051872,0.9743151664733887,-0.03401270881295204,-0.22058869898319244,-0.03746558725833893,0.9740534424781799,-0.02829167991876602,-0.22075532376766205,-0.0396634079515934,0.9741116166114807,-0.022883020341396332,-0.22102676331996918,-0.040660109370946884,0.974151074886322,-0.016921045258641243,-0.22097615897655487,-0.04084848240017891,0.9742764830589294,-0.011949053034186363,-0.21988196671009064,-0.04012010991573334,0.9746279120445251,-0.012040145695209503,-0.2186782956123352,-0.03771095350384712,0.9749937057495117,-0.01842535473406315,-0.21789546310901642,-0.03347602114081383,0.9752237796783447,-0.02554015815258026,-0.21612247824668884,-0.02890574373304844,0.9756040573120117,-0.02776278182864189,-0.21298488974571228,-0.025558017194271088,0.9763265252113342,-0.026277288794517517,-0.21037206053733826,-0.02339603193104267,0.9769880771636963,-0.02509285695850849,-0.20912884175777435,-0.021454857662320137,0.9773306250572205,-0.024957187473773956,-0.20755185186862946,-0.01948375627398491,0.9777114987373352,-0.02429179474711418,-0.20406882464885712,-0.018100835382938385,0.9784877300262451,-0.023123852908611298,-0.19915515184402466,-0.017480289563536644,0.979539155960083,-0.02280467562377453,-0.1940155029296875,-0.016993196681141853,0.9805861115455627,-0.02365454100072384,-0.18908177316188812,-0.01617351546883583,0.9815431833267212,-0.025110075250267982,-0.1843123883008957,-0.015131043270230293,0.9824303984642029,-0.026835521683096886,-0.17981433868408203,-0.014094613492488861,0.9832334518432617,-0.028520697727799416,-0.17560699582099915,-0.013218550011515617,0.98395836353302,-0.029865631833672523,-0.17133808135986328,-0.012542441487312317,0.984679639339447,-0.031195512041449547,-0.16673898696899414,-0.011791110038757324,0.9854369163513184,-0.03306489437818527,-0.1621740758419037,-0.01066176500171423,0.9861503839492798,-0.035394541919231415,-0.15834711492061615,-0.009349025785923004,0.9867046475410461,-0.03772570192813873,-0.15569064021110535,-0.008184516802430153,0.987051248550415,-0.039822161197662354,-0.1542452871799469,-0.007039419841021299,0.9872046709060669,-0.041520968079566956,-0.15369941294193268,-0.005804036743938923,0.9872278571128845,-0.04263191670179367,-0.15342383086681366,-0.004872092045843601,0.9872283935546875,-0.043326541781425476,-0.15290133655071259,-0.0045866528525948524,0.9872806072235107,-0.044088687747716904,-0.15215207636356354,-0.004794251173734665,0.9873616099357605,-0.04522043094038963,-0.15152673423290253,-0.005138371605426073,0.9874048829078674,-0.0466005839407444,-0.15134845674037933,-0.00548408692702651,0.9873661994934082,-0.047901418060064316,-0.15175572037696838,-0.00590366730466485,0.9872390031814575,-0.04904999956488609,-0.15261061489582062,-0.0064262645319104195,0.9870474934577942,-0.04991089180111885,-0.1530967503786087,-0.006930303759872913,0.9869256615638733,-0.05076483264565468,-0.15343637764453888,-0.007412351202219725,0.9868258833885193,-0.0514955073595047,-0.15360002219676971,-0.007826324552297592,0.98675936460495,-0.05195924639701843,-0.15368600189685822,-0.007990952581167221,0.9867203235626221,-0.05230975151062012,-0.15373781323432922,-0.008121117949485779,0.9866926670074463,-0.052532948553562164,-0.15376435220241547,-0.008212544023990631,0.9866759181022644,-0.0526508130133152,-0.15377400815486908,-0.00826626643538475,0.9866676926612854,-0.0526852086186409,-0.1537754386663437,-0.008283931761980057,0.9866654872894287]},{"name":"Bip001 Spine.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.058890122920274734,-0.002268796321004629,-0.0032555856741964817,0.9982565641403198,0.05871879681944847,-0.0020294629503041506,-0.0031194668263196945,0.998267650604248,0.05842316150665283,-0.0020413121674209833,-0.0030580402817577124,0.9982851147651672,0.058217853307724,-0.0026588805485516787,-0.0032008786220103502,0.998295247554779,0.05832495167851448,-0.00400936184450984,-0.003583189332857728,0.9982831478118896,0.058835219591856,-0.005957160145044327,-0.004130032379180193,0.9982413649559021,0.059694256633520126,-0.008260766044259071,-0.004752474837005138,0.9981712102890015,0.06079007685184479,-0.010764963924884796,-0.005402964074164629,0.9980778694152832,0.06206434220075607,-0.013510475866496563,-0.0060434164479374886,0.9979624152183533,0.0635194331407547,-0.016612784937024117,-0.006667663808912039,0.9978200197219849,0.06512366980314255,-0.02003181166946888,-0.007334659807384014,0.9976491332054138,0.06670374423265457,-0.0235043466091156,-0.008047311566770077,0.9974634647369385,0.06793283671140671,-0.02668599970638752,-0.00867172796279192,0.9972952604293823,0.06859517842531204,-0.029412323608994484,-0.009129418060183525,0.9971691370010376,0.0689290463924408,-0.03186628594994545,-0.009605797939002514,0.9970661997795105,0.06945008784532547,-0.03433110564947128,-0.010388609021902084,0.9969403743743896,0.0703270360827446,-0.036678217351436615,-0.011481493711471558,0.9967833161354065,0.07119390368461609,-0.038373302668333054,-0.012512537650763988,0.9966455698013306,0.07167178392410278,-0.03923937678337097,-0.013139027170836926,0.9965695142745972,0.07177021354436874,-0.039826858788728714,-0.013450340367853642,0.9965349435806274,0.07169510424137115,-0.04063224047422409,-0.013827580958604813,0.996502697467804,0.07150149345397949,-0.04138981178402901,-0.014498220756649971,0.996475875377655,0.07104627043008804,-0.04163159430027008,-0.01531972549855709,0.9964861273765564,0.07026036828756332,-0.041403889656066895,-0.01596054434776306,0.9965412616729736,0.06946345418691635,-0.0408245213329792,-0.016415562480688095,0.996613621711731,0.06924989074468613,-0.03964067995548248,-0.01712009310722351,0.9966644048690796,0.06975730508565903,-0.03788840398192406,-0.01827077567577362,0.9966767430305481,0.07013151049613953,-0.03630681708455086,-0.019414851441979408,0.9966877102851868,0.06947699934244156,-0.035296812653541565,-0.02016550675034523,0.9967549443244934,0.06792646646499634,-0.03422723710536957,-0.02055501751601696,0.9968911409378052,0.0661277174949646,-0.03234405443072319,-0.020568890497088432,0.9970746636390686,0.06433377414941788,-0.029704559594392776,-0.019948476925492287,0.9972867369651794,0.062486402690410614,-0.026845192536711693,-0.018550930544734,0.9975122213363647,0.06073208898305893,-0.024049971252679825,-0.016652997583150864,0.9977253675460815,0.05931732431054115,-0.02128019742667675,-0.01469118706882,0.9979041814804077,0.05825154483318329,-0.018503770232200623,-0.012885211035609245,0.9980472922325134,0.057411160320043564,-0.01582021825015545,-0.011248834431171417,0.9981619119644165,0.05674397200345993,-0.013349352404475212,-0.009743002243340015,0.9982519745826721,0.05621388927102089,-0.011160383000969887,-0.008310073055326939,0.9983217716217041,0.0557791069149971,-0.009300604462623596,-0.0069326371885836124,0.9983757734298706,0.055491574108600616,-0.007758989464491606,-0.005698248744010925,0.9984127283096313,0.055452048778533936,-0.006399407051503658,-0.004674968775361776,0.9984298944473267,0.055658288300037384,-0.00512758269906044,-0.0038258384447544813,0.9984293580055237,0.05599304288625717,-0.004027721006423235,-0.0031352504156529903,0.9984180927276611,0.05631854757666588,-0.0031317593529820442,-0.002613944699987769,0.9984045028686523,0.05652524158358574,-0.0023058957885950804,-0.0022002514451742172,0.998396098613739,0.05657251179218292,-0.0014795981114730239,-0.0018155560828745365,0.9983957409858704,0.056522760540246964,-0.0007553955656476319,-0.00145837536547333,0.9983999729156494,0.056471195071935654,-0.00024226753157563508,-0.0011439042864367366,0.9984035491943359,0.05644981935620308,0.0000497000219183974,-0.0008351464057341218,0.9984050989151001,0.05645638331770897,0.0002062451676465571,-0.0005137253901921213,0.9984049201011658,0.056523073464632034,0.0003592244756873697,-0.00024161195324268192,0.9984012246131897,0.05671149492263794,0.0005860048113390803,-0.00006674982432741672,0.9983904361724854,0.05706048011779785,0.0008349301642738283,0.00006367614696500823,0.998370349407196,0.05749799683690071,0.0009713975014165044,0.00026390940183773637,0.998345136642456,0.05787258967757225,0.0009538338053971529,0.0005678182351402938,0.998323380947113,0.057872869074344635,0.0009552852134220302,0.0005656819557771087,0.9983233213424683,0.057873137295246124,0.0009564271313138306,0.0005631340900436044,0.9983233213424683,0.05787326768040657,0.0009574326104484499,0.0005605297046713531,0.9983233213424683,0.0578734464943409,0.000958221557084471,0.0005582850426435471,0.9983233213424683,0.057873714715242386,0.0009583779610693455,0.0005574403912760317,0.9983233213424683]},{"name":"Bip001 Spine1.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.04578443616628647,-0.0053108143620193005,-0.0059211705811321735,0.998919665813446,0.045440252870321274,-0.004833170212805271,-0.005650111008435488,0.9989393949508667,0.04484698548913002,-0.0048602852039039135,-0.005505177658051252,0.9989668726921082,0.04443510249257088,-0.006107335444539785,-0.005727709271013737,0.9989771842956543,0.04464952275156975,-0.008829900994896889,-0.00638166256248951,0.9989433288574219,0.045671164989471436,-0.012755066156387329,-0.007318923249840736,0.9988482594490051,0.0473906435072422,-0.017399171367287636,-0.008372509852051735,0.9986897706985474,0.04958323761820793,-0.022453470155596733,-0.009452064521610737,0.9984728097915649,0.05213215574622154,-0.027999715879559517,-0.010472378693521023,0.9981926679611206,0.05504269152879715,-0.034266162663698196,-0.011421260423958302,0.9978305101394653,0.058252427726984024,-0.04117247462272644,-0.012437143363058567,0.997374951839447,0.0614139661192894,-0.04819416627287865,-0.013541852124035358,0.9968562126159668,0.06387213617563248,-0.05463847145438194,-0.014466699212789536,0.996356189250946,0.06519506126642227,-0.06017236411571503,-0.015059487894177437,0.9959428310394287,0.0658617913722992,-0.06516565382480621,-0.01571604423224926,0.9955745339393616,0.06690603494644165,-0.07018890231847763,-0.017046496272087097,0.9951414465904236,0.06866380572319031,-0.0749816820025444,-0.01905900426208973,0.9946355223655701,0.07039954513311386,-0.07846564054489136,-0.02097601629793644,0.9942067265510559,0.07135386019945145,-0.0802706778049469,-0.02207830734550953,0.9939706921577454,0.07154736667871475,-0.08148286491632462,-0.02252611517906189,0.9938481450080872,0.07139424979686737,-0.08310312777757645,-0.02309270203113556,0.9937119483947754,0.07100510597229004,-0.08462710678577423,-0.024268560111522675,0.9935832023620605,0.07009248435497284,-0.08512552827596664,-0.025782311335206032,0.9935672879219055,0.06851725280284882,-0.0846664234995842,-0.026955172419548035,0.993685245513916,0.06691985577344894,-0.08348987996578217,-0.027814488857984543,0.993869960308075,0.06649201363325119,-0.08114514499902725,-0.029348375275731087,0.9940487742424011,0.06751109659671783,-0.07772649079561234,-0.03197638690471649,0.994172215461731,0.06826566904783249,-0.07464755326509476,-0.03459794819355011,0.9942688345909119,0.06695979088544846,-0.07266666740179062,-0.03626735135912895,0.9944449067115784,0.06385841220617294,-0.07054173201322556,-0.03712127357721329,0.9947702884674072,0.06025892496109009,-0.066778264939785,-0.037288468331098557,0.9952482581138611,0.056667931377887726,-0.061461299657821655,-0.03628546744585037,0.9958386421203613,0.05296868458390236,-0.055638350546360016,-0.033762793987989426,0.9964731931686401,0.04945487156510353,-0.04989991709589958,-0.03024264983832836,0.9970704913139343,0.04662122204899788,-0.044208548963069916,-0.0266082976013422,0.9975790977478027,0.04448643699288368,-0.03851746767759323,-0.02328609861433506,0.9979955554008484,0.0428026057779789,-0.033025145530700684,-0.020279692485928535,0.998331606388092,0.04146617278456688,-0.027967125177383423,-0.017515879124403,0.9985948204994202,0.040405768901109695,-0.02347586490213871,-0.014886743389070034,0.9987965822219849,0.03953651711344719,-0.01964406482875347,-0.012339102104306221,0.9989488124847412,0.03896171227097511,-0.016455426812171936,-0.010036895051598549,0.9990547895431519,0.03888363763689995,-0.013645623810589314,-0.008139173500239849,0.9991174340248108,0.03929821029305458,-0.011027751490473747,-0.006584994960576296,0.999144971370697,0.03996960446238518,-0.008770866319537163,-0.005323190242052078,0.9991482496261597,0.040621399879455566,-0.006941033527255058,-0.004363900516182184,0.9991409778594971,0.04103485122323036,-0.0052620102651417255,-0.003603628370910883,0.9991373419761658,0.041129034012556076,-0.003585304832085967,-0.0028987573459744453,0.9991431832313538,0.041028980165719986,-0.002111003501340747,-0.0022401828318834305,0.999153196811676,0.040926091372966766,-0.0010599155211821198,-0.0016598764341324568,0.9991602301597595,0.040885236114263535,-0.0004516189801506698,-0.0010936162434518337,0.9991631507873535,0.040901195257902145,-0.00011494869249872863,-0.0005028992309235036,0.9991630911827087,0.0410369336605072,0.00020651648810599,-0.0000022401147816708544,0.9991576075553894,0.04141489043831825,0.0006645774701610208,0.0003181016363669187,0.9991417527198792,0.04211284965276718,0.0011647917563095689,0.0005661225877702236,0.9991120100021362,0.04298820346593857,0.0014473773771896958,0.0009590064873918891,0.9990740418434143,0.04373813420534134,0.001431804383173585,0.0015631291316822171,0.9990407824516296,0.043738335371017456,0.0014318686444312334,0.0015634819865226746,0.9990407824516296,0.04373854398727417,0.0014318895991891623,0.0015637228498235345,0.9990407824516296,0.04373832419514656,0.0014319129986688495,0.0015638326294720173,0.9990407824516296,0.04373832792043686,0.0014318982139229774,0.0015638781478628516,0.9990407824516296,0.043738771229982376,0.0014318327885121107,0.001563903409987688,0.9990407228469849]},{"name":"Bip001 Spine2.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.04578439146280289,-0.005310717970132828,-0.0059211705811321735,0.998919665813446,0.04544024169445038,-0.004833147395402193,-0.005650110077112913,0.9989393949508667,0.04484698176383972,-0.004860325716435909,-0.00550517812371254,0.9989668726921082,0.04443510249257088,-0.006107340566813946,-0.0057277069427073,0.9989771842956543,0.04464954137802124,-0.008829852566123009,-0.006381665356457233,0.9989433288574219,0.04567119479179382,-0.012755079194903374,-0.007318923715502024,0.9988482594490051,0.0473906435072422,-0.017399130389094353,-0.008372520096600056,0.9986897706985474,0.049583274871110916,-0.022453509271144867,-0.009452066384255886,0.9984728097915649,0.05213218182325363,-0.027999740093946457,-0.010472375899553299,0.9981926679611206,0.05504271760582924,-0.03426618129014969,-0.011421267874538898,0.9978305101394653,0.05825245752930641,-0.041172515600919724,-0.01243714801967144,0.997374951839447,0.06141400337219238,-0.048194143921136856,-0.013541857711970806,0.9968562126159668,0.06387211382389069,-0.05463842302560806,-0.014466705732047558,0.996356189250946,0.06519503146409988,-0.06017247959971428,-0.015059460885822773,0.9959428310394287,0.0658617913722992,-0.06516561657190323,-0.015716059133410454,0.9955745339393616,0.06690609455108643,-0.070188969373703,-0.01704651489853859,0.9951414465904236,0.06866380572319031,-0.0749814435839653,-0.019059021025896072,0.9946355223655701,0.07039950042963028,-0.07846558839082718,-0.020976020023226738,0.9942067265510559,0.07135390490293503,-0.08027087897062302,-0.022078316658735275,0.9939706921577454,0.07154733687639236,-0.0814829170703888,-0.022526094689965248,0.9938481450080872,0.07139428704977036,-0.08310304582118988,-0.023092718794941902,0.9937119483947754,0.07100512087345123,-0.08462701737880707,-0.024268554523587227,0.9935832023620605,0.07009255141019821,-0.0851256251335144,-0.02578234300017357,0.9935672879219055,0.0685172826051712,-0.08466653525829315,-0.026955172419548035,0.993685245513916,0.06691986322402954,-0.08348965644836426,-0.02781451679766178,0.9938700199127197,0.0664919838309288,-0.08114491403102875,-0.0293483417481184,0.9940487742424011,0.06751114130020142,-0.07772652059793472,-0.031976375728845596,0.994172215461731,0.06826557964086533,-0.07464752346277237,-0.03459794074296951,0.9942688345909119,0.06695982813835144,-0.07266679406166077,-0.03626738116145134,0.9944448471069336,0.06385848671197891,-0.07054194062948227,-0.03712128475308418,0.9947702884674072,0.060258898884058,-0.06677823513746262,-0.037288419902324677,0.9952482581138611,0.056667860597372055,-0.0614609569311142,-0.03628549352288246,0.9958387017250061,0.052968692034482956,-0.05563846230506897,-0.03376276418566704,0.9964731335639954,0.049454912543296814,-0.04990000277757645,-0.030242642387747765,0.9970704913139343,0.04662121832370758,-0.044208478182554245,-0.026608308777213097,0.9975790977478027,0.0444863922894001,-0.038517363369464874,-0.023286093026399612,0.9979955554008484,0.04280264675617218,-0.033025238662958145,-0.020279698073863983,0.998331606388092,0.04146619513630867,-0.02796712890267372,-0.017515886574983597,0.9985948204994202,0.04040578007698059,-0.02347584441304207,-0.014886741526424885,0.9987965822219849,0.03953650966286659,-0.01964395120739937,-0.012339112348854542,0.9989488124847412,0.03896171227097511,-0.016455449163913727,-0.010036898776888847,0.9990547895431519,0.03888364136219025,-0.013645624741911888,-0.008139173500239849,0.9991174340248108,0.039298199117183685,-0.011027774773538113,-0.00658499076962471,0.999144971370697,0.03996960073709488,-0.008770793676376343,-0.005323196295648813,0.9991482496261597,0.040621399879455566,-0.006940955761820078,-0.004363907966762781,0.9991409778594971,0.04103483632206917,-0.005262045655399561,-0.003603623015806079,0.9991373419761658,0.04112900793552399,-0.0035852878354489803,-0.0028987503610551357,0.9991431832313538,0.041028980165719986,-0.002110895235091448,-0.0022401930764317513,0.999153196811676,0.04092606529593468,-0.00105991680175066,-0.001659879693761468,0.9991602301597595,0.040885262191295624,-0.0004517066408880055,-0.0010936131002381444,0.9991631507873535,0.04090117663145065,-0.00011500472464831546,-0.0005028903833590448,0.9991630911827087,0.0410369336605072,0.00020649403450079262,-0.0000022368415102391737,0.9991576075553894,0.04141490161418915,0.0006645972025580704,0.00031810958171263337,0.9991417527198792,0.042112819850444794,0.0011648352956399322,0.0005661189206875861,0.9991120100021362,0.04298821836709976,0.0014473645715042949,0.000959005847107619,0.9990740418434143,0.043738141655921936,0.001431765384040773,0.0015631384449079633,0.9990407824516296,0.04373835772275925,0.0014318365138024092,0.001563491066917777,0.9990407824516296,0.04373855143785477,0.0014318819157779217,0.0015637315809726715,0.9990407824516296,0.04373832046985626,0.0014318092726171017,0.0015638459008187056,0.9990407824516296,0.04373832419514656,0.0014318693429231644,0.0015638768672943115,0.9990407824516296,0.04373873770236969,0.001431989949196577,0.00156388187315315,0.9990407824516296]},{"name":"Bip001 Neck.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.010281497612595558,0.034093163907527924,0.06307851523160934,0.9973730444908142,0.010925276204943657,0.034754157066345215,0.06286134570837021,0.9973571300506592,0.01126832328736782,0.03603532537817955,0.06380236893892288,0.9972480535507202,0.01036476157605648,0.03816298395395279,0.06724399328231812,0.9969525337219238,0.007907616905868053,0.04134918004274368,0.07344302535057068,0.9964104890823364,0.004526545759290457,0.04572450369596481,0.08173495531082153,0.9955943822860718,0.001418989966623485,0.05123694986104965,0.09133114665746689,0.9945005774497986,-0.0002946346066892147,0.05784188583493233,0.10124114155769348,0.9931789636611938,-0.0003428803465794772,0.0657377615571022,0.11013896763324738,0.991739809513092,0.0007143378024920821,0.07504686713218689,0.11751911044120789,0.9902306199073792,0.0023826160468161106,0.08537153154611588,0.12436708807945251,0.9885539412498474,0.004687672480940819,0.09605221450328827,0.13166320323944092,0.9866188764572144,0.007992521859705448,0.10655666887760162,0.13909876346588135,0.9844964742660522,0.012651086784899235,0.11659707129001617,0.14584101736545563,0.9823316335678101,0.0183554794639349,0.12614805996418,0.15116432309150696,0.980254590511322,0.023691799491643906,0.13509804010391235,0.15458360314369202,0.9784125685691833,0.0268784761428833,0.14292210340499878,0.15651045739650726,0.9769111275672913,0.027102245017886162,0.14927779138088226,0.15799766778945923,0.9757142663002014,0.02529313415288925,0.154535710811615,0.15936435759067535,0.9747214913368225,0.023767739534378052,0.15908122062683105,0.16033081710338593,0.9738697409629822,0.02428452856838703,0.16252855956554413,0.1613094061613083,0.9731258749961853,0.026419540867209435,0.16414158046245575,0.1630576252937317,0.9725079536437988,0.027993634343147278,0.16385997831821442,0.1654140055179596,0.9721134305000305,0.026894383132457733,0.16249294579029083,0.16715893149375916,0.9720754027366638,0.02248973585665226,0.16060860455036163,0.16750307381153107,0.9724411368370056,0.015976684167981148,0.15787506103515625,0.1664619743824005,0.9731960892677307,0.009993573650717735,0.15395419299602509,0.1641102433204651,0.9743028879165649,0.007487047463655472,0.149193674325943,0.16160134971141815,0.9754846096038818,0.009974983520805836,0.14407828450202942,0.16077616810798645,0.9763672351837158,0.016006547957658768,0.13878421485424042,0.16047336161136627,0.9771034121513367,0.021999334916472435,0.1330048143863678,0.15754571557044983,0.9782663583755493,0.025670049712061882,0.12592746317386627,0.15164034068584442,0.9800451397895813,0.027327673509716988,0.1171031966805458,0.14487048983573914,0.9821164011955261,0.02833639830350876,0.10715382546186447,0.13831058144569397,0.9841672778129578,0.02971394546329975,0.09701765328645706,0.13104060292243958,0.9861708879470825,0.031604692339897156,0.08717481046915054,0.12209337949752808,0.9881775379180908,0.033427756279706955,0.0779605433344841,0.11185948550701141,0.9900970458984375,0.034884124994277954,0.06983475387096405,0.10166090726852417,0.9917516112327576,0.03638984635472298,0.063062384724617,0.09263008087873459,0.9930350184440613,0.03792675584554672,0.05738425627350807,0.08506203442811966,0.9939985275268555,0.038487356156110764,0.052236270159482956,0.07858842611312866,0.9947934150695801,0.037467047572135925,0.04723535105586052,0.07262810319662094,0.995535135269165,0.035603832453489304,0.04239068552851677,0.06715663522481918,0.9962055087089539,0.033879756927490234,0.0380425862967968,0.06229054927825928,0.9967571496963501,0.03286515176296234,0.03446142002940178,0.056871723383665085,0.9972451329231262,0.03293633088469505,0.03161832317709923,0.04973325505852699,0.9977184534072876,0.0339883416891098,0.029378741979599,0.04228901118040085,0.9980948567390442,0.03535117954015732,0.0277028139680624,0.037305377423763275,0.9982941150665283,0.036633141338825226,0.026623396202921867,0.03530566394329071,0.9983500242233276,0.03840276971459389,0.026046965271234512,0.03422883152961731,0.9983361959457397,0.04086993262171745,0.025549937039613724,0.03262239694595337,0.9983049035072327,0.0428023561835289,0.02467035874724388,0.031275320798158646,0.9982891082763672,0.04311765357851982,0.023477988317608833,0.031261734664440155,0.9983047246932983,0.04195736348628998,0.022483086213469505,0.0325765423476696,0.998335063457489,0.04038185998797417,0.022130299359560013,0.03442462161183357,0.9983459115028381,0.03983014449477196,0.02230747602880001,0.03600853309035301,0.9983082413673401,0.03983019292354584,0.02230745181441307,0.036008529365062714,0.9983082413673401,0.0398300439119339,0.022307461127638817,0.03600848466157913,0.9983082413673401,0.039830807596445084,0.022307489067316055,0.03600852191448212,0.9983081817626953,0.039830826222896576,0.022307511419057846,0.036008499562740326,0.9983081817626953,0.03982976824045181,0.02230750024318695,0.03600843623280525,0.9983082413673401]},{"name":"Bip001 Head.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.05189119651913643,0.0716434046626091,-0.08233070373535156,0.9926712512969971,-0.052161961793899536,0.07235594838857651,-0.08258044719696045,0.9925845861434937,-0.05274348706007004,0.07419981807470322,-0.08378726243972778,0.9923166036605835,-0.05366143211722374,0.07785000652074814,-0.08662714064121246,0.9917436838150024,-0.05481601506471634,0.08353806287050247,-0.09101010113954544,0.9908247590065002,-0.05646425113081932,0.09113923460245132,-0.09649596363306046,0.9895423054695129,-0.059340450912714005,0.10039205849170685,-0.10289769619703293,0.9878320693969727,-0.06414496153593063,0.11106165498495102,-0.11022856831550598,0.9855964779853821,-0.07076188921928406,0.12303697317838669,-0.11832897365093231,0.9827781319618225,-0.07792839407920837,0.1362588256597519,-0.12694627046585083,0.979410707950592,-0.08393312990665436,0.1503906100988388,-0.1356598138809204,0.9756712317466736,-0.08775881677865982,0.1647074967622757,-0.1435389369726181,0.9718880653381348,-0.08970139175653458,0.1784307360649109,-0.14984335005283356,0.9683300852775574,-0.09064147621393204,0.19112254679203033,-0.15455301105976105,0.9650749564170837,-0.09111999720335007,0.20260299742221832,-0.15759609639644623,0.9621915817260742,-0.09139307588338852,0.21259251236915588,-0.15875746309757233,0.9598165154457092,-0.0915130153298378,0.22071610391139984,-0.15839113295078278,0.9580302834510803,-0.0915471762418747,0.22703585028648376,-0.15728306770324707,0.9567318558692932,-0.09244494140148163,0.23208078742027283,-0.15585026144981384,0.9556689262390137,-0.09540367871522903,0.23614948987960815,-0.1539541333913803,0.9546882510185242,-0.09963167458772659,0.2390621155500412,-0.15181627869606018,0.9538735151290894,-0.10294495522975922,0.24054625630378723,-0.1503133922815323,0.9533864259719849,-0.10446982830762863,0.24052715301513672,-0.14956094324588776,0.9533437490463257,-0.10514304786920547,0.23906758427619934,-0.1480218768119812,0.9538769125938416,-0.10595247894525528,0.2360534816980362,-0.1439569592475891,0.9551592469215393,-0.10734713822603226,0.23109722137451172,-0.13711291551589966,0.9572203159332275,-0.10994859039783478,0.22427664697170258,-0.12888625264167786,0.9596872329711914,-0.11380598694086075,0.21692077815532684,-0.12185875326395035,0.9618440866470337,-0.11726509034633636,0.2105102837085724,-0.11798696964979172,0.9633345007896423,-0.11817629635334015,0.20457519590854645,-0.11559975147247314,0.9647901654243469,-0.11624698340892792,0.1971556842327118,-0.11125081777572632,0.9670778512954712,-0.11361157149076462,0.18718624114990234,-0.10474278032779694,0.9700941443443298,-0.11229083687067032,0.17534588277339935,-0.09880305826663971,0.9730789065361023,-0.11236850172281265,0.16304481029510498,-0.09529635310173035,0.9755553603172302,-0.11273995041847229,0.1508539915084839,-0.09332671761512756,0.9776619672775269,-0.11228780448436737,0.1384461522102356,-0.09062869846820831,0.9798012971878052,-0.11040963232517242,0.12592342495918274,-0.08614501357078552,0.9821059107780457,-0.1076139286160469,0.11447171121835709,-0.08153510838747025,0.9842090606689453,-0.10534656792879105,0.10527409613132477,-0.07920999825000763,0.9856699705123901,-0.10379920154809952,0.09811390191316605,-0.07911290973424911,0.9865801930427551,-0.10128290206193924,0.09165581315755844,-0.07887902855873108,0.9874811768531799,-0.09735208004713058,0.08497962355613708,-0.07711024582386017,0.988612711429596,-0.09402007609605789,0.07853870093822479,-0.07536085695028305,0.989602267742157,-0.0924946740269661,0.0731620341539383,-0.07515416294336319,0.9901736974716187,-0.09233944863080978,0.06847932934761047,-0.07501590996980667,0.9905335307121277,-0.09325364232063293,0.06360986083745956,-0.07282169163227081,0.9909362196922302,-0.09482009708881378,0.05893772095441818,-0.06943122297525406,0.9913197159767151,-0.09606361389160156,0.055658627301454544,-0.06724662333726883,0.9915401339530945,-0.09677386283874512,0.05395560339093208,-0.06645213067531586,0.9916187524795532,-0.0982099249958992,0.052885279059410095,-0.06551209092140198,0.9915977716445923,-0.10108043998479843,0.05169179290533066,-0.0642392709851265,0.9914554953575134,-0.1041700690984726,0.05040028318762779,-0.06378043442964554,0.9912317991256714,-0.10617764294147491,0.04938069358468056,-0.064462810754776,0.9910259246826172,-0.10682058334350586,0.04898146167397499,-0.06572060286998749,0.9908940196037292,-0.10643818229436874,0.049356698989868164,-0.06704216450452805,0.9908280372619629,-0.1061515361070633,0.050246357917785645,-0.06842751055955887,0.9907193779945374,-0.10615161806344986,0.05024639144539833,-0.06842757016420364,0.9907193183898926,-0.10615160316228867,0.05024637281894684,-0.06842759251594543,0.9907193183898926,-0.10615156590938568,0.05024639144539833,-0.06842760741710663,0.9907193183898926,-0.10615162551403046,0.05024636536836624,-0.06842760741710663,0.9907193183898926,-0.10615161806344986,0.05024637281894684,-0.06842758506536484,0.9907193183898926]},{"name":"Bip001 L Clavicle.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.6695586442947388,0.3204400837421417,-0.5321458578109741,0.4072224497795105,0.6700468063354492,0.31972792744636536,-0.5323095917701721,0.40676504373550415,0.6701382398605347,0.3195975422859192,-0.5323193073272705,0.4067041873931885,0.6693224310874939,0.32078883051872253,-0.5320364236831665,0.40747907757759094,0.6680855751037598,0.32258012890815735,-0.5316488146781921,0.4085991382598877,0.667258620262146,0.3237701952457428,-0.5314109921455383,0.40931791067123413,0.6672160029411316,0.3238237202167511,-0.531436562538147,0.40931183099746704,0.6675000786781311,0.3234044313430786,-0.5315820574760437,0.40899112820625305,0.6671253442764282,0.3239363431930542,-0.5315059423446655,0.40928035974502563,0.6657803654670715,0.32587143778800964,-0.5310724973678589,0.41049525141716003,0.664024293422699,0.3283889889717102,-0.5304780602455139,0.41209882497787476,0.6623649001121521,0.3307526707649231,-0.5299152731895447,0.4136003851890564,0.6607952117919922,0.33297595381736755,-0.5293781161308289,0.41501274704933167,0.6590554714202881,0.3354291021823883,-0.5287613272666931,0.41658690571784973,0.657207190990448,0.3380226492881775,-0.5280811190605164,0.41826993227005005,0.6556466817855835,0.34020185470581055,-0.5274902582168579,0.41969528794288635,0.6545190215110779,0.3417701721191406,-0.5270513296127319,0.4207313656806946,0.6534910202026367,0.3431962728500366,-0.5266416668891907,0.42168042063713074,0.6523821949958801,0.3447277545928955,-0.5262002348899841,0.4226979911327362,0.6516649127006531,0.34571611881256104,-0.5259104371070862,0.4233573079109192,0.6517474055290222,0.34560462832450867,-0.5259295701980591,0.42329761385917664,0.6523164510726929,0.3448236882686615,-0.5261405110359192,0.42279544472694397,0.6526317000389099,0.3443893790245056,-0.5262649059295654,0.4225080907344818,0.6524220108985901,0.3446757197380066,-0.5262007713317871,0.42267823219299316,0.6520677804946899,0.3451581597328186,-0.526094377040863,0.42296355962753296,0.65200275182724,0.34524232149124146,-0.526101291179657,0.42298653721809387,0.6525071263313293,0.34454527497291565,-0.526314377784729,0.4225117564201355,0.6537213921546936,0.34286877512931824,-0.5267833471298218,0.4214126765727997,0.6552910208702087,0.3406928777694702,-0.5273783206939697,0.41999301314353943,0.6565723419189453,0.3389074206352234,-0.5278606414794922,0.4188288748264313,0.657151460647583,0.33809730410575867,-0.5280799865722656,0.4182985723018646,0.6568672060966492,0.33849263191223145,-0.5279895663261414,0.4185394048690796,0.6557918190956116,0.33998504281044006,-0.5276318192481995,0.4194662272930145,0.6542903780937195,0.34205904603004456,-0.5271381139755249,0.420743465423584,0.6530376672744751,0.3437795639038086,-0.5267366766929626,0.42178890109062195,0.652642548084259,0.34431856870651245,-0.5266226530075073,0.42210307717323303,0.6530208587646484,0.34379780292510986,-0.5267620086669922,0.4217683970928192,0.6534766554832458,0.3431701064109802,-0.5269263982772827,0.42136815190315247,0.6535409092903137,0.34307852387428284,-0.5269650220870972,0.4212947189807892,0.6534180045127869,0.3432452380657196,-0.5269416570663452,0.4213787913322449,0.6537103056907654,0.3428410291671753,-0.5270485877990723,0.42112067341804504,0.6549593210220337,0.34112128615379333,-0.5274531245231628,0.42006877064704895,0.6569769382476807,0.3383297026157379,-0.5280869007110596,0.41837602853775024,0.6585609912872314,0.33612656593322754,-0.5285741090774536,0.41704410314559937,0.6591253876686096,0.33533886075019836,-0.5287444591522217,0.41657042503356934,0.6594250798225403,0.3349197506904602,-0.5288320183753967,0.4163220226764679,0.6600399613380432,0.3340606391429901,-0.5290087461471558,0.41581299901008606,0.6607142686843872,0.33311742544174194,-0.5291979312896729,0.4152577221393585,0.661160409450531,0.33249127864837646,-0.5293204188346863,0.4148932099342346,0.6615904569625854,0.33188894391059875,-0.5294417142868042,0.4145349860191345,0.6622081995010376,0.3310161828994751,-0.5296260118484497,0.4140107035636902,0.6627594828605652,0.33023709058761597,-0.5297908782958984,0.4135395586490631,0.6629567742347717,0.3299599885940552,-0.5298438668251038,0.4133765995502472,0.6627774834632874,0.3302149176597595,-0.5297814607620239,0.41354042291641235,0.662466287612915,0.33065682649612427,-0.5296841859817505,0.41381049156188965,0.6623838543891907,0.33077216148376465,-0.5296594500541687,0.41388198733329773,0.6623837947845459,0.3307722508907318,-0.5296594500541687,0.41388195753097534,0.6623837947845459,0.33077219128608704,-0.5296594500541687,0.4138820171356201,0.662384033203125,0.33077260851860046,-0.5296591520309448,0.41388165950775146,0.662384033203125,0.3307727575302124,-0.5296590328216553,0.41388171911239624,0.6623837351799011,0.3307720124721527,-0.5296595096588135,0.41388219594955444]},{"name":"Bip001 L UpperArm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.23281005024909973,0.08231665939092636,0.09526070207357407,0.9643385410308838,0.23133349418640137,0.08703360706567764,0.09607119858264923,0.9641993045806885,0.23054498434066772,0.09218394011259079,0.09558995813131332,0.9639573097229004,0.23119686543941498,0.09681522101163864,0.09420311450958252,0.9634835720062256,0.23193620145320892,0.1014123409986496,0.09296261519193649,0.9629533290863037,0.2310876101255417,0.1062145009636879,0.09222912788391113,0.9627101421356201,0.22809948027133942,0.1104063093662262,0.09134180098772049,0.9630357027053833,0.22427089512348175,0.1126655638217926,0.0898292139172554,0.9638152122497559,0.22214092314243317,0.11186526715755463,0.08792532980442047,0.9645769596099854,0.22248968482017517,0.10796447098255157,0.0855393335223198,0.9651554226875305,0.22371743619441986,0.10212676227092743,0.08232235163450241,0.9657865762710571,0.22418153285980225,0.09597671777009964,0.07855112850666046,0.9666234254837036,0.2235875427722931,0.09080798178911209,0.07485558837652206,0.9675531983375549,0.22284753620624542,0.08682618290185928,0.07138821482658386,0.9683511257171631,0.22277960181236267,0.08325078338384628,0.06802694499492645,0.9689225554466248,0.22247016429901123,0.07973598688840866,0.06572128087282181,0.9694482684135437,0.21994368731975555,0.07683401554822922,0.06628180295228958,0.9702206254005432,0.21481113135814667,0.0743473619222641,0.07005803287029266,0.9712983965873718,0.20828305184841156,0.07093668729066849,0.074806347489357,0.9726202487945557,0.20054665207862854,0.0662245824933052,0.07758834958076477,0.9743589758872986,0.19111540913581848,0.06150899454951286,0.0774374008178711,0.97657310962677,0.18121762573719025,0.05802629888057709,0.07561538368463516,0.9788132905960083,0.17358054220676422,0.055795930325984955,0.07360193133354187,0.9804791808128357,0.16960409283638,0.05430286377668381,0.0714988186955452,0.9814140796661377,0.16844093799591064,0.05362679809331894,0.06888911873102188,0.9818381071090698,0.16869263350963593,0.05417982488870621,0.06562158465385437,0.9819883704185486,0.16880224645137787,0.05580514669418335,0.06030696630477905,0.9822192788124084,0.16642294824123383,0.05813424661755562,0.050495076924562454,0.9830432534217834,0.16077643632888794,0.06063055619597435,0.03686262294650078,0.9844369292259216,0.15510979294776917,0.06189223751425743,0.02491600625216961,0.9856416583061218,0.15428927540779114,0.06040239334106445,0.02003168687224388,0.9859741926193237,0.16040150821208954,0.056419260799884796,0.022257419303059578,0.9851866960525513,0.17104938626289368,0.05293874442577362,0.027584893628954887,0.9834524393081665,0.1815292239189148,0.053427692502737045,0.03307879716157913,0.9813757538795471,0.18817222118377686,0.058301087468862534,0.038817938417196274,0.9796352982521057,0.1900901198387146,0.06481098383665085,0.04563022032380104,0.9785617589950562,0.18915610015392303,0.06956899911165237,0.05314986780285835,0.9780364036560059,0.1882529854774475,0.07099815458059311,0.060540903359651566,0.9776782989501953,0.18870972096920013,0.07005398720502853,0.06735184043645859,0.9772127866744995,0.18981324136257172,0.06860600411891937,0.07327625900506973,0.9766753315925598,0.19030937552452087,0.06774719059467316,0.07817886024713516,0.9762585163116455,0.1895388513803482,0.06731311231851578,0.08218847215175629,0.9761091470718384,0.1881018579006195,0.06624983996152878,0.08507946878671646,0.9762121438980103,0.18803967535495758,0.06368757039308548,0.08677709847688675,0.9762452244758606,0.19003868103027344,0.06038396805524826,0.0882209837436676,0.975938618183136,0.19208167493343353,0.058356501162052155,0.09069850295782089,0.9754347205162048,0.19246768951416016,0.05874546617269516,0.0945572704076767,0.9749687314033508,0.19179899990558624,0.06078527495265007,0.09880252927541733,0.9745544195175171,0.19152699410915375,0.06295350939035416,0.10215870290994644,0.9741241335868835,0.1919720619916916,0.0646459087729454,0.1042056456208229,0.9737088084220886,0.19240856170654297,0.06627127528190613,0.1055103987455368,0.9733728170394897,0.1918635219335556,0.06748170405626297,0.10806518793106079,0.973116934299469,0.19097155332565308,0.06928668171167374,0.11053219437599182,0.9728884100914001,0.1908177137374878,0.06986339390277863,0.1134742945432663,0.9725385904312134,0.19086208939552307,0.07049749791622162,0.11670099198818207,0.972102165222168,0.19083179533481598,0.0717586874961853,0.119047611951828,0.9717312455177307,0.19048796594142914,0.07319971174001694,0.11934790760278702,0.9716543555259705,0.19016249477863312,0.07450065016746521,0.11945906281471252,0.9716055989265442,0.18988363444805145,0.07555687427520752,0.11943374574184418,0.9715816974639893,0.18968886137008667,0.07626565545797348,0.11935660243034363,0.9715738296508789,0.18961547315120697,0.07652457058429718,0.11931232362985611,0.9715732336044312]},{"name":"Bip001 L Forearm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.021672792732715607,-0.00033608023659326136,0.282943993806839,0.9588914513587952,-0.021883999928832054,-0.00033504306338727474,0.28565388917922974,0.958082914352417,-0.022321639582514763,-0.00033417591475881636,0.2912646532058716,0.9563819766044617,-0.022882282733917236,-0.000336717494064942,0.2984454333782196,0.9541522860527039,-0.02339131198823452,-0.00034191180020570755,0.30496272444725037,0.9520769119262695,-0.02381754480302334,-0.00034541060449555516,0.310390442609787,0.9503106474876404,-0.024254370480775833,-0.0003446699702180922,0.3159182071685791,0.9484763145446777,-0.024727405980229378,-0.00034064793726429343,0.3219013810157776,0.9464501738548279,-0.025196945294737816,-0.0003379262052476406,0.3278385400772095,0.9443976283073425,-0.025656981393694878,-0.0003373754443600774,0.3336580693721771,0.9423449039459229,-0.02609851397573948,-0.0003359066613484174,0.3392544984817505,0.9403324723243713,-0.026496633887290955,-0.0003311647742521018,0.3443296551704407,0.9384747743606567,-0.026853976771235466,-0.0003221197403036058,0.34892183542251587,0.9367669224739075,-0.027183514088392258,-0.00030923940357752144,0.35321083664894104,0.9351487159729004,-0.027482163161039352,-0.0002949443878605962,0.35713833570480347,0.933647096157074,-0.02772654965519905,-0.00028229589224793017,0.3603353798389435,0.9324105978012085,-0.027893079444766045,-0.00027140468591824174,0.3624538481235504,0.9315841794013977,-0.028014155104756355,-0.0002588791830930859,0.3639327585697174,0.9310038089752197,-0.028204530477523804,-0.0002406450657872483,0.36632391810417175,0.9300597906112671,-0.028556549921631813,-0.00021584730711765587,0.37088513374328613,0.9282395839691162,-0.029006056487560272,-0.00018880497373174876,0.37677279114723206,0.9258514642715454,-0.02937728352844715,-0.00016562495147809386,0.38166549801826477,0.9238335490226746,-0.0295809805393219,-0.0001507496926933527,0.38439470529556274,0.9226948022842407,-0.029710106551647186,-0.00014170016220305115,0.3862054646015167,0.921934187412262,-0.029902471229434013,-0.00013496215979103,0.3888736069202423,0.9208056926727295,-0.030210167169570923,-0.0001304837060160935,0.39299675822257996,0.9190434813499451,-0.03066059947013855,-0.00012638315092772245,0.39891237020492554,0.916476309299469,-0.03125175088644028,-0.00011926847946597263,0.40668970346450806,0.9130316376686096,-0.03175746276974678,-0.00011251436808379367,0.4134352207183838,0.909979522228241,-0.031781893223524094,-0.00011475017527118325,0.4139156639575958,0.9097602367401123,-0.031195079907774925,-0.00012713970500044525,0.40636497735977173,0.9131781458854675,-0.030353229492902756,-0.0001430301635991782,0.39545536041259766,0.9179834723472595,-0.029759380966424942,-0.00015564914792776108,0.3878323435783386,0.9212493896484375,-0.029574770480394363,-0.00016336906992364675,0.3855830132961273,0.922199010848999,-0.0295396875590086,-0.00017006511916406453,0.38522833585739136,0.9223483800888062,-0.02932567708194256,-0.00017917285731527954,0.38242092728614807,0.9235227108001709,-0.02883727103471756,-0.0001905558310681954,0.37592813372612,0.9261999726295471,-0.028172263875603676,-0.0002047694579232484,0.3670749366283417,0.9297646284103394,-0.027478137984871864,-0.0002212819963460788,0.35782358050346375,0.9333847761154175,-0.026886792853474617,-0.00023709004744887352,0.3499327003955841,0.9363889098167419,-0.026463337242603302,-0.00024910259526222944,0.3442671597003937,0.9384986758232117,-0.02619735151529312,-0.0002558354171924293,0.34067919850349426,0.9398145079612732,-0.026066916063427925,-0.0002581487933639437,0.33886414766311646,0.9404740929603577,-0.026019377633929253,-0.00025970159913413227,0.3381224572658539,0.9407423138618469,-0.02593674138188362,-0.00026349324616603553,0.3369351923465729,0.9411705136299133,-0.02573247253894806,-0.00026849727146327496,0.3342008888721466,0.9421504735946655,-0.02544383332133293,-0.00027164563653059304,0.3304287791252136,0.9434878826141357,-0.025197191163897514,-0.00027282198425382376,0.3272457718849182,0.9446032047271729,-0.0250769704580307,-0.00027381457039155066,0.3257073163986206,0.9451379776000977,-0.025042854249477386,-0.0002744767989497632,0.32528549432754517,0.9452841281890869,-0.02499682642519474,-0.0002745819219853729,0.32471057772636414,0.9454830288887024,-0.02471400424838066,-0.00027429661713540554,0.32106277346611023,0.9467353820800781,-0.024518394842743874,-0.0002756730536930263,0.31850704550743103,0.9476032853126526,-0.024374857544898987,-0.00027849432080984116,0.31658995151519775,0.9482492208480835,-0.0241687148809433,-0.00028285279404371977,0.31386250257492065,0.9491607546806335,-0.02396753802895546,-0.0002869008167181164,0.31122562289237976,0.9500337243080139,-0.023965798318386078,-0.0002876638900488615,0.31120458245277405,0.9500406384468079,-0.023990394547581673,-0.0002883540000766516,0.31151771545410156,0.9499374032020569,-0.024029355496168137,-0.0002889408206101507,0.31201285123825073,0.949773907661438,-0.024065285921096802,-0.00028933072462677956,0.3124706447124481,0.9496225118637085,-0.02408103086054325,-0.00028956218739040196,0.31267181038856506,0.9495558738708496]},{"name":"Bip001 R Clavicle.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.6659364104270935,-0.3323783576488495,0.4817703366279602,0.46254801750183105,0.6644406914710999,-0.33437684178352356,0.48214420676231384,0.46286889910697937,0.6618736982345581,-0.3377741873264313,0.4828279912471771,0.46336695551872253,0.6583617329597473,-0.3423594534397125,0.4838271737098694,0.4639624059200287,0.654269814491272,-0.3476114869117737,0.48506736755371094,0.4645501673221588,0.6501113772392273,-0.352853387594223,0.4863744080066681,0.46506941318511963,0.6465917825698853,-0.35721462965011597,0.48745477199554443,0.46551549434661865,0.6443252563476562,-0.3599880337715149,0.4880346953868866,0.4659138321876526,0.6430732011795044,-0.3615063428878784,0.4882175624370575,0.4662763178348541,0.6418465375900269,-0.3629869520664215,0.4884207546710968,0.46660327911376953,0.6402575373649597,-0.3648945689201355,0.48873934149742126,0.4669647514820099,0.6388003826141357,-0.3666313588619232,0.4889047145843506,0.4674266278743744,0.6375844478607178,-0.36807239055633545,0.48901525139808655,0.46783849596977234,0.636222779750824,-0.36968114972114563,0.4895079731941223,0.46790850162506104,0.6346491575241089,-0.37153181433677673,0.49046796560287476,0.46757426857948303,0.6328509449958801,-0.37363195419311523,0.49161240458488464,0.46713605523109436,0.6304522156715393,-0.3764049708843231,0.4927692413330078,0.4669343829154968,0.6273792386054993,-0.37991654872894287,0.4938907027244568,0.4670446217060089,0.6235042810440063,-0.3842816948890686,0.495027095079422,0.46745923161506653,0.6179326772689819,-0.3904428482055664,0.49640953540802,0.46828535199165344,0.6101764440536499,-0.39880308508872986,0.4981658458709717,0.46954405307769775,0.6010044813156128,-0.4083808660507202,0.5001715421676636,0.47100645303726196,0.5918533802032471,-0.4176214337348938,0.5021011233329773,0.4724366068840027,0.5838991403579712,-0.4254113733768463,0.5035943388938904,0.4737929105758667,0.5775254964828491,-0.431498646736145,0.5045673847198486,0.47506311535835266,0.571780800819397,-0.43687140941619873,0.5053772330284119,0.47623932361602783,0.5645983219146729,-0.443440318107605,0.506525456905365,0.4775157868862152,0.5550260543823242,-0.45194873213768005,0.5080240964889526,0.4791658818721771,0.5454434752464294,-0.4602002799510956,0.5091356039047241,0.48113206028938293,0.5393791198730469,-0.46529415249824524,0.5092569589614868,0.482937753200531,0.5376725792884827,-0.46681177616119385,0.508499264717102,0.4841730296611786,0.5394830107688904,-0.46541252732276917,0.5077222585678101,0.48432156443595886,0.5436187982559204,-0.462221622467041,0.5067684054374695,0.4837515652179718,0.5500116944313049,-0.45718467235565186,0.5053219199180603,0.48282405734062195,0.5565694570541382,-0.4515136778354645,0.5053626894950867,0.48059794306755066,0.5640343427658081,-0.4447231590747833,0.505381166934967,0.4782012403011322,0.5718927383422852,-0.43716275691986084,0.5053886771202087,0.4758252799510956,0.5798271298408508,-0.4290466010570526,0.5053136348724365,0.4736851453781128,0.5857350826263428,-0.4182310700416565,0.5061871409416199,0.47515445947647095,0.5920154452323914,-0.40699830651283264,0.5066869854927063,0.4765903651714325,0.5972324013710022,-0.39342617988586426,0.5075066089630127,0.4805895686149597,0.6008914113044739,-0.37831631302833557,0.5085293650627136,0.4870361089706421,0.6028837561607361,-0.3642613887786865,0.5095335245132446,0.4941866099834442,0.6037392020225525,-0.35276728868484497,0.5103631615638733,0.5005833506584167,0.603910505771637,-0.34326428174972534,0.5109882950782776,0.5063127875328064,0.6036534309387207,-0.335002601146698,0.5114469528198242,0.5116618275642395,0.6032245755195618,-0.3278171122074127,0.5118168592453003,0.516429603099823,0.6028305888175964,-0.32183945178985596,0.5121585726737976,0.5202962756156921,0.6024940013885498,-0.31735071539878845,0.5124232769012451,0.5231748223304749,0.6021841168403625,-0.3143043518066406,0.512570321559906,0.5252225995063782,0.6018690466880798,-0.3119848370552063,0.5126466751098633,0.5268895030021667,0.6015373468399048,-0.3093787431716919,0.5127503275871277,0.5287010073661804,0.6012036800384521,-0.3063155710697174,0.5129110217094421,0.5307043790817261,0.600908637046814,-0.3034636676311493,0.5130783319473267,0.5325121879577637,0.6006703972816467,-0.30130699276924133,0.513200044631958,0.5338866114616394,0.6004815697669983,-0.29992371797561646,0.5132613182067871,0.534818172454834,0.6006185412406921,-0.29968541860580444,0.5132994055747986,0.534761369228363,0.6009453535079956,-0.29911699891090393,0.5133900046348572,0.5346254706382751,0.6013352870941162,-0.2984393537044525,0.5134969353675842,0.5344629883766174,0.6016618013381958,-0.2978712320327759,0.5135863423347473,0.5343265533447266,0.6017979979515076,-0.2976325750350952,0.5136237740516663,0.5342702269554138]},{"name":"Bip001 R UpperArm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.3425823152065277,-0.010915359482169151,0.04358949512243271,0.938412606716156,0.34463033080101013,-0.022652795538306236,0.045450929552316666,0.937363862991333,0.3487383723258972,-0.0473351776599884,0.04696228355169296,0.9348451495170593,0.356711745262146,-0.08207765221595764,0.048281408846378326,0.9293486475944519,0.3699456453323364,-0.1205691248178482,0.05103844776749611,0.9197816848754883,0.38864773511886597,-0.1585720032453537,0.05730810761451721,0.9058275818824768,0.4122386872768402,-0.19355003535747528,0.06844369322061539,0.8876447081565857,0.43923524022102356,-0.223965585231781,0.08408024162054062,0.8659343719482422,0.4674268960952759,-0.2500191032886505,0.10163342952728271,0.8418272733688354,0.49499836564064026,-0.27257487177848816,0.11798834800720215,0.8165526986122131,0.5212144255638123,-0.2923106551170349,0.13113895058631897,0.7910073399543762,0.5460002422332764,-0.3094855844974518,0.13910162448883057,0.765998125076294,0.5683496594429016,-0.3210214674472809,0.14141052961349487,0.7442626953125,0.5860191583633423,-0.32357436418533325,0.14012806117534637,0.7295514345169067,0.5974764227867126,-0.3199053406715393,0.13636264204978943,0.7225563526153564,0.603047251701355,-0.3152531385421753,0.13033640384674072,0.7210838198661804,0.60451340675354,-0.3121836185455322,0.12390795350074768,0.7223238348960876,0.6032590866088867,-0.3114350140094757,0.12104086577892303,0.7241794466972351,0.5990465879440308,-0.312762588262558,0.12575022876262665,0.7262985706329346,0.5785918831825256,-0.3405667543411255,0.11457005888223648,0.7322017550468445,0.5498138666152954,-0.37139174342155457,0.10419641435146332,0.7408886551856995,0.5149046778678894,-0.4020090401172638,0.09408090263605118,0.7512727379798889,0.4775574803352356,-0.4305051267147064,0.08517993241548538,0.761149525642395,0.4416966736316681,-0.45504486560821533,0.07833769172430038,0.769221305847168,0.4099847674369812,-0.4736718535423279,0.07381926476955414,0.7759498357772827,0.38144975900650024,-0.48506101965904236,0.07361700385808945,0.7834490537643433,0.3552463948726654,-0.4840351641178131,0.08567458391189575,0.7950910925865173,0.3274631202220917,-0.46823418140411377,0.10989827662706375,0.8132939338684082,0.2955462634563446,-0.4369373917579651,0.13539697229862213,0.8386929035186768,0.2640817165374756,-0.3959256410598755,0.1533249020576477,0.8660226464271545,0.23722520470619202,-0.3480268716812134,0.16269102692604065,0.8922629356384277,0.21434874832630157,-0.2911117970943451,0.16473956406116486,0.9176979064941406,0.19623726606369019,-0.2308356761932373,0.16047753393650055,0.9393895864486694,0.1842772662639618,-0.17357848584651947,0.1506204456090927,0.955628514289856,0.1782888025045395,-0.1258043497800827,0.1305311918258667,0.9671338796615601,0.1778687834739685,-0.08852463960647583,0.10431371629238129,0.9744971394538879,0.17983107268810272,-0.058876361697912216,0.07517238706350327,0.9790523052215576,0.1953565627336502,-0.02456897683441639,0.04832596704363823,0.9792327284812927,0.2153998166322708,0.0018613007850944996,0.026990357786417007,0.9761511087417603,0.23472578823566437,0.021647311747074127,0.008555660024285316,0.971782922744751,0.250588595867157,0.031747475266456604,-0.0049771578051149845,0.9675601720809937,0.2617235779762268,0.034580424427986145,-0.012548201717436314,0.9644415378570557,0.2704612910747528,0.03494222089648247,-0.01596110500395298,0.9619641304016113,0.279544472694397,0.036485929042100906,-0.018437329679727554,0.9592620730400085,0.28924256563186646,0.03898660093545914,-0.0208317581564188,0.9562346935272217,0.2984175682067871,0.04056946933269501,-0.022562135010957718,0.9533058404922485,0.3067152798175812,0.04098811000585556,-0.020559486001729965,0.9506960511207581,0.31488049030303955,0.040653035044670105,-0.016898617148399353,0.9481097459793091,0.3231654167175293,0.04073720797896385,-0.012582727707922459,0.9453815221786499,0.3314710259437561,0.04094281047582626,-0.008833848871290684,0.9425352215766907,0.3395737111568451,0.04088000953197479,-0.0052189212292432785,0.9396761655807495,0.3473599851131439,0.04070134833455086,-0.00019297856488265097,0.9368481040000916,0.354352742433548,0.04129406809806824,0.0061525083146989346,0.9341793656349182,0.3598732650279999,0.0418614000082016,0.012995429337024689,0.93197101354599,0.360996812582016,0.03977995365858078,0.014677555300295353,0.9316025972366333,0.3619309365749359,0.03866374492645264,0.015058380551636219,0.9312810301780701,0.36285099387168884,0.0374806672334671,0.01513766497373581,0.9309700131416321,0.3633851110935211,0.03600678592920303,0.015751253813505173,0.9308096766471863,0.36363595724105835,0.03456554934382439,0.016592368483543396,0.9307517409324646,0.3637080490589142,0.03347253054380417,0.017334533855319023,0.9307500123977661,0.36370694637298584,0.03304247185587883,0.01765287294983864,0.9307597875595093]},{"name":"Bip001 R Forearm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.02552872896194458,0.0007447588723152876,-0.34514501690864563,0.9382018446922302,-0.026065636426210403,0.0007375529385171831,-0.3525475263595581,0.9354305267333984,-0.027004586532711983,0.0007250187918543816,-0.3654865622520447,0.9304245114326477,-0.02822853811085224,0.0007069617859087884,-0.3823009431362152,0.9236063361167908,-0.029515206813812256,0.0006933624972589314,-0.3999166488647461,0.9160758852958679,-0.030656613409519196,0.0007006247760728002,-0.41551607847213745,0.909068763256073,-0.03149421885609627,0.000737613532692194,-0.42697885632514954,0.9037126898765564,-0.032027363777160645,0.0007986867567524314,-0.4343317151069641,0.9001830816268921,-0.03247007727622986,0.0008695528958924115,-0.44048431515693665,0.8971725106239319,-0.033114172518253326,0.0009358465322293341,-0.44938990473747253,0.8927212953567505,-0.0340472050011158,0.0009988633682951331,-0.4622713625431061,0.8860840797424316,-0.03511553257703781,0.001076814136467874,-0.47725221514701843,0.8780637979507446,-0.036261964589357376,0.001158819766715169,-0.493604838848114,0.8689292073249817,-0.037495844066143036,0.0012047080090269446,-0.5111820101737976,0.8586533665657043,-0.038658175617456436,0.001210117363370955,-0.5275977253913879,0.8486133813858032,-0.03947646915912628,0.0012083122273907065,-0.5390715003013611,0.8413335084915161,-0.03984881937503815,0.001217345823533833,-0.5441643595695496,0.8380308747291565,-0.040105197578668594,0.0012203296646475792,-0.5473906993865967,0.8359147906303406,-0.04086953029036522,0.0011884730774909258,-0.5572647452354431,0.8293276429176331,-0.04242141172289848,0.0011290869442746043,-0.5780735015869141,0.8148804306983948,-0.04453058913350105,0.0010403151391074061,-0.6063831448554993,0.7939240336418152,-0.04677248001098633,0.0009379183175042272,-0.6365522742271423,0.7698133587837219,-0.04890820384025574,0.0008326074457727373,-0.6654555201530457,0.7448330521583557,-0.050866398960351944,0.0007340704323723912,-0.6921429634094238,0.719965398311615,-0.05259478837251663,0.000648669374641031,-0.7157931327819824,0.6963286399841309,-0.05414879322052002,0.0005747906398028135,-0.7369354367256165,0.6737905740737915,-0.0558331124484539,0.0004957683268003166,-0.7594430446624756,0.6481733322143555,-0.057802360504865646,0.00038571280310861766,-0.7855014204978943,0.6161544322967529,-0.05962216481566429,0.00024031191424001008,-0.8096539378166199,0.5838713049888611,-0.06098382920026779,0.00009486210183240473,-0.8280583620071411,0.5573152899742126,-0.06197286397218704,-0.00003266265775891952,-0.8418017625808716,0.5362173914909363,-0.06260520964860916,-0.00014233171532396227,-0.8508420586585999,0.5216784477233887,-0.06288667023181915,-0.0002217282453784719,-0.8551520705223083,0.5145485401153564,-0.06283897906541824,-0.0002649641828611493,-0.8550192713737488,0.5147749185562134,-0.06251367926597595,-0.0002805628173518926,-0.8512779474258423,0.520977795124054,-0.062037430703639984,-0.0002751637948676944,-0.8456729650497437,0.5300834774971008,-0.06144995614886284,-0.00025706543237902224,-0.8386292457580566,0.541225254535675,-0.0607820488512516,-0.00023218987917061895,-0.8304887413978577,0.5537092685699463,-0.060085318982601166,-0.00020260116434656084,-0.8218240737915039,0.5665640830993652,-0.05942779779434204,-0.0001751822273945436,-0.8136191368103027,0.5783529877662659,-0.05877390503883362,-0.00014853141328785568,-0.8053211569786072,0.5899181962013245,-0.058110080659389496,-0.0001236332464031875,-0.7966904640197754,0.6015874743461609,-0.05751024931669235,-0.00010386089707026258,-0.7887772917747498,0.6119828224182129,-0.05699944868683815,-0.00008965151209849864,-0.7820297479629517,0.6206291317939758,-0.05658307671546936,-0.00007842712511774153,-0.7765945792198181,0.6274545788764954,-0.056347865611314774,-0.00006908947398187593,-0.773705244064331,0.6310349702835083,-0.05624108016490936,-0.000059934205637546256,-0.7725433707237244,0.6324663162231445,-0.0562027208507061,-0.000053513256716541946,-0.7722522020339966,0.6328252553939819,-0.05617644265294075,-0.00004970558438799344,-0.7720531225204468,0.6330704689025879,-0.05614207312464714,-0.00004702182923210785,-0.7716962695121765,0.6335084438323975,-0.05610877647995949,-0.00004440258635440841,-0.7713395953178406,0.6339455842971802,-0.05609472841024399,-0.00004162088589509949,-0.7712391018867493,0.6340690851211548,-0.056136731058359146,-0.00003977480446337722,-0.7719117999076843,0.6332463026046753,-0.05623500421643257,-0.00003933994594262913,-0.7733385562896729,0.6314943432807922,-0.056222885847091675,-0.00003996097075287253,-0.7731834650039673,0.6316852569580078,-0.05627314746379852,-0.000042609142838045955,-0.7739042639732361,0.6307975649833679,-0.056190039962530136,-0.00003991895937360823,-0.7727606892585754,0.6322053074836731,-0.05613067001104355,-0.00003723366171470843,-0.7719451785087585,0.6332060694694519,-0.05609307065606117,-0.00003486414789222181,-0.7714331746101379,0.633833110332489,-0.0560736246407032,-0.000033430569601478055,-0.7711665630340576,0.6341591477394104,-0.05606822296977043,-0.00003296386421425268,-0.7710906863212585,0.6342518925666809]},{"name":"Bip001 L Thigh.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.03252058103680611,-0.04403537139296532,-0.9917194843292236,0.11617125570774078,-0.03308558091521263,-0.04429936036467552,-0.9916461706161499,0.11653656512498856,-0.03334667161107063,-0.044935088604688644,-0.9915827512741089,0.11675839126110077,-0.03371709585189819,-0.045789044350385666,-0.9915042519569397,0.11698633432388306,-0.03446489945054054,-0.046505119651556015,-0.9914118647575378,0.11726886034011841,-0.035152968019247055,-0.046780429780483246,-0.9913433790206909,0.11753373593091965,-0.03530278429389,-0.04668150842189789,-0.9913118481636047,0.11779376864433289,-0.03534621372818947,-0.04669155925512314,-0.9912604689598083,0.11820857226848602,-0.03620384261012077,-0.04721060022711754,-0.9911215305328369,0.11890571564435959,-0.03829071670770645,-0.04801439493894577,-0.9908901453018188,0.11985492706298828,-0.04156868904829025,-0.04836640506982803,-0.9905913472175598,0.12108466029167175,-0.04607286676764488,-0.048292018473148346,-0.9901638031005859,0.12296658009290695,-0.05194515734910965,-0.04912837967276573,-0.9894476532936096,0.1260215938091278,-0.05889274924993515,-0.051611725240945816,-0.9883895516395569,0.13020747900009155,-0.0656934306025505,-0.054013755172491074,-0.9872828125953674,0.13431145250797272,-0.07055038213729858,-0.05373154208064079,-0.9866291880607605,0.13674208521842957,-0.07257717847824097,-0.051171667873859406,-0.986507773399353,0.13753680884838104,-0.07330639660358429,-0.04991903156042099,-0.9863622188568115,0.13864995539188385,-0.07502669095993042,-0.05181630328297615,-0.9856955409049988,0.14174044132232666,-0.07752187550067902,-0.055899616330862045,-0.9846595525741577,0.14598359167575836,-0.0790911391377449,-0.059957996010780334,-0.9837722182273865,0.14947189390659332,-0.07965711504220963,-0.062478676438331604,-0.9833099842071533,0.1511709839105606,-0.07987432181835175,-0.0637458935379982,-0.9832266569137573,0.1510692983865738,-0.07880418747663498,-0.06482979655265808,-0.9834493398666382,0.14971420168876648,-0.07608871906995773,-0.06448633223772049,-0.9840957522392273,0.14699529111385345,-0.07446638494729996,-0.05911125987768173,-0.985260009765625,0.14220894873142242,-0.07642878592014313,-0.04812418669462204,-0.9866301417350769,0.13565990328788757,-0.07962589710950851,-0.03744519501924515,-0.9876706600189209,0.12947700917720795,-0.08067990094423294,-0.03237203508615494,-0.9883106350898743,0.12523925304412842,-0.08024682104587555,-0.03126201778650284,-0.9887292385101318,0.12246493995189667,-0.08002416789531708,-0.030149899423122406,-0.989094078540802,0.11991690844297409,-0.07924337685108185,-0.02862497605383396,-0.9894983768463135,0.1174481213092804,-0.07638376206159592,-0.028596799820661545,-0.9899090528488159,0.11587823182344437,-0.07140927016735077,-0.030122341588139534,-0.9903015494346619,0.11530918627977371,-0.06538374722003937,-0.03175704553723335,-0.9907076954841614,0.11495538800954819,-0.059767093509435654,-0.03308802843093872,-0.9910686016082764,0.11452557891607285,-0.05589073896408081,-0.03449103981256485,-0.991257905960083,0.11443053185939789,-0.05367682874202728,-0.03588324040174484,-0.9912932515144348,0.11475574970245361,-0.0515231117606163,-0.03723166510462761,-0.9913106560707092,0.11516234278678894,-0.04824531823396683,-0.038895100355148315,-0.9913793802261353,0.11544031649827957,-0.04432858154177666,-0.04076091945171356,-0.9914897084236145,0.11541955173015594,-0.04085462540388107,-0.042193733155727386,-0.9916411638259888,0.11488410830497742,-0.038230009377002716,-0.043129172176122665,-0.9918076395988464,0.11399982869625092,-0.03672681376338005,-0.04407385364174843,-0.991910457611084,0.11323538422584534,-0.03696132451295853,-0.04514563828706741,-0.991908073425293,0.11275669187307358,-0.038595136255025864,-0.04611583054065704,-0.9918327331542969,0.1124793216586113,-0.03985128179192543,-0.04696452617645264,-0.9917481541633606,0.11243569850921631,-0.03950905799865723,-0.04777718335390091,-0.9916949272155762,0.11268361657857895,-0.03794792667031288,-0.04842745140194893,-0.9916754364967346,0.11311312764883041,-0.035927608609199524,-0.04866904392838478,-0.9916682243347168,0.11373055726289749,-0.03405650332570076,-0.048257309943437576,-0.9916874170303345,0.11431276798248291,-0.032840829342603683,-0.04755036532878876,-0.9916831851005554,0.1149996891617775,-0.032555218786001205,-0.04699111729860306,-0.9916207790374756,0.1158454641699791,-0.032967109233140945,-0.047074951231479645,-0.9915061593055725,0.11667316406965256,-0.033457085490226746,-0.047077711671590805,-0.9913966059684753,0.11746080964803696,-0.03358611464500427,-0.04685642570257187,-0.9913351535797119,0.11802969127893448,-0.033503253012895584,-0.04699786752462387,-0.9913151860237122,0.11816491186618805,-0.03343347832560539,-0.04719686135649681,-0.9912957549095154,0.11826849728822708,-0.03337892144918442,-0.04742615669965744,-0.9912780523300171,0.11834042519330978,-0.03334321454167366,-0.04761514067649841,-0.9912651181221008,0.11838265508413315,-0.033330414444208145,-0.04769468307495117,-0.9912600517272949,0.11839660257101059]},{"name":"Bip001 L Calf.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.2969572842121124,0.008482183329761028,-0.030670033767819405,0.9543604254722595,-0.29799747467041016,0.008636555634438992,-0.030771121382713318,0.9540314674377441,-0.2986457943916321,0.008866813965141773,-0.030883627012372017,0.9538229703903198,-0.2986598610877991,0.009043867699801922,-0.03095516748726368,0.9538145661354065,-0.29824382066726685,0.009091638028621674,-0.030972076579928398,0.9539437890052795,-0.2978689670562744,0.009030110202729702,-0.030955100432038307,0.9540619850158691,-0.2980831563472748,0.008884625509381294,-0.030923593789339066,0.9539974927902222,-0.29955151677131653,0.008665117435157299,-0.030902540311217308,0.9535401463508606,-0.3025573492050171,0.008389877155423164,-0.03091738559305668,0.952592670917511,-0.30652692914009094,0.008091305382549763,-0.03096386417746544,0.9513238072395325,-0.31036439538002014,0.007805661763995886,-0.03100389800965786,0.9500798583030701,-0.3137255609035492,0.007529822178184986,-0.031017035245895386,0.9489771127700806,-0.31727591156959534,0.007262414786964655,-0.031043291091918945,0.9477972388267517,-0.32148274779319763,0.007052029948681593,-0.031131764873862267,0.9463772773742676,-0.3257790505886078,0.006897368002682924,-0.03124961629509926,0.9449041485786438,-0.3288736939430237,0.006728867068886757,-0.0313020795583725,0.9438310265541077,-0.3310762345790863,0.006518768146634102,-0.03128977119922638,0.9430625438690186,-0.33450081944465637,0.006014760583639145,-0.031241828575730324,0.9418582320213318,-0.33843794465065,0.005445900838822126,-0.031204067170619965,0.940455436706543,-0.34068921208381653,0.00479535199701786,-0.03107103519141674,0.9396501779556274,-0.33962029218673706,0.004460850264877081,-0.030920162796974182,0.9400436878204346,-0.3352237641811371,0.004677961114794016,-0.030835077166557312,0.9416221976280212,-0.3284236192703247,0.004925791639834642,-0.030662428587675095,0.9440198540687561,-0.31996527314186096,0.005184580106288195,-0.030431203544139862,0.9469262361526489,-0.3102598190307617,0.005369536112993956,-0.03013266995549202,0.950158953666687,-0.2997075021266937,0.005459060426801443,-0.029757697135210037,0.9535512924194336,-0.2894887924194336,0.005407773423939943,-0.029332764446735382,0.9567165970802307,-0.2809452414512634,0.005351379979401827,-0.02895381860435009,0.959272027015686,-0.27451083064079285,0.005431854631751776,-0.02866162732243538,0.9611414074897766,-0.26976779103279114,0.0055905906483531,-0.028453953564167023,0.9624887108802795,-0.2660735547542572,0.005686286836862564,-0.028264963999390602,0.9635214805603027,-0.26284506916999817,0.005680135916918516,-0.02805740386247635,0.9644132852554321,-0.2602732181549072,0.005614050664007664,-0.027867034077644348,0.9651164412498474,-0.25891533493995667,0.005505976267158985,-0.027742255479097366,0.9654858112335205,-0.25900983810424805,0.005353021435439587,-0.02769993245601654,0.9654625654220581,-0.2604273855686188,0.005191305186599493,-0.027740472927689552,0.9650808572769165,-0.2627139389514923,0.005075640976428986,-0.02784733660519123,0.9644584655761719,-0.2651510238647461,0.005001373123377562,-0.02797095477581024,0.9637880921363831,-0.2673788070678711,0.00491537619382143,-0.028072457760572433,0.9631699323654175,-0.2696421146392822,0.004806410521268845,-0.028164424002170563,0.962536633014679,-0.2721301019191742,0.004708531312644482,-0.02826690673828125,0.961833655834198,-0.2746306359767914,0.004627656657248735,-0.02836802788078785,0.9611201286315918,-0.2772212028503418,0.004539879038929939,-0.028462868183851242,0.9603736996650696,-0.2803446352481842,0.004400746896862984,-0.028563007712364197,0.9594642519950867,-0.28360265493392944,0.004292761441320181,-0.028664346784353256,0.958503782749176,-0.286243736743927,0.0042325593531131744,-0.028738290071487427,0.9577164053916931,-0.28802797198295593,0.0041753919795155525,-0.02877935953438282,0.9571803212165833,-0.28935760259628296,0.004095546901226044,-0.02880273014307022,0.956778883934021,-0.2907637059688568,0.003971675410866737,-0.028820838779211044,0.9563524723052979,-0.29216936230659485,0.004447849467396736,-0.029025046154856682,0.9559156894683838,-0.2931478023529053,0.005134354345500469,-0.02927890606224537,0.9556049108505249,-0.29382243752479553,0.005905228201299906,-0.029547806829214096,0.9553849697113037,-0.29471421241760254,0.006550529971718788,-0.029786281287670135,0.9550986289978027,-0.29608863592147827,0.006939355283975601,-0.02996343933045864,0.9546651244163513,-0.29722270369529724,0.007161927875131369,-0.030077876523137093,0.9543074369430542,-0.297830730676651,0.007391022052615881,-0.03017299808561802,0.9541131258010864,-0.2987675666809082,0.007388797588646412,-0.030209556221961975,0.9538190364837646,-0.2996290922164917,0.00738576939329505,-0.0302430372685194,0.9535477161407471,-0.3003403842449188,0.0073826927691698074,-0.030270839110016823,0.9533230662345886,-0.3008248805999756,0.007381371222436428,-0.030290009453892708,0.9531696438789368,-0.3010055422782898,0.007381464820355177,-0.030297355726361275,0.9531124234199524]},{"name":"Bip001 L Foot.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.5223862528800964,-0.10252691805362701,-0.06608512252569199,0.8439393639564514,0.5231484174728394,-0.103171207010746,-0.06590668112039566,0.8434025049209595,0.5237952470779419,-0.10418876260519028,-0.06553829461336136,0.8429045081138611,0.5241017937660217,-0.1044815257191658,-0.06525742262601852,0.8426994681358337,0.524127185344696,-0.10350220650434494,-0.06527625024318695,0.8428030610084534,0.5241637825965881,-0.10200513154268265,-0.06550061702728271,0.842945396900177,0.524589478969574,-0.10106314718723297,-0.0656844824552536,0.8427797555923462,0.5257939100265503,-0.10075879842042923,-0.06575002521276474,0.8420601487159729,0.5278856158256531,-0.10041312128305435,-0.06579932570457458,0.8407880067825317,0.5304986834526062,-0.09975174069404602,-0.06589674204587936,0.839212954044342,0.5330644249916077,-0.09910640865564346,-0.06603758782148361,0.8376510739326477,0.5354809761047363,-0.09872610121965408,-0.06622517853975296,0.8361384868621826,0.5381509065628052,-0.09873713552951813,-0.06641184538602829,0.8344064354896545,0.5412775874137878,-0.09941449761390686,-0.06648250669240952,0.8322952389717102,0.5444347262382507,-0.10066673159599304,-0.066373810172081,0.8300912976264954,0.5468432307243347,-0.10201328247785568,-0.0660739466547966,0.8283658623695374,0.5486915111541748,-0.10347223281860352,-0.06555166095495224,0.8270031213760376,0.5511609315872192,-0.10482264310121536,-0.06338295340538025,0.8253583908081055,0.5540484189987183,-0.10608088970184326,-0.06107740104198456,0.82343590259552,0.5561738610267639,-0.1064877137541771,-0.058852434158325195,0.8221115469932556,0.5554264187812805,-0.10644295066595078,-0.057480257004499435,0.8227195143699646,0.5514355897903442,-0.107065349817276,-0.05713525414466858,0.8253431916236877,0.5462669730186462,-0.10874979943037033,-0.05614853650331497,0.8286212682723999,0.5412111282348633,-0.112095408141613,-0.055371079593896866,0.8315402269363403,0.5369912385940552,-0.11639375239610672,-0.0551627017557621,0.8336966037750244,0.5325296521186829,-0.11902514100074768,-0.05501829460263252,0.8361926674842834,0.52801513671875,-0.11745888739824295,-0.05475909262895584,0.8392882943153381,0.5240797400474548,-0.11312197148799896,-0.05522467941045761,0.8423147201538086,0.5210803151130676,-0.10989075899124146,-0.056771356612443924,0.8444976806640625,0.5187898278236389,-0.10898858308792114,-0.058410998433828354,0.8459118008613586,0.5168620944023132,-0.10904882848262787,-0.05915290117263794,0.8470317721366882,0.5153355002403259,-0.10903824865818024,-0.05886317044496536,0.8479829430580139,0.5145424604415894,-0.10903245955705643,-0.05775212123990059,0.8485414981842041,0.514487087726593,-0.10939226299524307,-0.05591202154755592,0.8486520051956177,0.5147990584373474,-0.11005458235740662,-0.05354313179850578,0.8485299348831177,0.5150638222694397,-0.11020117253065109,-0.051476381719112396,0.8484781384468079,0.5150286555290222,-0.10883737355470657,-0.050718553364276886,0.8487211465835571,0.514829695224762,-0.10611245036125183,-0.05102873593568802,0.8491681814193726,0.5149139761924744,-0.10332528501749039,-0.05106770619750023,0.8494583964347839,0.515453040599823,-0.10129828006029129,-0.050387024879455566,0.8494162559509277,0.5162414908409119,-0.09967412799596786,-0.04986938089132309,0.8491600751876831,0.5173071622848511,-0.09800118207931519,-0.04999435320496559,0.8486987948417664,0.5187743306159973,-0.09641341865062714,-0.05027773976325989,0.8479680418968201,0.5207703113555908,-0.0947919636964798,-0.05050929635763168,0.8469129800796509,0.5218065977096558,-0.09266594797372818,-0.05092128738760948,0.8464856147766113,0.5217883586883545,-0.09032454341650009,-0.05115218088030815,0.8467360138893127,0.5219951868057251,-0.08887821435928345,-0.050754573196172714,0.8467854857444763,0.5230339765548706,-0.08867374807596207,-0.04992338642477989,0.8462151885032654,0.5246396064758301,-0.08906488120555878,-0.04878944903612137,0.8452457189559937,0.5249885320663452,-0.09058845043182373,-0.05001543462276459,0.8447953462600708,0.5250422358512878,-0.09268461912870407,-0.05233702436089516,0.8443939089775085,0.5251083970069885,-0.09492592513561249,-0.05539202690124512,0.8439087271690369,0.5253172516822815,-0.09655575454235077,-0.058198682963848114,0.8434048295021057,0.5256883502006531,-0.09677589684724808,-0.06009267643094063,0.8430154323577881,0.525910496711731,-0.09663794934749603,-0.06141515076160431,0.8427973985671997,0.5259768962860107,-0.09694324433803558,-0.0627601370215416,0.8426218032836914,0.5262731313705444,-0.09694686532020569,-0.062737837433815,0.8424380421638489,0.5265573263168335,-0.09692418575286865,-0.062708280980587,0.842265248298645,0.5267966985702515,-0.09689370542764664,-0.06267993897199631,0.8421211838722229,0.5269627571105957,-0.09686850011348724,-0.06266332417726517,0.8420214056968689,0.527025580406189,-0.09685872495174408,-0.06265988945960999,0.841983437538147]},{"name":"Bip001 L Toe0.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.4060518741607666,0.023528864607214928,-0.015740158036351204,0.9134114980697632,0.4060141146183014,0.02362322248518467,-0.01583000086247921,0.9134242534637451,0.4059015214443207,0.023725751787424088,-0.01592152938246727,0.9134700298309326,0.4057634174823761,0.023750167340040207,-0.015934014692902565,0.9135305881500244,0.40573039650917053,0.023576337844133377,-0.015759890899062157,0.9135527610778809,0.40580660104751587,0.02340869978070259,-0.015601079910993576,0.9135259389877319,0.4058033227920532,0.023708991706371307,-0.015896786004304886,0.9135145545005798,0.4055982232093811,0.024413226172327995,-0.016573678702116013,0.9135750532150269,0.4053882360458374,0.024776224046945572,-0.016913827508687973,0.913652241230011,0.4054134786128998,0.024510709568858147,-0.01665426790714264,0.9136530160903931,0.40556004643440247,0.024212894961237907,-0.01637302152812481,0.9136009812355042,0.4055982530117035,0.024278247728943825,-0.01644064113497734,0.9135810732841492,0.4055423140525818,0.024434493854641914,-0.01659003645181656,0.9135990142822266,0.4054887592792511,0.024453001096844673,-0.016603758558630943,0.9136220216751099,0.40543892979621887,0.024431001394987106,-0.016577860340476036,0.9136452078819275,0.40540334582328796,0.024460893124341965,-0.016604341566562653,0.9136596918106079,0.40550169348716736,0.024420028552412987,-0.01657228171825409,0.913617730140686,0.4057445824146271,0.024200323969125748,-0.01637626811861992,0.9135193228721619,0.4059818387031555,0.023937147110700607,-0.01613682322204113,0.913425087928772,0.4062489867210388,0.023859456181526184,-0.016082806512713432,0.9133092761039734,0.4067516624927521,0.02404974214732647,-0.016312621533870697,0.9130764603614807,0.40747395157814026,0.02434723824262619,-0.016884658485651016,0.9127360582351685,0.40901464223861694,0.023465396836400032,-0.018301010131835938,0.912042498588562,0.40923643112182617,0.02339117042720318,-0.019375372678041458,0.9119226932525635,0.40767353773117065,0.024822909384965897,-0.01942991465330124,0.9125834703445435,0.4066770076751709,0.025521593168377876,-0.019283821806311607,0.9130118489265442,0.40639346837997437,0.025501815602183342,-0.018974870443344116,0.9131450653076172,0.40599173307418823,0.02562943659722805,-0.018485568463802338,0.9133302569389343,0.4052773714065552,0.026107557117938995,-0.018216729164123535,0.9136393070220947,0.40489035844802856,0.026341218501329422,-0.018414705991744995,0.9138002395629883,0.40469926595687866,0.026566777378320694,-0.018621034920215607,0.9138741493225098,0.40465715527534485,0.026695767417550087,-0.018744604662060738,0.9138864874839783,0.40452516078948975,0.02675735205411911,-0.018794259056448936,0.9139421582221985,0.40420448780059814,0.0266838688403368,-0.018694840371608734,0.9140881896018982,0.403798371553421,0.026415614411234856,-0.01839623972773552,0.9142814874649048,0.4035700559616089,0.02608572505414486,-0.018051790073513985,0.9143986701965332,0.40370652079582214,0.025810133665800095,-0.017791621387004852,0.9143513441085815,0.40402835607528687,0.02541867457330227,-0.01743277907371521,0.9142270684242249,0.40458178520202637,0.024310121312737465,-0.016920560970902443,0.9140220284461975,0.405305415391922,0.022555582225322723,-0.016296615824103355,0.9137577414512634,0.4056682884693146,0.02110605500638485,-0.015546339564025402,0.9136443734169006,0.40550118684768677,0.020310737192630768,-0.014851094223558903,0.9137481451034546,0.40536701679229736,0.019605139270424843,-0.01446092501282692,0.9138293862342834,0.40465909242630005,0.019832054153084755,-0.013881584629416466,0.914147138595581,0.40434199571609497,0.020177623257040977,-0.013750072568655014,0.9142818450927734,0.40444663166999817,0.02038574032485485,-0.014500522054731846,0.9142193794250488,0.4041275978088379,0.020824622362852097,-0.015564115718007088,0.9143331050872803,0.4033201336860657,0.021515604108572006,-0.016013093292713165,0.9146658182144165,0.40255096554756165,0.02239718660712242,-0.01565438322722912,0.9149896502494812,0.4026505649089813,0.02271064929664135,-0.015329406596720219,0.9149435758590698,0.40281039476394653,0.02286756969988346,-0.015142003074288368,0.9148724675178528,0.4027928411960602,0.02288958989083767,-0.015055891126394272,0.9148810505867004,0.4027382433414459,0.022877562791109085,-0.015007274225354195,0.9149062037467957,0.4027460217475891,0.022920580580830574,-0.014984984882175922,0.9149020314216614,0.4027723968029022,0.02299346588551998,-0.015022078529000282,0.9148879647254944,0.4027767479419708,0.0231038648635149,-0.015186618082225323,0.9148805737495422,0.4027769863605499,0.02310345508158207,-0.015186754986643791,0.9148804545402527,0.4027771055698395,0.023103274405002594,-0.015186821110546589,0.9148804545402527,0.40277743339538574,0.023102793842554092,-0.015187022276222706,0.9148802757263184,0.402777761220932,0.023102277889847755,-0.015187223441898823,0.9148801565170288,0.4027775526046753,0.02310255356132984,-0.01518712006509304,0.9148802161216736]},{"name":"Bip001 R Thigh.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.00027724687242880464,0.09583603590726852,0.9890747666358948,0.11201084405183792,0.0030612191185355186,0.09623055905103683,0.9891318082809448,0.11112416535615921,0.005714045837521553,0.09690672159194946,0.9891372919082642,0.1103806272149086,0.007431161589920521,0.09727520495653152,0.9891566038131714,0.10977919399738312,0.008138473145663738,0.09711510688066483,0.9892128109931946,0.10936370491981506,0.008427317254245281,0.09642087668180466,0.9893076419830322,0.10909787565469742,0.00924585945904255,0.09536199271678925,0.9894422888755798,0.10874057561159134,0.010907105170190334,0.09462302178144455,0.9895591139793396,0.10816790163516998,0.012750565074384212,0.09493537247180939,0.9895739555358887,0.10755515098571777,0.01478500198572874,0.09619101136922836,0.9894874095916748,0.1069735512137413,0.018354028463363647,0.09753081202507019,0.989397406578064,0.10603611171245575,0.0243130624294281,0.09876995533704758,0.9893521666526794,0.10409464687108994,0.03234872967004776,0.10105107724666595,0.9892529249191284,0.10060236603021622,0.0415334478020668,0.10510051250457764,0.9889701008796692,0.0957445427775383,0.04990159720182419,0.10937690734863281,0.9885566830635071,0.09111613035202026,0.05477817356586456,0.11124280095100403,0.9883125424385071,0.08867214620113373,0.05577588081359863,0.11049286276102066,0.9883760213851929,0.08827904611825943,0.056256189942359924,0.10973276197910309,0.9885171055793762,0.08733753859996796,0.059368718415498734,0.1105886697769165,0.9885475039482117,0.08378152549266815,0.0640348419547081,0.1118050366640091,0.9885480403900146,0.0785616859793663,0.06736109405755997,0.11129031330347061,0.9887252449989319,0.07415778189897537,0.06894729286432266,0.10865400731563568,0.9890960454940796,0.0716211199760437,0.07055822759866714,0.10548802465200424,0.9894130229949951,0.07039657235145569,0.07305921614170074,0.1022941917181015,0.9896144866943359,0.06972389668226242,0.07602590322494507,0.09823962301015854,0.9897851347923279,0.06996046751737595,0.07860090583562851,0.09211993217468262,0.9899685382843018,0.07278847694396973,0.0794357880949974,0.08430328220129013,0.9901416301727295,0.07875601202249527,0.07795035094022751,0.07725343853235245,0.9902839064598083,0.08540147542953491,0.07623188197612762,0.07300674170255661,0.990347146987915,0.08984041959047318,0.07682429254055023,0.07102614641189575,0.9902415871620178,0.09205920994281769,0.07923296838998795,0.06965257227420807,0.9899858236312866,0.09380176663398743,0.08070125430822372,0.06830205768346786,0.9897909760475159,0.09558235853910446,0.07936684042215347,0.0674503892660141,0.9898571968078613,0.09661276638507843,0.07523542642593384,0.0673561692237854,0.9901676774024963,0.09680282324552536,0.06922270357608795,0.0678359866142273,0.9905533194541931,0.0970083475112915,0.06213036924600601,0.0687251016497612,0.9908923506736755,0.09771928936243057,0.05411333590745926,0.06991954892873764,0.9911693930625916,0.09882408380508423,0.04525288566946983,0.07143791019916534,0.9913689494132996,0.10018210858106613,0.03634769096970558,0.07328534871339798,0.991462767124176,0.1015363410115242,0.028557009994983673,0.07510079443454742,0.9914774298667908,0.10255187004804611,0.022063810378313065,0.07647938281297684,0.9914385080337524,0.10350723564624786,0.015898944810032845,0.07765766978263855,0.9912992119789124,0.10508269816637039,0.009735483676195145,0.07921108603477478,0.9910241961479187,0.10724692791700363,0.004522532690316439,0.08124063163995743,0.9906597137451172,0.10942064970731735,0.0010288039920851588,0.08346383273601532,0.9902638792991638,0.11140099912881851,-0.0013601119862869382,0.08570546656847,0.9898689985275269,0.11319070309400558,-0.0035299500450491905,0.08767145872116089,0.9895528554916382,0.11439584195613861,-0.0054728989489376545,0.08899375051259995,0.9893916249275208,0.11469244211912155,-0.006988673470914364,0.08970250934362411,0.9893519878387451,0.11439963430166245,-0.008354702964425087,0.09010511636734009,0.989338219165802,0.11411002278327942,-0.009656754322350025,0.09040514379739761,0.9893129467964172,0.11398936808109283,-0.010417959652841091,0.09072347730398178,0.9893001317977905,0.11378014087677002,-0.010348792187869549,0.09101059287786484,0.9893248081207275,0.1133420318365097,-0.009874904528260231,0.0911482572555542,0.9893707036972046,0.11287189275026321,-0.009892557747662067,0.09115664660930634,0.9894146919250488,0.11247724294662476,-0.010029582306742668,0.09101847559213638,0.9894556999206543,0.11221589148044586,-0.01010528951883316,0.09111776202917099,0.9894458055496216,0.11221598088741302,-0.010208871215581894,0.09125328809022903,0.9894295930862427,0.1122392937541008,-0.01031363382935524,0.09141553938388824,0.989409863948822,0.1122715175151825,-0.010394359938800335,0.09155131876468658,0.9893931746482849,0.11230055242776871,-0.010426837019622326,0.09160904586315155,0.9893860816955566,0.1123129278421402]},{"name":"Bip001 R Calf.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.33135607838630676,-0.0035666953772306442,0.029010260477662086,0.9430529475212097,-0.33156082034111023,-0.003397491993382573,0.028969546779990196,0.9429828524589539,-0.33109787106513977,-0.003502641338855028,0.02899886481463909,0.9431442022323608,-0.32991039752960205,-0.003567150328308344,0.028975849971175194,0.9435607194900513,-0.3285805284976959,-0.003579004667699337,0.02892318367958069,0.9440262317657471,-0.3275998532772064,-0.003661994356662035,0.02890698052942753,0.9443671703338623,-0.32705527544021606,-0.0038028545677661896,0.028920872136950493,0.9445549249649048,-0.3271523714065552,-0.003923213575035334,0.028943117707967758,0.944520115852356,-0.32812508940696716,-0.004031066317111254,0.02899361401796341,0.9441806077957153,-0.3297877013683319,-0.004163229838013649,0.029084965586662292,0.9435977935791016,-0.33142605423927307,-0.0042976997792720795,0.029179539531469345,0.9430201053619385,-0.33228275179862976,-0.004376578610390425,0.029220212250947952,0.9427169561386108,-0.33190470933914185,-0.004359014332294464,0.02917087823152542,0.942851722240448,-0.3303990364074707,-0.004250457976013422,0.029042238369584084,0.9433848261833191,-0.32882261276245117,-0.0041072554886341095,0.02890722081065178,0.9439402222633362,-0.3281775116920471,-0.00399059010669589,0.02883237786591053,0.9441674947738647,-0.32840198278427124,-0.0039061747957021,0.028809333220124245,0.9440904855728149,-0.32790324091911316,-0.003836043644696474,0.028757184743881226,0.9442657232284546,-0.32427626848220825,-0.0037690619938075542,0.028574690222740173,0.9455232620239258,-0.316410094499588,-0.0037113926373422146,0.02822495810687542,0.948195219039917,-0.3046361804008484,-0.003905604826286435,0.02780388481914997,0.9520548582077026,-0.2901516258716583,-0.0040958113968372345,0.02727244608104229,0.9565832018852234,-0.27455705404281616,-0.004082302562892437,0.02665378339588642,0.9611926674842834,-0.2578548491001129,-0.0040822699666023254,0.025825461372733116,0.9658298492431641,-0.2420303374528885,-0.004106658510863781,0.02490316331386566,0.9699403643608093,-0.2317916303873062,-0.004166115075349808,0.02428220957517624,0.9724534153938293,-0.22982828319072723,-0.004253120627254248,0.0241813063621521,0.9729214310646057,-0.2315666824579239,-0.004320900421589613,0.024311870336532593,0.9725055694580078,-0.2309812754392624,-0.004333871882408857,0.02428225427865982,0.9726454615592957,-0.22802452743053436,-0.004324997775256634,0.024096712470054626,0.973347544670105,-0.22592127323150635,-0.004356938414275646,0.02397901378571987,0.9738406538963318,-0.225486621260643,-0.004456516820937395,0.02398815006017685,0.9739407300949097,-0.22547923028469086,-0.004614031873643398,0.02404090017080307,0.973940372467041,-0.22616873681545258,-0.00481232488527894,0.024147486314177513,0.9737768769264221,-0.22908255457878113,-0.005013252608478069,0.024399489164352417,0.973088264465332,-0.23439247906208038,-0.005166724324226379,0.02479989267885685,0.9718119502067566,-0.24096056818962097,-0.005265503190457821,0.025268804281949997,0.9701915979385376,-0.24812057614326477,-0.0053445156663656235,0.0257615577429533,0.9683718085289001,-0.25541314482688904,-0.0054243686608970165,0.026244057342410088,0.9664605259895325,-0.2620871961116791,-0.005493493285030127,0.026661956682801247,0.9646601676940918,-0.268215537071228,-0.005538504105061293,0.027014726772904396,0.9629641771316528,-0.27483654022216797,-0.005561250727623701,0.02735108509659767,0.9610857963562012,-0.282340407371521,-0.005569016095250845,0.02767094597220421,0.9588989615440369,-0.2899278998374939,-0.005564376711845398,0.02797279879450798,0.9566234350204468,-0.296773225069046,-0.005554665811359882,0.02824348583817482,0.9545140862464905,-0.30252134799957275,-0.005543157458305359,0.028470713645219803,0.9527011513710022,-0.30672308802604675,-0.005523053463548422,0.02863406576216221,0.9513519406318665,-0.30937400460243225,-0.005487672984600067,0.02872953936457634,0.9504905343055725,-0.3113422691822052,-0.005451982840895653,0.02879742719233036,0.9498457908630371,-0.313468337059021,-0.005436096340417862,0.028880294412374496,0.9491438269615173,-0.31576627492904663,-0.005426215007901192,0.028972752392292023,0.9483790397644043,-0.3177688717842102,-0.005404271651059389,0.029047908261418343,0.9477077126502991,-0.3191806375980377,-0.005372977815568447,0.029094064608216286,0.9472319483757019,-0.32007071375846863,-0.005346355959773064,0.029119987040758133,0.9469309449195862,-0.32081156969070435,-0.005324683152139187,0.029141869395971298,0.9466796517372131,-0.321380615234375,-0.005302399396896362,0.029159409925341606,0.9464861750602722,-0.32206010818481445,-0.005299496930092573,0.029185809195041656,0.9462544322013855,-0.3226650059223175,-0.0052971020340919495,0.02920941449701786,0.9460476040840149,-0.32315394282341003,-0.005295879207551479,0.029228609055280685,0.945880115032196,-0.3234803378582001,-0.005295412614941597,0.02924155630171299,0.9457681179046631,-0.3236004412174225,-0.005295185837894678,0.029246265068650246,0.9457268714904785]},{"name":"Bip001 R Foot.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.494439035654068,0.0654541477560997,0.06977703422307968,0.8639311194419861,0.4947528541088104,0.06657446175813675,0.06822386384010315,0.8637898564338684,0.4939432144165039,0.06800437718629837,0.06800452619791031,0.8641590476036072,0.49332115054130554,0.06907633692026138,0.06806793808937073,0.8644243478775024,0.4932069480419159,0.07009211927652359,0.06807593256235123,0.8644071221351624,0.49322402477264404,0.07144202291965485,0.06795801222324371,0.8642961382865906,0.4934050440788269,0.07303842157125473,0.06786597520112991,0.8640666007995605,0.49396979808807373,0.07418770343065262,0.0680321529507637,0.8636327981948853,0.4947156608104706,0.07410288602113724,0.06856362521648407,0.8631710410118103,0.4953469932079315,0.07320725917816162,0.06930095702409744,0.862826406955719,0.49578577280044556,0.07310440391302109,0.06989835202693939,0.8625348806381226,0.495951384305954,0.0744955986738205,0.07019863277673721,0.8622962236404419,0.4955037832260132,0.07659849524497986,0.07031845301389694,0.8623595237731934,0.4941626787185669,0.07832609117031097,0.07046020776033401,0.8629621267318726,0.4924108684062958,0.07873603701591492,0.07088123261928558,0.8638911843299866,0.4911004304885864,0.0770992860198021,0.07177618145942688,0.8647105097770691,0.4904908537864685,0.07390110194683075,0.07307693362236023,0.8652266263961792,0.48965150117874146,0.07124055176973343,0.07438970357179642,0.865813136100769,0.4873185157775879,0.07126803696155548,0.07520744204521179,0.8670555949211121,0.4833102822303772,0.07434362173080444,0.07548782974481583,0.8690142631530762,0.47719696164131165,0.07936616986989975,0.07677990198135376,0.8718307614326477,0.46992936730384827,0.08421187102794647,0.07796090841293335,0.8752124309539795,0.46192917227745056,0.0882355347275734,0.07713759690523148,0.8791391849517822,0.4534183442592621,0.09333740174770355,0.07599430531263351,0.8831335306167603,0.445773720741272,0.09959432482719421,0.07488328963518143,0.8864306211471558,0.44112691283226013,0.1043403223156929,0.07458294928073883,0.8882328271865845,0.440413236618042,0.10519472509622574,0.07533615082502365,0.8884226083755493,0.44141265749931335,0.10402480512857437,0.07596874237060547,0.8880103826522827,0.44125425815582275,0.10477514564990997,0.07531493902206421,0.8880565762519836,0.43986764550209045,0.10817676782608032,0.07375698536634445,0.8884673118591309,0.43907392024993896,0.11209791898727417,0.072355717420578,0.8884890675544739,0.4396977126598358,0.11530306935310364,0.07159356027841568,0.8878319263458252,0.44098424911499023,0.11787120997905731,0.0716024786233902,0.8868553042411804,0.4422804117202759,0.11928123980760574,0.07258027791976929,0.8859413862228394,0.4439893364906311,0.11869415640830994,0.07416441291570663,0.8850337862968445,0.4467574954032898,0.11618608981370926,0.0754401907324791,0.8838649988174438,0.4502996504306793,0.11216188222169876,0.07624861598014832,0.8825169205665588,0.4537779986858368,0.1067650318145752,0.07724018394947052,0.8813176155090332,0.45684656500816345,0.1008242815732956,0.07864373177289963,0.8803072571754456,0.45983439683914185,0.09613402187824249,0.07994719594717026,0.8791581392288208,0.4632250964641571,0.09335073083639145,0.08077603578567505,0.877600908279419,0.4670492708683014,0.09094581753015518,0.08138342946767807,0.8757685422897339,0.4707608222961426,0.08763673901557922,0.08204180002212524,0.8740556240081787,0.47374677658081055,0.08387762308120728,0.08266563713550568,0.8727513551712036,0.4759666621685028,0.08053426444530487,0.08309532701969147,0.8718171119689941,0.4777953624725342,0.07761679589748383,0.08329924941062927,0.8710616827011108,0.47941023111343384,0.07507540285587311,0.08322523534297943,0.8704040050506592,0.4807852804660797,0.07327760756015778,0.08282983303070068,0.8698362708091736,0.4820393919944763,0.07221021503210068,0.08250471949577332,0.869262158870697,0.48326367139816284,0.07119730859994888,0.08262582868337631,0.868654191493988,0.4843275845050812,0.06974520534276962,0.08283599466085434,0.868159294128418,0.48525744676589966,0.06820042431354523,0.08274008333683014,0.8677718639373779,0.4861787259578705,0.0671439915895462,0.08241891860961914,0.8673690557479858,0.4870673716068268,0.0671134814620018,0.08213751763105392,0.8668994307518005,0.4876827597618103,0.06681831926107407,0.08193226903676987,0.8665956258773804,0.4880507290363312,0.06644625216722488,0.08167213946580887,0.8664416074752808,0.4881688058376312,0.06616701930761337,0.08174805343151093,0.866389274597168,0.48828426003456116,0.06591250002384186,0.08182884752750397,0.8663359880447388,0.4883824586868286,0.0657077357172966,0.08190234005451202,0.8662892580032349,0.48845037817955017,0.06557223200798035,0.08195842057466507,0.8662559390068054,0.4884762167930603,0.06552224606275558,0.08197811245918274,0.866243302822113]},{"name":"Bip001 R Toe0.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.4453660249710083,-0.013012758456170559,0.007651119027286768,0.8952213525772095,0.44549620151519775,-0.012791810557246208,0.007466444745659828,0.8951613306999207,0.4458116590976715,-0.012390161864459515,0.0071297516115009785,0.895012617111206,0.4461829364299774,-0.011846012435853481,0.006674293894320726,0.8948385119438171,0.44611236453056335,-0.011600211262702942,0.0064719910733401775,0.8948783874511719,0.44564661383628845,-0.01173123624175787,0.0065870583057403564,0.8951078653335571,0.4454021453857422,-0.011687392368912697,0.006554191000759602,0.8952303528785706,0.4456371068954468,-0.011219332925975323,0.006163613870739937,0.8951221704483032,0.44594675302505493,-0.010727804154157639,0.005752583499997854,0.8949767351150513,0.44600653648376465,-0.010392808355391026,0.00547453761100769,0.8949525952339172,0.44592490792274475,-0.009998335503041744,0.00514924107119441,0.8949996829032898,0.44599300622940063,-0.009473562240600586,0.004714175593107939,0.894973874092102,0.4464394748210907,-0.008890981785953045,0.004225832875818014,0.8947596549987793,0.4472140669822693,-0.008249394595623016,0.0036839747335761786,0.8943813443183899,0.4479027986526489,-0.007688141893595457,0.003209745977073908,0.8940434455871582,0.44812190532684326,-0.007491787895560265,0.003044240875169635,0.8939358592033386,0.44792455434799194,-0.007723772432655096,0.003239011624827981,0.8940321207046509,0.4476096034049988,-0.008123097941279411,0.0035739124286919832,0.8941850662231445,0.447375625371933,-0.008450884371995926,0.0038484446704387665,0.8942979574203491,0.4471670687198639,-0.008772118017077446,0.004117351491004229,0.8943979740142822,0.4468730092048645,-0.009235475212335587,0.004505203105509281,0.894538402557373,0.4465387463569641,-0.009768741205334663,0.004951675422489643,0.8946972489356995,0.4462631344795227,-0.01016344502568245,0.0052806115709245205,0.8948284983634949,0.4460327923297882,-0.010355714708566666,0.005443006753921509,0.8949401378631592,0.4457061290740967,-0.01042646262794733,0.005506263580173254,0.8951016664505005,0.4453391432762146,-0.010427736677229404,0.005512389354407787,0.8952842950820923,0.4451562464237213,-0.010335585102438927,0.005438933148980141,0.8953767418861389,0.4451214671134949,-0.010167213156819344,0.005299881566315889,0.8953967690467834,0.4448273181915283,-0.010060795582830906,0.005216146819293499,0.8955446481704712,0.44407084584236145,-0.010103707201778889,0.005262344144284725,0.8959192633628845,0.44328805804252625,-0.010162414982914925,0.005322278011590242,0.8963057994842529,0.4429149627685547,-0.010157379321753979,0.005323321558535099,0.8964902758598328,0.44291213154792786,-0.010276040062308311,0.0054214708507061005,0.8964897394180298,0.4431527554988861,-0.010600464418530464,0.005686463788151741,0.8963654041290283,0.4434710443019867,-0.010940260253846645,0.00596305588260293,0.8962020874023438,0.44355279207229614,-0.01117515005171299,0.006156242918223143,0.896157443523407,0.44340136647224426,-0.011304033920168877,0.0062650772742927074,0.8962299823760986,0.44337549805641174,-0.011144746094942093,0.00613372353836894,0.8962457180023193,0.44364556670188904,-0.01055221538990736,0.005639572162181139,0.8961224555969238,0.4440072476863861,-0.00989123061299324,0.005087520461529493,0.8959541320800781,0.44415295124053955,-0.009652094915509224,0.004887640941888094,0.8958856463432312,0.4440281093120575,-0.009812737815082073,0.005022394470870495,0.8959450721740723,0.4438573718070984,-0.009994152002036572,0.005174899473786354,0.8960267901420593,0.4438597559928894,-0.009978446178138256,0.005163646303117275,0.8960258364677429,0.44398412108421326,-0.009892330504953861,0.005090244580060244,0.895965576171875,0.44401270151138306,-0.009992544539272785,0.005172566510736942,0.8959498405456543,0.443885862827301,-0.010280181653797626,0.005412230268120766,0.8960080146789551,0.4437962770462036,-0.010521761141717434,0.00561318825930357,0.896048367023468,0.44377779960632324,-0.010671437717974186,0.005737236235290766,0.8960549831390381,0.44377657771110535,-0.01081771869212389,0.005858187098056078,0.8960530161857605,0.443867951631546,-0.010888033546507359,0.005915028043091297,0.8960065245628357,0.443928986787796,-0.010834688320755959,0.005870011635124683,0.895977258682251,0.44377681612968445,-0.010825859382748604,0.0058649335987865925,0.8960527777671814,0.4435424506664276,-0.011045355349779129,0.006049733143299818,0.8961648941040039,0.44339168071746826,-0.011405099183321,0.006349337752908468,0.8962329030036926,0.4433043301105499,-0.011690542101860046,0.0065867723897099495,0.896270751953125,0.44330427050590515,-0.011690547689795494,0.006586841307580471,0.896270751953125,0.44330424070358276,-0.01169058308005333,0.006586848292499781,0.896270751953125,0.4433041512966156,-0.011690507642924786,0.00658676540479064,0.8962708115577698,0.4433041214942932,-0.011690507642924786,0.00658677751198411,0.8962708115577698,0.44330406188964844,-0.011690493673086166,0.006586697418242693,0.8962708711624146]}]} \ No newline at end of file diff --git a/public/anims/baked/pistol/reload.json b/public/anims/baked/pistol/reload.json new file mode 100644 index 0000000..4e0dafe --- /dev/null +++ b/public/anims/baked/pistol/reload.json @@ -0,0 +1 @@ +{"name":"pistol/reload","duration":2,"tracks":[{"name":"Bip001 Pelvis.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.057442326098680496,-0.15881440043449402,-0.005046594887971878,0.9856231212615967,-0.05719399452209473,-0.15994738042354584,-0.0053848098032176495,0.9854525327682495,-0.05633072182536125,-0.16105574369430542,-0.005669484380632639,0.9853201508522034,-0.05508367717266083,-0.1614990383386612,-0.005913667846471071,0.9853166341781616,-0.053968507796525955,-0.16104978322982788,-0.006068362854421139,0.9854509234428406,-0.05337166041135788,-0.1599973440170288,-0.006165702827274799,0.9856542348861694,-0.053246598690748215,-0.15902911126613617,-0.0064057838171720505,0.9858161807060242,-0.05326816439628601,-0.15901513397693634,-0.00695612421259284,0.9858134984970093,-0.05319353565573692,-0.16043972969055176,-0.007783001754432917,0.9855805039405823,-0.05309281125664711,-0.16314345598220825,-0.008737594820559025,0.9851340055465698,-0.0531952939927578,-0.1668645143508911,-0.009816509671509266,0.9844948649406433,-0.05314745754003525,-0.17178753018379211,-0.011343736201524734,0.9836339354515076,-0.05173134058713913,-0.17844147980213165,-0.013737872242927551,0.9824936389923096,-0.04845954477787018,-0.18686893582344055,-0.016980044543743134,0.9810419678688049,-0.04525947570800781,-0.19567371904850006,-0.02025480754673481,0.979414701461792,-0.044774219393730164,-0.20257781445980072,-0.022559821605682373,0.9779818654060364,-0.0467921644449234,-0.20686937868595123,-0.02406252734363079,0.9769526720046997,-0.04802234470844269,-0.2104620784521103,-0.026158951222896576,0.9760713577270508,-0.045754846185445786,-0.21494439244270325,-0.029707083478569984,0.9751014709472656,-0.04034919664263725,-0.2189306914806366,-0.03393016383051872,0.9743151664733887,-0.03401270881295204,-0.22058869898319244,-0.03746558725833893,0.9740534424781799,-0.02829167991876602,-0.22075532376766205,-0.0396634079515934,0.9741116166114807,-0.022883020341396332,-0.22102676331996918,-0.040660109370946884,0.974151074886322,-0.016921045258641243,-0.22097615897655487,-0.04084848240017891,0.9742764830589294,-0.011949053034186363,-0.21988196671009064,-0.04012010991573334,0.9746279120445251,-0.012040145695209503,-0.2186782956123352,-0.03771095350384712,0.9749937057495117,-0.01842535473406315,-0.21789546310901642,-0.03347602114081383,0.9752237796783447,-0.02554015815258026,-0.21612247824668884,-0.02890574373304844,0.9756040573120117,-0.02776278182864189,-0.21298488974571228,-0.025558017194271088,0.9763265252113342,-0.026277288794517517,-0.21037206053733826,-0.02339603193104267,0.9769880771636963,-0.02509285695850849,-0.20912884175777435,-0.021454857662320137,0.9773306250572205,-0.024957187473773956,-0.20755185186862946,-0.01948375627398491,0.9777114987373352,-0.02429179474711418,-0.20406882464885712,-0.018100835382938385,0.9784877300262451,-0.023123852908611298,-0.19915515184402466,-0.017480289563536644,0.979539155960083,-0.02280467562377453,-0.1940155029296875,-0.016993196681141853,0.9805861115455627,-0.02365454100072384,-0.18908177316188812,-0.01617351546883583,0.9815431833267212,-0.025110075250267982,-0.1843123883008957,-0.015131043270230293,0.9824303984642029,-0.026835521683096886,-0.17981433868408203,-0.014094613492488861,0.9832334518432617,-0.028520697727799416,-0.17560699582099915,-0.013218550011515617,0.98395836353302,-0.029865631833672523,-0.17133808135986328,-0.012542441487312317,0.984679639339447,-0.031195512041449547,-0.16673898696899414,-0.011791110038757324,0.9854369163513184,-0.03306489437818527,-0.1621740758419037,-0.01066176500171423,0.9861503839492798,-0.035394541919231415,-0.15834711492061615,-0.009349025785923004,0.9867046475410461,-0.03772570192813873,-0.15569064021110535,-0.008184516802430153,0.987051248550415,-0.039822161197662354,-0.1542452871799469,-0.007039419841021299,0.9872046709060669,-0.041520968079566956,-0.15369941294193268,-0.005804036743938923,0.9872278571128845,-0.04263191670179367,-0.15342383086681366,-0.004872092045843601,0.9872283935546875,-0.043326541781425476,-0.15290133655071259,-0.0045866528525948524,0.9872806072235107,-0.044088687747716904,-0.15215207636356354,-0.004794251173734665,0.9873616099357605,-0.04522043094038963,-0.15152673423290253,-0.005138371605426073,0.9874048829078674,-0.0466005839407444,-0.15134845674037933,-0.00548408692702651,0.9873661994934082,-0.047901418060064316,-0.15175572037696838,-0.00590366730466485,0.9872390031814575,-0.04904999956488609,-0.15261061489582062,-0.0064262645319104195,0.9870474934577942,-0.04991089180111885,-0.1530967503786087,-0.006930303759872913,0.9869256615638733,-0.05076483264565468,-0.15343637764453888,-0.007412351202219725,0.9868258833885193,-0.0514955073595047,-0.15360002219676971,-0.007826324552297592,0.98675936460495,-0.05195924639701843,-0.15368600189685822,-0.007990952581167221,0.9867203235626221,-0.05230975151062012,-0.15373781323432922,-0.008121117949485779,0.9866926670074463,-0.052532948553562164,-0.15376435220241547,-0.008212544023990631,0.9866759181022644,-0.0526508130133152,-0.15377400815486908,-0.00826626643538475,0.9866676926612854,-0.0526852086186409,-0.1537754386663437,-0.008283931761980057,0.9866654872894287]},{"name":"Bip001 Spine.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.058890122920274734,-0.002268796321004629,-0.0032555856741964817,0.9982565641403198,0.05871879681944847,-0.0020294629503041506,-0.0031194668263196945,0.998267650604248,0.05842316150665283,-0.0020413121674209833,-0.0030580402817577124,0.9982851147651672,0.058217853307724,-0.0026588805485516787,-0.0032008786220103502,0.998295247554779,0.05832495167851448,-0.00400936184450984,-0.003583189332857728,0.9982831478118896,0.058835219591856,-0.005957160145044327,-0.004130032379180193,0.9982413649559021,0.059694256633520126,-0.008260766044259071,-0.004752474837005138,0.9981712102890015,0.06079007685184479,-0.010764963924884796,-0.005402964074164629,0.9980778694152832,0.06206434220075607,-0.013510475866496563,-0.0060434164479374886,0.9979624152183533,0.0635194331407547,-0.016612784937024117,-0.006667663808912039,0.9978200197219849,0.06512366980314255,-0.02003181166946888,-0.007334659807384014,0.9976491332054138,0.06670374423265457,-0.0235043466091156,-0.008047311566770077,0.9974634647369385,0.06793283671140671,-0.02668599970638752,-0.00867172796279192,0.9972952604293823,0.06859517842531204,-0.029412323608994484,-0.009129418060183525,0.9971691370010376,0.0689290463924408,-0.03186628594994545,-0.009605797939002514,0.9970661997795105,0.06945008784532547,-0.03433110564947128,-0.010388609021902084,0.9969403743743896,0.0703270360827446,-0.036678217351436615,-0.011481493711471558,0.9967833161354065,0.07119390368461609,-0.038373302668333054,-0.012512537650763988,0.9966455698013306,0.07167178392410278,-0.03923937678337097,-0.013139027170836926,0.9965695142745972,0.07177021354436874,-0.039826858788728714,-0.013450340367853642,0.9965349435806274,0.07169510424137115,-0.04063224047422409,-0.013827580958604813,0.996502697467804,0.07150149345397949,-0.04138981178402901,-0.014498220756649971,0.996475875377655,0.07104627043008804,-0.04163159430027008,-0.01531972549855709,0.9964861273765564,0.07026036828756332,-0.041403889656066895,-0.01596054434776306,0.9965412616729736,0.06946345418691635,-0.0408245213329792,-0.016415562480688095,0.996613621711731,0.06924989074468613,-0.03964067995548248,-0.01712009310722351,0.9966644048690796,0.06975730508565903,-0.03788840398192406,-0.01827077567577362,0.9966767430305481,0.07013151049613953,-0.03630681708455086,-0.019414851441979408,0.9966877102851868,0.06947699934244156,-0.035296812653541565,-0.02016550675034523,0.9967549443244934,0.06792646646499634,-0.03422723710536957,-0.02055501751601696,0.9968911409378052,0.0661277174949646,-0.03234405443072319,-0.020568890497088432,0.9970746636390686,0.06433377414941788,-0.029704559594392776,-0.019948476925492287,0.9972867369651794,0.062486402690410614,-0.026845192536711693,-0.018550930544734,0.9975122213363647,0.06073208898305893,-0.024049971252679825,-0.016652997583150864,0.9977253675460815,0.05931732431054115,-0.02128019742667675,-0.01469118706882,0.9979041814804077,0.05825154483318329,-0.018503770232200623,-0.012885211035609245,0.9980472922325134,0.057411160320043564,-0.01582021825015545,-0.011248834431171417,0.9981619119644165,0.05674397200345993,-0.013349352404475212,-0.009743002243340015,0.9982519745826721,0.05621388927102089,-0.011160383000969887,-0.008310073055326939,0.9983217716217041,0.0557791069149971,-0.009300604462623596,-0.0069326371885836124,0.9983757734298706,0.055491574108600616,-0.007758989464491606,-0.005698248744010925,0.9984127283096313,0.055452048778533936,-0.006399407051503658,-0.004674968775361776,0.9984298944473267,0.055658288300037384,-0.00512758269906044,-0.0038258384447544813,0.9984293580055237,0.05599304288625717,-0.004027721006423235,-0.0031352504156529903,0.9984180927276611,0.05631854757666588,-0.0031317593529820442,-0.002613944699987769,0.9984045028686523,0.05652524158358574,-0.0023058957885950804,-0.0022002514451742172,0.998396098613739,0.05657251179218292,-0.0014795981114730239,-0.0018155560828745365,0.9983957409858704,0.056522760540246964,-0.0007553955656476319,-0.00145837536547333,0.9983999729156494,0.056471195071935654,-0.00024226753157563508,-0.0011439042864367366,0.9984035491943359,0.05644981935620308,0.0000497000219183974,-0.0008351464057341218,0.9984050989151001,0.05645638331770897,0.0002062451676465571,-0.0005137253901921213,0.9984049201011658,0.056523073464632034,0.0003592244756873697,-0.00024161195324268192,0.9984012246131897,0.05671149492263794,0.0005860048113390803,-0.00006674982432741672,0.9983904361724854,0.05706048011779785,0.0008349301642738283,0.00006367614696500823,0.998370349407196,0.05749799683690071,0.0009713975014165044,0.00026390940183773637,0.998345136642456,0.05787258967757225,0.0009538338053971529,0.0005678182351402938,0.998323380947113,0.057872869074344635,0.0009552852134220302,0.0005656819557771087,0.9983233213424683,0.057873137295246124,0.0009564271313138306,0.0005631340900436044,0.9983233213424683,0.05787326768040657,0.0009574326104484499,0.0005605297046713531,0.9983233213424683,0.0578734464943409,0.000958221557084471,0.0005582850426435471,0.9983233213424683,0.057873714715242386,0.0009583779610693455,0.0005574403912760317,0.9983233213424683]},{"name":"Bip001 Spine1.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.04578443616628647,-0.0053108143620193005,-0.0059211705811321735,0.998919665813446,0.045440252870321274,-0.004833170212805271,-0.005650111008435488,0.9989393949508667,0.04484698548913002,-0.0048602852039039135,-0.005505177658051252,0.9989668726921082,0.04443510249257088,-0.006107335444539785,-0.005727709271013737,0.9989771842956543,0.04464952275156975,-0.008829900994896889,-0.00638166256248951,0.9989433288574219,0.045671164989471436,-0.012755066156387329,-0.007318923249840736,0.9988482594490051,0.0473906435072422,-0.017399171367287636,-0.008372509852051735,0.9986897706985474,0.04958323761820793,-0.022453470155596733,-0.009452064521610737,0.9984728097915649,0.05213215574622154,-0.027999715879559517,-0.010472378693521023,0.9981926679611206,0.05504269152879715,-0.034266162663698196,-0.011421260423958302,0.9978305101394653,0.058252427726984024,-0.04117247462272644,-0.012437143363058567,0.997374951839447,0.0614139661192894,-0.04819416627287865,-0.013541852124035358,0.9968562126159668,0.06387213617563248,-0.05463847145438194,-0.014466699212789536,0.996356189250946,0.06519506126642227,-0.06017236411571503,-0.015059487894177437,0.9959428310394287,0.0658617913722992,-0.06516565382480621,-0.01571604423224926,0.9955745339393616,0.06690603494644165,-0.07018890231847763,-0.017046496272087097,0.9951414465904236,0.06866380572319031,-0.0749816820025444,-0.01905900426208973,0.9946355223655701,0.07039954513311386,-0.07846564054489136,-0.02097601629793644,0.9942067265510559,0.07135386019945145,-0.0802706778049469,-0.02207830734550953,0.9939706921577454,0.07154736667871475,-0.08148286491632462,-0.02252611517906189,0.9938481450080872,0.07139424979686737,-0.08310312777757645,-0.02309270203113556,0.9937119483947754,0.07100510597229004,-0.08462710678577423,-0.024268560111522675,0.9935832023620605,0.07009248435497284,-0.08512552827596664,-0.025782311335206032,0.9935672879219055,0.06851725280284882,-0.0846664234995842,-0.026955172419548035,0.993685245513916,0.06691985577344894,-0.08348987996578217,-0.027814488857984543,0.993869960308075,0.06649201363325119,-0.08114514499902725,-0.029348375275731087,0.9940487742424011,0.06751109659671783,-0.07772649079561234,-0.03197638690471649,0.994172215461731,0.06826566904783249,-0.07464755326509476,-0.03459794819355011,0.9942688345909119,0.06695979088544846,-0.07266666740179062,-0.03626735135912895,0.9944449067115784,0.06385841220617294,-0.07054173201322556,-0.03712127357721329,0.9947702884674072,0.06025892496109009,-0.066778264939785,-0.037288468331098557,0.9952482581138611,0.056667931377887726,-0.061461299657821655,-0.03628546744585037,0.9958386421203613,0.05296868458390236,-0.055638350546360016,-0.033762793987989426,0.9964731931686401,0.04945487156510353,-0.04989991709589958,-0.03024264983832836,0.9970704913139343,0.04662122204899788,-0.044208548963069916,-0.0266082976013422,0.9975790977478027,0.04448643699288368,-0.03851746767759323,-0.02328609861433506,0.9979955554008484,0.0428026057779789,-0.033025145530700684,-0.020279692485928535,0.998331606388092,0.04146617278456688,-0.027967125177383423,-0.017515879124403,0.9985948204994202,0.040405768901109695,-0.02347586490213871,-0.014886743389070034,0.9987965822219849,0.03953651711344719,-0.01964406482875347,-0.012339102104306221,0.9989488124847412,0.03896171227097511,-0.016455426812171936,-0.010036895051598549,0.9990547895431519,0.03888363763689995,-0.013645623810589314,-0.008139173500239849,0.9991174340248108,0.03929821029305458,-0.011027751490473747,-0.006584994960576296,0.999144971370697,0.03996960446238518,-0.008770866319537163,-0.005323190242052078,0.9991482496261597,0.040621399879455566,-0.006941033527255058,-0.004363900516182184,0.9991409778594971,0.04103485122323036,-0.0052620102651417255,-0.003603628370910883,0.9991373419761658,0.041129034012556076,-0.003585304832085967,-0.0028987573459744453,0.9991431832313538,0.041028980165719986,-0.002111003501340747,-0.0022401828318834305,0.999153196811676,0.040926091372966766,-0.0010599155211821198,-0.0016598764341324568,0.9991602301597595,0.040885236114263535,-0.0004516189801506698,-0.0010936162434518337,0.9991631507873535,0.040901195257902145,-0.00011494869249872863,-0.0005028992309235036,0.9991630911827087,0.0410369336605072,0.00020651648810599,-0.0000022401147816708544,0.9991576075553894,0.04141489043831825,0.0006645774701610208,0.0003181016363669187,0.9991417527198792,0.04211284965276718,0.0011647917563095689,0.0005661225877702236,0.9991120100021362,0.04298820346593857,0.0014473773771896958,0.0009590064873918891,0.9990740418434143,0.04373813420534134,0.001431804383173585,0.0015631291316822171,0.9990407824516296,0.043738335371017456,0.0014318686444312334,0.0015634819865226746,0.9990407824516296,0.04373854398727417,0.0014318895991891623,0.0015637228498235345,0.9990407824516296,0.04373832419514656,0.0014319129986688495,0.0015638326294720173,0.9990407824516296,0.04373832792043686,0.0014318982139229774,0.0015638781478628516,0.9990407824516296,0.043738771229982376,0.0014318327885121107,0.001563903409987688,0.9990407228469849]},{"name":"Bip001 Spine2.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.04578439146280289,-0.005310717970132828,-0.0059211705811321735,0.998919665813446,0.04544024169445038,-0.004833147395402193,-0.005650110077112913,0.9989393949508667,0.04484698176383972,-0.004860325716435909,-0.00550517812371254,0.9989668726921082,0.04443510249257088,-0.006107340566813946,-0.0057277069427073,0.9989771842956543,0.04464954137802124,-0.008829852566123009,-0.006381665356457233,0.9989433288574219,0.04567119479179382,-0.012755079194903374,-0.007318923715502024,0.9988482594490051,0.0473906435072422,-0.017399130389094353,-0.008372520096600056,0.9986897706985474,0.049583274871110916,-0.022453509271144867,-0.009452066384255886,0.9984728097915649,0.05213218182325363,-0.027999740093946457,-0.010472375899553299,0.9981926679611206,0.05504271760582924,-0.03426618129014969,-0.011421267874538898,0.9978305101394653,0.05825245752930641,-0.041172515600919724,-0.01243714801967144,0.997374951839447,0.06141400337219238,-0.048194143921136856,-0.013541857711970806,0.9968562126159668,0.06387211382389069,-0.05463842302560806,-0.014466705732047558,0.996356189250946,0.06519503146409988,-0.06017247959971428,-0.015059460885822773,0.9959428310394287,0.0658617913722992,-0.06516561657190323,-0.015716059133410454,0.9955745339393616,0.06690609455108643,-0.070188969373703,-0.01704651489853859,0.9951414465904236,0.06866380572319031,-0.0749814435839653,-0.019059021025896072,0.9946355223655701,0.07039950042963028,-0.07846558839082718,-0.020976020023226738,0.9942067265510559,0.07135390490293503,-0.08027087897062302,-0.022078316658735275,0.9939706921577454,0.07154733687639236,-0.0814829170703888,-0.022526094689965248,0.9938481450080872,0.07139428704977036,-0.08310304582118988,-0.023092718794941902,0.9937119483947754,0.07100512087345123,-0.08462701737880707,-0.024268554523587227,0.9935832023620605,0.07009255141019821,-0.0851256251335144,-0.02578234300017357,0.9935672879219055,0.0685172826051712,-0.08466653525829315,-0.026955172419548035,0.993685245513916,0.06691986322402954,-0.08348965644836426,-0.02781451679766178,0.9938700199127197,0.0664919838309288,-0.08114491403102875,-0.0293483417481184,0.9940487742424011,0.06751114130020142,-0.07772652059793472,-0.031976375728845596,0.994172215461731,0.06826557964086533,-0.07464752346277237,-0.03459794074296951,0.9942688345909119,0.06695982813835144,-0.07266679406166077,-0.03626738116145134,0.9944448471069336,0.06385848671197891,-0.07054194062948227,-0.03712128475308418,0.9947702884674072,0.060258898884058,-0.06677823513746262,-0.037288419902324677,0.9952482581138611,0.056667860597372055,-0.0614609569311142,-0.03628549352288246,0.9958387017250061,0.052968692034482956,-0.05563846230506897,-0.03376276418566704,0.9964731335639954,0.049454912543296814,-0.04990000277757645,-0.030242642387747765,0.9970704913139343,0.04662121832370758,-0.044208478182554245,-0.026608308777213097,0.9975790977478027,0.0444863922894001,-0.038517363369464874,-0.023286093026399612,0.9979955554008484,0.04280264675617218,-0.033025238662958145,-0.020279698073863983,0.998331606388092,0.04146619513630867,-0.02796712890267372,-0.017515886574983597,0.9985948204994202,0.04040578007698059,-0.02347584441304207,-0.014886741526424885,0.9987965822219849,0.03953650966286659,-0.01964395120739937,-0.012339112348854542,0.9989488124847412,0.03896171227097511,-0.016455449163913727,-0.010036898776888847,0.9990547895431519,0.03888364136219025,-0.013645624741911888,-0.008139173500239849,0.9991174340248108,0.039298199117183685,-0.011027774773538113,-0.00658499076962471,0.999144971370697,0.03996960073709488,-0.008770793676376343,-0.005323196295648813,0.9991482496261597,0.040621399879455566,-0.006940955761820078,-0.004363907966762781,0.9991409778594971,0.04103483632206917,-0.005262045655399561,-0.003603623015806079,0.9991373419761658,0.04112900793552399,-0.0035852878354489803,-0.0028987503610551357,0.9991431832313538,0.041028980165719986,-0.002110895235091448,-0.0022401930764317513,0.999153196811676,0.04092606529593468,-0.00105991680175066,-0.001659879693761468,0.9991602301597595,0.040885262191295624,-0.0004517066408880055,-0.0010936131002381444,0.9991631507873535,0.04090117663145065,-0.00011500472464831546,-0.0005028903833590448,0.9991630911827087,0.0410369336605072,0.00020649403450079262,-0.0000022368415102391737,0.9991576075553894,0.04141490161418915,0.0006645972025580704,0.00031810958171263337,0.9991417527198792,0.042112819850444794,0.0011648352956399322,0.0005661189206875861,0.9991120100021362,0.04298821836709976,0.0014473645715042949,0.000959005847107619,0.9990740418434143,0.043738141655921936,0.001431765384040773,0.0015631384449079633,0.9990407824516296,0.04373835772275925,0.0014318365138024092,0.001563491066917777,0.9990407824516296,0.04373855143785477,0.0014318819157779217,0.0015637315809726715,0.9990407824516296,0.04373832046985626,0.0014318092726171017,0.0015638459008187056,0.9990407824516296,0.04373832419514656,0.0014318693429231644,0.0015638768672943115,0.9990407824516296,0.04373873770236969,0.001431989949196577,0.00156388187315315,0.9990407824516296]},{"name":"Bip001 Neck.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.010281497612595558,0.034093163907527924,0.06307851523160934,0.9973730444908142,0.010925276204943657,0.034754157066345215,0.06286134570837021,0.9973571300506592,0.01126832328736782,0.03603532537817955,0.06380236893892288,0.9972480535507202,0.01036476157605648,0.03816298395395279,0.06724399328231812,0.9969525337219238,0.007907616905868053,0.04134918004274368,0.07344302535057068,0.9964104890823364,0.004526545759290457,0.04572450369596481,0.08173495531082153,0.9955943822860718,0.001418989966623485,0.05123694986104965,0.09133114665746689,0.9945005774497986,-0.0002946346066892147,0.05784188583493233,0.10124114155769348,0.9931789636611938,-0.0003428803465794772,0.0657377615571022,0.11013896763324738,0.991739809513092,0.0007143378024920821,0.07504686713218689,0.11751911044120789,0.9902306199073792,0.0023826160468161106,0.08537153154611588,0.12436708807945251,0.9885539412498474,0.004687672480940819,0.09605221450328827,0.13166320323944092,0.9866188764572144,0.007992521859705448,0.10655666887760162,0.13909876346588135,0.9844964742660522,0.012651086784899235,0.11659707129001617,0.14584101736545563,0.9823316335678101,0.0183554794639349,0.12614805996418,0.15116432309150696,0.980254590511322,0.023691799491643906,0.13509804010391235,0.15458360314369202,0.9784125685691833,0.0268784761428833,0.14292210340499878,0.15651045739650726,0.9769111275672913,0.027102245017886162,0.14927779138088226,0.15799766778945923,0.9757142663002014,0.02529313415288925,0.154535710811615,0.15936435759067535,0.9747214913368225,0.023767739534378052,0.15908122062683105,0.16033081710338593,0.9738697409629822,0.02428452856838703,0.16252855956554413,0.1613094061613083,0.9731258749961853,0.026419540867209435,0.16414158046245575,0.1630576252937317,0.9725079536437988,0.027993634343147278,0.16385997831821442,0.1654140055179596,0.9721134305000305,0.026894383132457733,0.16249294579029083,0.16715893149375916,0.9720754027366638,0.02248973585665226,0.16060860455036163,0.16750307381153107,0.9724411368370056,0.015976684167981148,0.15787506103515625,0.1664619743824005,0.9731960892677307,0.009993573650717735,0.15395419299602509,0.1641102433204651,0.9743028879165649,0.007487047463655472,0.149193674325943,0.16160134971141815,0.9754846096038818,0.009974983520805836,0.14407828450202942,0.16077616810798645,0.9763672351837158,0.016006547957658768,0.13878421485424042,0.16047336161136627,0.9771034121513367,0.021999334916472435,0.1330048143863678,0.15754571557044983,0.9782663583755493,0.025670049712061882,0.12592746317386627,0.15164034068584442,0.9800451397895813,0.027327673509716988,0.1171031966805458,0.14487048983573914,0.9821164011955261,0.02833639830350876,0.10715382546186447,0.13831058144569397,0.9841672778129578,0.02971394546329975,0.09701765328645706,0.13104060292243958,0.9861708879470825,0.031604692339897156,0.08717481046915054,0.12209337949752808,0.9881775379180908,0.033427756279706955,0.0779605433344841,0.11185948550701141,0.9900970458984375,0.034884124994277954,0.06983475387096405,0.10166090726852417,0.9917516112327576,0.03638984635472298,0.063062384724617,0.09263008087873459,0.9930350184440613,0.03792675584554672,0.05738425627350807,0.08506203442811966,0.9939985275268555,0.038487356156110764,0.052236270159482956,0.07858842611312866,0.9947934150695801,0.037467047572135925,0.04723535105586052,0.07262810319662094,0.995535135269165,0.035603832453489304,0.04239068552851677,0.06715663522481918,0.9962055087089539,0.033879756927490234,0.0380425862967968,0.06229054927825928,0.9967571496963501,0.03286515176296234,0.03446142002940178,0.056871723383665085,0.9972451329231262,0.03293633088469505,0.03161832317709923,0.04973325505852699,0.9977184534072876,0.0339883416891098,0.029378741979599,0.04228901118040085,0.9980948567390442,0.03535117954015732,0.0277028139680624,0.037305377423763275,0.9982941150665283,0.036633141338825226,0.026623396202921867,0.03530566394329071,0.9983500242233276,0.03840276971459389,0.026046965271234512,0.03422883152961731,0.9983361959457397,0.04086993262171745,0.025549937039613724,0.03262239694595337,0.9983049035072327,0.0428023561835289,0.02467035874724388,0.031275320798158646,0.9982891082763672,0.04311765357851982,0.023477988317608833,0.031261734664440155,0.9983047246932983,0.04195736348628998,0.022483086213469505,0.0325765423476696,0.998335063457489,0.04038185998797417,0.022130299359560013,0.03442462161183357,0.9983459115028381,0.03983014449477196,0.02230747602880001,0.03600853309035301,0.9983082413673401,0.03983019292354584,0.02230745181441307,0.036008529365062714,0.9983082413673401,0.0398300439119339,0.022307461127638817,0.03600848466157913,0.9983082413673401,0.039830807596445084,0.022307489067316055,0.03600852191448212,0.9983081817626953,0.039830826222896576,0.022307511419057846,0.036008499562740326,0.9983081817626953,0.03982976824045181,0.02230750024318695,0.03600843623280525,0.9983082413673401]},{"name":"Bip001 Head.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.05189119651913643,0.0716434046626091,-0.08233070373535156,0.9926712512969971,-0.052161961793899536,0.07235594838857651,-0.08258044719696045,0.9925845861434937,-0.05274348706007004,0.07419981807470322,-0.08378726243972778,0.9923166036605835,-0.05366143211722374,0.07785000652074814,-0.08662714064121246,0.9917436838150024,-0.05481601506471634,0.08353806287050247,-0.09101010113954544,0.9908247590065002,-0.05646425113081932,0.09113923460245132,-0.09649596363306046,0.9895423054695129,-0.059340450912714005,0.10039205849170685,-0.10289769619703293,0.9878320693969727,-0.06414496153593063,0.11106165498495102,-0.11022856831550598,0.9855964779853821,-0.07076188921928406,0.12303697317838669,-0.11832897365093231,0.9827781319618225,-0.07792839407920837,0.1362588256597519,-0.12694627046585083,0.979410707950592,-0.08393312990665436,0.1503906100988388,-0.1356598138809204,0.9756712317466736,-0.08775881677865982,0.1647074967622757,-0.1435389369726181,0.9718880653381348,-0.08970139175653458,0.1784307360649109,-0.14984335005283356,0.9683300852775574,-0.09064147621393204,0.19112254679203033,-0.15455301105976105,0.9650749564170837,-0.09111999720335007,0.20260299742221832,-0.15759609639644623,0.9621915817260742,-0.09139307588338852,0.21259251236915588,-0.15875746309757233,0.9598165154457092,-0.0915130153298378,0.22071610391139984,-0.15839113295078278,0.9580302834510803,-0.0915471762418747,0.22703585028648376,-0.15728306770324707,0.9567318558692932,-0.09244494140148163,0.23208078742027283,-0.15585026144981384,0.9556689262390137,-0.09540367871522903,0.23614948987960815,-0.1539541333913803,0.9546882510185242,-0.09963167458772659,0.2390621155500412,-0.15181627869606018,0.9538735151290894,-0.10294495522975922,0.24054625630378723,-0.1503133922815323,0.9533864259719849,-0.10446982830762863,0.24052715301513672,-0.14956094324588776,0.9533437490463257,-0.10514304786920547,0.23906758427619934,-0.1480218768119812,0.9538769125938416,-0.10595247894525528,0.2360534816980362,-0.1439569592475891,0.9551592469215393,-0.10734713822603226,0.23109722137451172,-0.13711291551589966,0.9572203159332275,-0.10994859039783478,0.22427664697170258,-0.12888625264167786,0.9596872329711914,-0.11380598694086075,0.21692077815532684,-0.12185875326395035,0.9618440866470337,-0.11726509034633636,0.2105102837085724,-0.11798696964979172,0.9633345007896423,-0.11817629635334015,0.20457519590854645,-0.11559975147247314,0.9647901654243469,-0.11624698340892792,0.1971556842327118,-0.11125081777572632,0.9670778512954712,-0.11361157149076462,0.18718624114990234,-0.10474278032779694,0.9700941443443298,-0.11229083687067032,0.17534588277339935,-0.09880305826663971,0.9730789065361023,-0.11236850172281265,0.16304481029510498,-0.09529635310173035,0.9755553603172302,-0.11273995041847229,0.1508539915084839,-0.09332671761512756,0.9776619672775269,-0.11228780448436737,0.1384461522102356,-0.09062869846820831,0.9798012971878052,-0.11040963232517242,0.12592342495918274,-0.08614501357078552,0.9821059107780457,-0.1076139286160469,0.11447171121835709,-0.08153510838747025,0.9842090606689453,-0.10534656792879105,0.10527409613132477,-0.07920999825000763,0.9856699705123901,-0.10379920154809952,0.09811390191316605,-0.07911290973424911,0.9865801930427551,-0.10128290206193924,0.09165581315755844,-0.07887902855873108,0.9874811768531799,-0.09735208004713058,0.08497962355613708,-0.07711024582386017,0.988612711429596,-0.09402007609605789,0.07853870093822479,-0.07536085695028305,0.989602267742157,-0.0924946740269661,0.0731620341539383,-0.07515416294336319,0.9901736974716187,-0.09233944863080978,0.06847932934761047,-0.07501590996980667,0.9905335307121277,-0.09325364232063293,0.06360986083745956,-0.07282169163227081,0.9909362196922302,-0.09482009708881378,0.05893772095441818,-0.06943122297525406,0.9913197159767151,-0.09606361389160156,0.055658627301454544,-0.06724662333726883,0.9915401339530945,-0.09677386283874512,0.05395560339093208,-0.06645213067531586,0.9916187524795532,-0.0982099249958992,0.052885279059410095,-0.06551209092140198,0.9915977716445923,-0.10108043998479843,0.05169179290533066,-0.0642392709851265,0.9914554953575134,-0.1041700690984726,0.05040028318762779,-0.06378043442964554,0.9912317991256714,-0.10617764294147491,0.04938069358468056,-0.064462810754776,0.9910259246826172,-0.10682058334350586,0.04898146167397499,-0.06572060286998749,0.9908940196037292,-0.10643818229436874,0.049356698989868164,-0.06704216450452805,0.9908280372619629,-0.1061515361070633,0.050246357917785645,-0.06842751055955887,0.9907193779945374,-0.10615161806344986,0.05024639144539833,-0.06842757016420364,0.9907193183898926,-0.10615160316228867,0.05024637281894684,-0.06842759251594543,0.9907193183898926,-0.10615156590938568,0.05024639144539833,-0.06842760741710663,0.9907193183898926,-0.10615162551403046,0.05024636536836624,-0.06842760741710663,0.9907193183898926,-0.10615161806344986,0.05024637281894684,-0.06842758506536484,0.9907193183898926]},{"name":"Bip001 L Clavicle.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.6695586442947388,0.3204400837421417,-0.5321458578109741,0.4072224497795105,0.6700468063354492,0.31972792744636536,-0.5323095917701721,0.40676504373550415,0.6701382398605347,0.3195975422859192,-0.5323193073272705,0.4067041873931885,0.6693224310874939,0.32078883051872253,-0.5320364236831665,0.40747907757759094,0.6680855751037598,0.32258012890815735,-0.5316488146781921,0.4085991382598877,0.667258620262146,0.3237701952457428,-0.5314109921455383,0.40931791067123413,0.6672160029411316,0.3238237202167511,-0.531436562538147,0.40931183099746704,0.6675000786781311,0.3234044313430786,-0.5315820574760437,0.40899112820625305,0.6671253442764282,0.3239363431930542,-0.5315059423446655,0.40928035974502563,0.6657803654670715,0.32587143778800964,-0.5310724973678589,0.41049525141716003,0.664024293422699,0.3283889889717102,-0.5304780602455139,0.41209882497787476,0.6623649001121521,0.3307526707649231,-0.5299152731895447,0.4136003851890564,0.6607952117919922,0.33297595381736755,-0.5293781161308289,0.41501274704933167,0.6590554714202881,0.3354291021823883,-0.5287613272666931,0.41658690571784973,0.657207190990448,0.3380226492881775,-0.5280811190605164,0.41826993227005005,0.6556466817855835,0.34020185470581055,-0.5274902582168579,0.41969528794288635,0.6545190215110779,0.3417701721191406,-0.5270513296127319,0.4207313656806946,0.6534910202026367,0.3431962728500366,-0.5266416668891907,0.42168042063713074,0.6523821949958801,0.3447277545928955,-0.5262002348899841,0.4226979911327362,0.6516649127006531,0.34571611881256104,-0.5259104371070862,0.4233573079109192,0.6517474055290222,0.34560462832450867,-0.5259295701980591,0.42329761385917664,0.6523164510726929,0.3448236882686615,-0.5261405110359192,0.42279544472694397,0.6526317000389099,0.3443893790245056,-0.5262649059295654,0.4225080907344818,0.6524220108985901,0.3446757197380066,-0.5262007713317871,0.42267823219299316,0.6520677804946899,0.3451581597328186,-0.526094377040863,0.42296355962753296,0.65200275182724,0.34524232149124146,-0.526101291179657,0.42298653721809387,0.6525071263313293,0.34454527497291565,-0.526314377784729,0.4225117564201355,0.6537213921546936,0.34286877512931824,-0.5267833471298218,0.4214126765727997,0.6552910208702087,0.3406928777694702,-0.5273783206939697,0.41999301314353943,0.6565723419189453,0.3389074206352234,-0.5278606414794922,0.4188288748264313,0.657151460647583,0.33809730410575867,-0.5280799865722656,0.4182985723018646,0.6568672060966492,0.33849263191223145,-0.5279895663261414,0.4185394048690796,0.6557918190956116,0.33998504281044006,-0.5276318192481995,0.4194662272930145,0.6542903780937195,0.34205904603004456,-0.5271381139755249,0.420743465423584,0.6530376672744751,0.3437795639038086,-0.5267366766929626,0.42178890109062195,0.652642548084259,0.34431856870651245,-0.5266226530075073,0.42210307717323303,0.6530208587646484,0.34379780292510986,-0.5267620086669922,0.4217683970928192,0.6534766554832458,0.3431701064109802,-0.5269263982772827,0.42136815190315247,0.6535409092903137,0.34307852387428284,-0.5269650220870972,0.4212947189807892,0.6534180045127869,0.3432452380657196,-0.5269416570663452,0.4213787913322449,0.6537103056907654,0.3428410291671753,-0.5270485877990723,0.42112067341804504,0.6549593210220337,0.34112128615379333,-0.5274531245231628,0.42006877064704895,0.6569769382476807,0.3383297026157379,-0.5280869007110596,0.41837602853775024,0.6585609912872314,0.33612656593322754,-0.5285741090774536,0.41704410314559937,0.6591253876686096,0.33533886075019836,-0.5287444591522217,0.41657042503356934,0.6594250798225403,0.3349197506904602,-0.5288320183753967,0.4163220226764679,0.6600399613380432,0.3340606391429901,-0.5290087461471558,0.41581299901008606,0.6607142686843872,0.33311742544174194,-0.5291979312896729,0.4152577221393585,0.661160409450531,0.33249127864837646,-0.5293204188346863,0.4148932099342346,0.6615904569625854,0.33188894391059875,-0.5294417142868042,0.4145349860191345,0.6622081995010376,0.3310161828994751,-0.5296260118484497,0.4140107035636902,0.6627594828605652,0.33023709058761597,-0.5297908782958984,0.4135395586490631,0.6629567742347717,0.3299599885940552,-0.5298438668251038,0.4133765995502472,0.6627774834632874,0.3302149176597595,-0.5297814607620239,0.41354042291641235,0.662466287612915,0.33065682649612427,-0.5296841859817505,0.41381049156188965,0.6623838543891907,0.33077216148376465,-0.5296594500541687,0.41388198733329773,0.6623837947845459,0.3307722508907318,-0.5296594500541687,0.41388195753097534,0.6623837947845459,0.33077219128608704,-0.5296594500541687,0.4138820171356201,0.662384033203125,0.33077260851860046,-0.5296591520309448,0.41388165950775146,0.662384033203125,0.3307727575302124,-0.5296590328216553,0.41388171911239624,0.6623837351799011,0.3307720124721527,-0.5296595096588135,0.41388219594955444]},{"name":"Bip001 L UpperArm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.23281005024909973,0.08231665939092636,0.09526070207357407,0.9643385410308838,0.23133349418640137,0.08703360706567764,0.09607119858264923,0.9641993045806885,0.23054498434066772,0.09218394011259079,0.09558995813131332,0.9639573097229004,0.23119686543941498,0.09681522101163864,0.09420311450958252,0.9634835720062256,0.23193620145320892,0.1014123409986496,0.09296261519193649,0.9629533290863037,0.2310876101255417,0.1062145009636879,0.09222912788391113,0.9627101421356201,0.22809948027133942,0.1104063093662262,0.09134180098772049,0.9630357027053833,0.22427089512348175,0.1126655638217926,0.0898292139172554,0.9638152122497559,0.22214092314243317,0.11186526715755463,0.08792532980442047,0.9645769596099854,0.22248968482017517,0.10796447098255157,0.0855393335223198,0.9651554226875305,0.22371743619441986,0.10212676227092743,0.08232235163450241,0.9657865762710571,0.22418153285980225,0.09597671777009964,0.07855112850666046,0.9666234254837036,0.2235875427722931,0.09080798178911209,0.07485558837652206,0.9675531983375549,0.22284753620624542,0.08682618290185928,0.07138821482658386,0.9683511257171631,0.22277960181236267,0.08325078338384628,0.06802694499492645,0.9689225554466248,0.22247016429901123,0.07973598688840866,0.06572128087282181,0.9694482684135437,0.21994368731975555,0.07683401554822922,0.06628180295228958,0.9702206254005432,0.21481113135814667,0.0743473619222641,0.07005803287029266,0.9712983965873718,0.20828305184841156,0.07093668729066849,0.074806347489357,0.9726202487945557,0.20054665207862854,0.0662245824933052,0.07758834958076477,0.9743589758872986,0.19111540913581848,0.06150899454951286,0.0774374008178711,0.97657310962677,0.18121762573719025,0.05802629888057709,0.07561538368463516,0.9788132905960083,0.17358054220676422,0.055795930325984955,0.07360193133354187,0.9804791808128357,0.16960409283638,0.05430286377668381,0.0714988186955452,0.9814140796661377,0.16844093799591064,0.05362679809331894,0.06888911873102188,0.9818381071090698,0.16869263350963593,0.05417982488870621,0.06562158465385437,0.9819883704185486,0.16880224645137787,0.05580514669418335,0.06030696630477905,0.9822192788124084,0.16642294824123383,0.05813424661755562,0.050495076924562454,0.9830432534217834,0.16077643632888794,0.06063055619597435,0.03686262294650078,0.9844369292259216,0.15510979294776917,0.06189223751425743,0.02491600625216961,0.9856416583061218,0.15428927540779114,0.06040239334106445,0.02003168687224388,0.9859741926193237,0.16040150821208954,0.056419260799884796,0.022257419303059578,0.9851866960525513,0.17104938626289368,0.05293874442577362,0.027584893628954887,0.9834524393081665,0.1815292239189148,0.053427692502737045,0.03307879716157913,0.9813757538795471,0.18817222118377686,0.058301087468862534,0.038817938417196274,0.9796352982521057,0.1900901198387146,0.06481098383665085,0.04563022032380104,0.9785617589950562,0.18915610015392303,0.06956899911165237,0.05314986780285835,0.9780364036560059,0.1882529854774475,0.07099815458059311,0.060540903359651566,0.9776782989501953,0.18870972096920013,0.07005398720502853,0.06735184043645859,0.9772127866744995,0.18981324136257172,0.06860600411891937,0.07327625900506973,0.9766753315925598,0.19030937552452087,0.06774719059467316,0.07817886024713516,0.9762585163116455,0.1895388513803482,0.06731311231851578,0.08218847215175629,0.9761091470718384,0.1881018579006195,0.06624983996152878,0.08507946878671646,0.9762121438980103,0.18803967535495758,0.06368757039308548,0.08677709847688675,0.9762452244758606,0.19003868103027344,0.06038396805524826,0.0882209837436676,0.975938618183136,0.19208167493343353,0.058356501162052155,0.09069850295782089,0.9754347205162048,0.19246768951416016,0.05874546617269516,0.0945572704076767,0.9749687314033508,0.19179899990558624,0.06078527495265007,0.09880252927541733,0.9745544195175171,0.19152699410915375,0.06295350939035416,0.10215870290994644,0.9741241335868835,0.1919720619916916,0.0646459087729454,0.1042056456208229,0.9737088084220886,0.19240856170654297,0.06627127528190613,0.1055103987455368,0.9733728170394897,0.1918635219335556,0.06748170405626297,0.10806518793106079,0.973116934299469,0.19097155332565308,0.06928668171167374,0.11053219437599182,0.9728884100914001,0.1908177137374878,0.06986339390277863,0.1134742945432663,0.9725385904312134,0.19086208939552307,0.07049749791622162,0.11670099198818207,0.972102165222168,0.19083179533481598,0.0717586874961853,0.119047611951828,0.9717312455177307,0.19048796594142914,0.07319971174001694,0.11934790760278702,0.9716543555259705,0.19016249477863312,0.07450065016746521,0.11945906281471252,0.9716055989265442,0.18988363444805145,0.07555687427520752,0.11943374574184418,0.9715816974639893,0.18968886137008667,0.07626565545797348,0.11935660243034363,0.9715738296508789,0.18961547315120697,0.07652457058429718,0.11931232362985611,0.9715732336044312]},{"name":"Bip001 L Forearm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.021672792732715607,-0.00033608023659326136,0.282943993806839,0.9588914513587952,-0.021883999928832054,-0.00033504306338727474,0.28565388917922974,0.958082914352417,-0.022321639582514763,-0.00033417591475881636,0.2912646532058716,0.9563819766044617,-0.022882282733917236,-0.000336717494064942,0.2984454333782196,0.9541522860527039,-0.02339131198823452,-0.00034191180020570755,0.30496272444725037,0.9520769119262695,-0.02381754480302334,-0.00034541060449555516,0.310390442609787,0.9503106474876404,-0.024254370480775833,-0.0003446699702180922,0.3159182071685791,0.9484763145446777,-0.024727405980229378,-0.00034064793726429343,0.3219013810157776,0.9464501738548279,-0.025196945294737816,-0.0003379262052476406,0.3278385400772095,0.9443976283073425,-0.025656981393694878,-0.0003373754443600774,0.3336580693721771,0.9423449039459229,-0.02609851397573948,-0.0003359066613484174,0.3392544984817505,0.9403324723243713,-0.026496633887290955,-0.0003311647742521018,0.3443296551704407,0.9384747743606567,-0.026853976771235466,-0.0003221197403036058,0.34892183542251587,0.9367669224739075,-0.027183514088392258,-0.00030923940357752144,0.35321083664894104,0.9351487159729004,-0.027482163161039352,-0.0002949443878605962,0.35713833570480347,0.933647096157074,-0.02772654965519905,-0.00028229589224793017,0.3603353798389435,0.9324105978012085,-0.027893079444766045,-0.00027140468591824174,0.3624538481235504,0.9315841794013977,-0.028014155104756355,-0.0002588791830930859,0.3639327585697174,0.9310038089752197,-0.028204530477523804,-0.0002406450657872483,0.36632391810417175,0.9300597906112671,-0.028556549921631813,-0.00021584730711765587,0.37088513374328613,0.9282395839691162,-0.029006056487560272,-0.00018880497373174876,0.37677279114723206,0.9258514642715454,-0.02937728352844715,-0.00016562495147809386,0.38166549801826477,0.9238335490226746,-0.0295809805393219,-0.0001507496926933527,0.38439470529556274,0.9226948022842407,-0.029710106551647186,-0.00014170016220305115,0.3862054646015167,0.921934187412262,-0.029902471229434013,-0.00013496215979103,0.3888736069202423,0.9208056926727295,-0.030210167169570923,-0.0001304837060160935,0.39299675822257996,0.9190434813499451,-0.03066059947013855,-0.00012638315092772245,0.39891237020492554,0.916476309299469,-0.03125175088644028,-0.00011926847946597263,0.40668970346450806,0.9130316376686096,-0.03175746276974678,-0.00011251436808379367,0.4134352207183838,0.909979522228241,-0.031781893223524094,-0.00011475017527118325,0.4139156639575958,0.9097602367401123,-0.031195079907774925,-0.00012713970500044525,0.40636497735977173,0.9131781458854675,-0.030353229492902756,-0.0001430301635991782,0.39545536041259766,0.9179834723472595,-0.029759380966424942,-0.00015564914792776108,0.3878323435783386,0.9212493896484375,-0.029574770480394363,-0.00016336906992364675,0.3855830132961273,0.922199010848999,-0.0295396875590086,-0.00017006511916406453,0.38522833585739136,0.9223483800888062,-0.02932567708194256,-0.00017917285731527954,0.38242092728614807,0.9235227108001709,-0.02883727103471756,-0.0001905558310681954,0.37592813372612,0.9261999726295471,-0.028172263875603676,-0.0002047694579232484,0.3670749366283417,0.9297646284103394,-0.027478137984871864,-0.0002212819963460788,0.35782358050346375,0.9333847761154175,-0.026886792853474617,-0.00023709004744887352,0.3499327003955841,0.9363889098167419,-0.026463337242603302,-0.00024910259526222944,0.3442671597003937,0.9384986758232117,-0.02619735151529312,-0.0002558354171924293,0.34067919850349426,0.9398145079612732,-0.026066916063427925,-0.0002581487933639437,0.33886414766311646,0.9404740929603577,-0.026019377633929253,-0.00025970159913413227,0.3381224572658539,0.9407423138618469,-0.02593674138188362,-0.00026349324616603553,0.3369351923465729,0.9411705136299133,-0.02573247253894806,-0.00026849727146327496,0.3342008888721466,0.9421504735946655,-0.02544383332133293,-0.00027164563653059304,0.3304287791252136,0.9434878826141357,-0.025197191163897514,-0.00027282198425382376,0.3272457718849182,0.9446032047271729,-0.0250769704580307,-0.00027381457039155066,0.3257073163986206,0.9451379776000977,-0.025042854249477386,-0.0002744767989497632,0.32528549432754517,0.9452841281890869,-0.02499682642519474,-0.0002745819219853729,0.32471057772636414,0.9454830288887024,-0.02471400424838066,-0.00027429661713540554,0.32106277346611023,0.9467353820800781,-0.024518394842743874,-0.0002756730536930263,0.31850704550743103,0.9476032853126526,-0.024374857544898987,-0.00027849432080984116,0.31658995151519775,0.9482492208480835,-0.0241687148809433,-0.00028285279404371977,0.31386250257492065,0.9491607546806335,-0.02396753802895546,-0.0002869008167181164,0.31122562289237976,0.9500337243080139,-0.023965798318386078,-0.0002876638900488615,0.31120458245277405,0.9500406384468079,-0.023990394547581673,-0.0002883540000766516,0.31151771545410156,0.9499374032020569,-0.024029355496168137,-0.0002889408206101507,0.31201285123825073,0.949773907661438,-0.024065285921096802,-0.00028933072462677956,0.3124706447124481,0.9496225118637085,-0.02408103086054325,-0.00028956218739040196,0.31267181038856506,0.9495558738708496]},{"name":"Bip001 R Clavicle.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.6659364104270935,-0.3323783576488495,0.4817703366279602,0.46254801750183105,0.6644406914710999,-0.33437684178352356,0.48214420676231384,0.46286889910697937,0.6618736982345581,-0.3377741873264313,0.4828279912471771,0.46336695551872253,0.6583617329597473,-0.3423594534397125,0.4838271737098694,0.4639624059200287,0.654269814491272,-0.3476114869117737,0.48506736755371094,0.4645501673221588,0.6501113772392273,-0.352853387594223,0.4863744080066681,0.46506941318511963,0.6465917825698853,-0.35721462965011597,0.48745477199554443,0.46551549434661865,0.6443252563476562,-0.3599880337715149,0.4880346953868866,0.4659138321876526,0.6430732011795044,-0.3615063428878784,0.4882175624370575,0.4662763178348541,0.6418465375900269,-0.3629869520664215,0.4884207546710968,0.46660327911376953,0.6402575373649597,-0.3648945689201355,0.48873934149742126,0.4669647514820099,0.6388003826141357,-0.3666313588619232,0.4889047145843506,0.4674266278743744,0.6375844478607178,-0.36807239055633545,0.48901525139808655,0.46783849596977234,0.636222779750824,-0.36968114972114563,0.4895079731941223,0.46790850162506104,0.6346491575241089,-0.37153181433677673,0.49046796560287476,0.46757426857948303,0.6328509449958801,-0.37363195419311523,0.49161240458488464,0.46713605523109436,0.6304522156715393,-0.3764049708843231,0.4927692413330078,0.4669343829154968,0.6273792386054993,-0.37991654872894287,0.4938907027244568,0.4670446217060089,0.6235042810440063,-0.3842816948890686,0.495027095079422,0.46745923161506653,0.6179326772689819,-0.3904428482055664,0.49640953540802,0.46828535199165344,0.6101764440536499,-0.39880308508872986,0.4981658458709717,0.46954405307769775,0.6010044813156128,-0.4083808660507202,0.5001715421676636,0.47100645303726196,0.5918533802032471,-0.4176214337348938,0.5021011233329773,0.4724366068840027,0.5838991403579712,-0.4254113733768463,0.5035943388938904,0.4737929105758667,0.5775254964828491,-0.431498646736145,0.5045673847198486,0.47506311535835266,0.571780800819397,-0.43687140941619873,0.5053772330284119,0.47623932361602783,0.5645983219146729,-0.443440318107605,0.506525456905365,0.4775157868862152,0.5550260543823242,-0.45194873213768005,0.5080240964889526,0.4791658818721771,0.5454434752464294,-0.4602002799510956,0.5091356039047241,0.48113206028938293,0.5393791198730469,-0.46529415249824524,0.5092569589614868,0.482937753200531,0.5376725792884827,-0.46681177616119385,0.508499264717102,0.4841730296611786,0.5394830107688904,-0.46541252732276917,0.5077222585678101,0.48432156443595886,0.5436187982559204,-0.462221622467041,0.5067684054374695,0.4837515652179718,0.5500116944313049,-0.45718467235565186,0.5053219199180603,0.48282405734062195,0.5565694570541382,-0.4515136778354645,0.5053626894950867,0.48059794306755066,0.5640343427658081,-0.4447231590747833,0.505381166934967,0.4782012403011322,0.5718927383422852,-0.43716275691986084,0.5053886771202087,0.4758252799510956,0.5798271298408508,-0.4290466010570526,0.5053136348724365,0.4736851453781128,0.5857350826263428,-0.4182310700416565,0.5061871409416199,0.47515445947647095,0.5920154452323914,-0.40699830651283264,0.5066869854927063,0.4765903651714325,0.5972324013710022,-0.39342617988586426,0.5075066089630127,0.4805895686149597,0.6008914113044739,-0.37831631302833557,0.5085293650627136,0.4870361089706421,0.6028837561607361,-0.3642613887786865,0.5095335245132446,0.4941866099834442,0.6037392020225525,-0.35276728868484497,0.5103631615638733,0.5005833506584167,0.603910505771637,-0.34326428174972534,0.5109882950782776,0.5063127875328064,0.6036534309387207,-0.335002601146698,0.5114469528198242,0.5116618275642395,0.6032245755195618,-0.3278171122074127,0.5118168592453003,0.516429603099823,0.6028305888175964,-0.32183945178985596,0.5121585726737976,0.5202962756156921,0.6024940013885498,-0.31735071539878845,0.5124232769012451,0.5231748223304749,0.6021841168403625,-0.3143043518066406,0.512570321559906,0.5252225995063782,0.6018690466880798,-0.3119848370552063,0.5126466751098633,0.5268895030021667,0.6015373468399048,-0.3093787431716919,0.5127503275871277,0.5287010073661804,0.6012036800384521,-0.3063155710697174,0.5129110217094421,0.5307043790817261,0.600908637046814,-0.3034636676311493,0.5130783319473267,0.5325121879577637,0.6006703972816467,-0.30130699276924133,0.513200044631958,0.5338866114616394,0.6004815697669983,-0.29992371797561646,0.5132613182067871,0.534818172454834,0.6006185412406921,-0.29968541860580444,0.5132994055747986,0.534761369228363,0.6009453535079956,-0.29911699891090393,0.5133900046348572,0.5346254706382751,0.6013352870941162,-0.2984393537044525,0.5134969353675842,0.5344629883766174,0.6016618013381958,-0.2978712320327759,0.5135863423347473,0.5343265533447266,0.6017979979515076,-0.2976325750350952,0.5136237740516663,0.5342702269554138]},{"name":"Bip001 R UpperArm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.3425823152065277,-0.010915359482169151,0.04358949512243271,0.938412606716156,0.34463033080101013,-0.022652795538306236,0.045450929552316666,0.937363862991333,0.3487383723258972,-0.0473351776599884,0.04696228355169296,0.9348451495170593,0.356711745262146,-0.08207765221595764,0.048281408846378326,0.9293486475944519,0.3699456453323364,-0.1205691248178482,0.05103844776749611,0.9197816848754883,0.38864773511886597,-0.1585720032453537,0.05730810761451721,0.9058275818824768,0.4122386872768402,-0.19355003535747528,0.06844369322061539,0.8876447081565857,0.43923524022102356,-0.223965585231781,0.08408024162054062,0.8659343719482422,0.4674268960952759,-0.2500191032886505,0.10163342952728271,0.8418272733688354,0.49499836564064026,-0.27257487177848816,0.11798834800720215,0.8165526986122131,0.5212144255638123,-0.2923106551170349,0.13113895058631897,0.7910073399543762,0.5460002422332764,-0.3094855844974518,0.13910162448883057,0.765998125076294,0.5683496594429016,-0.3210214674472809,0.14141052961349487,0.7442626953125,0.5860191583633423,-0.32357436418533325,0.14012806117534637,0.7295514345169067,0.5974764227867126,-0.3199053406715393,0.13636264204978943,0.7225563526153564,0.603047251701355,-0.3152531385421753,0.13033640384674072,0.7210838198661804,0.60451340675354,-0.3121836185455322,0.12390795350074768,0.7223238348960876,0.6032590866088867,-0.3114350140094757,0.12104086577892303,0.7241794466972351,0.5990465879440308,-0.312762588262558,0.12575022876262665,0.7262985706329346,0.5785918831825256,-0.3405667543411255,0.11457005888223648,0.7322017550468445,0.5498138666152954,-0.37139174342155457,0.10419641435146332,0.7408886551856995,0.5149046778678894,-0.4020090401172638,0.09408090263605118,0.7512727379798889,0.4775574803352356,-0.4305051267147064,0.08517993241548538,0.761149525642395,0.4416966736316681,-0.45504486560821533,0.07833769172430038,0.769221305847168,0.4099847674369812,-0.4736718535423279,0.07381926476955414,0.7759498357772827,0.38144975900650024,-0.48506101965904236,0.07361700385808945,0.7834490537643433,0.3552463948726654,-0.4840351641178131,0.08567458391189575,0.7950910925865173,0.3274631202220917,-0.46823418140411377,0.10989827662706375,0.8132939338684082,0.2955462634563446,-0.4369373917579651,0.13539697229862213,0.8386929035186768,0.2640817165374756,-0.3959256410598755,0.1533249020576477,0.8660226464271545,0.23722520470619202,-0.3480268716812134,0.16269102692604065,0.8922629356384277,0.21434874832630157,-0.2911117970943451,0.16473956406116486,0.9176979064941406,0.19623726606369019,-0.2308356761932373,0.16047753393650055,0.9393895864486694,0.1842772662639618,-0.17357848584651947,0.1506204456090927,0.955628514289856,0.1782888025045395,-0.1258043497800827,0.1305311918258667,0.9671338796615601,0.1778687834739685,-0.08852463960647583,0.10431371629238129,0.9744971394538879,0.17983107268810272,-0.058876361697912216,0.07517238706350327,0.9790523052215576,0.1953565627336502,-0.02456897683441639,0.04832596704363823,0.9792327284812927,0.2153998166322708,0.0018613007850944996,0.026990357786417007,0.9761511087417603,0.23472578823566437,0.021647311747074127,0.008555660024285316,0.971782922744751,0.250588595867157,0.031747475266456604,-0.0049771578051149845,0.9675601720809937,0.2617235779762268,0.034580424427986145,-0.012548201717436314,0.9644415378570557,0.2704612910747528,0.03494222089648247,-0.01596110500395298,0.9619641304016113,0.279544472694397,0.036485929042100906,-0.018437329679727554,0.9592620730400085,0.28924256563186646,0.03898660093545914,-0.0208317581564188,0.9562346935272217,0.2984175682067871,0.04056946933269501,-0.022562135010957718,0.9533058404922485,0.3067152798175812,0.04098811000585556,-0.020559486001729965,0.9506960511207581,0.31488049030303955,0.040653035044670105,-0.016898617148399353,0.9481097459793091,0.3231654167175293,0.04073720797896385,-0.012582727707922459,0.9453815221786499,0.3314710259437561,0.04094281047582626,-0.008833848871290684,0.9425352215766907,0.3395737111568451,0.04088000953197479,-0.0052189212292432785,0.9396761655807495,0.3473599851131439,0.04070134833455086,-0.00019297856488265097,0.9368481040000916,0.354352742433548,0.04129406809806824,0.0061525083146989346,0.9341793656349182,0.3598732650279999,0.0418614000082016,0.012995429337024689,0.93197101354599,0.360996812582016,0.03977995365858078,0.014677555300295353,0.9316025972366333,0.3619309365749359,0.03866374492645264,0.015058380551636219,0.9312810301780701,0.36285099387168884,0.0374806672334671,0.01513766497373581,0.9309700131416321,0.3633851110935211,0.03600678592920303,0.015751253813505173,0.9308096766471863,0.36363595724105835,0.03456554934382439,0.016592368483543396,0.9307517409324646,0.3637080490589142,0.03347253054380417,0.017334533855319023,0.9307500123977661,0.36370694637298584,0.03304247185587883,0.01765287294983864,0.9307597875595093]},{"name":"Bip001 R Forearm.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.02552872896194458,0.0007447588723152876,-0.34514501690864563,0.9382018446922302,-0.026065636426210403,0.0007375529385171831,-0.3525475263595581,0.9354305267333984,-0.027004586532711983,0.0007250187918543816,-0.3654865622520447,0.9304245114326477,-0.02822853811085224,0.0007069617859087884,-0.3823009431362152,0.9236063361167908,-0.029515206813812256,0.0006933624972589314,-0.3999166488647461,0.9160758852958679,-0.030656613409519196,0.0007006247760728002,-0.41551607847213745,0.909068763256073,-0.03149421885609627,0.000737613532692194,-0.42697885632514954,0.9037126898765564,-0.032027363777160645,0.0007986867567524314,-0.4343317151069641,0.9001830816268921,-0.03247007727622986,0.0008695528958924115,-0.44048431515693665,0.8971725106239319,-0.033114172518253326,0.0009358465322293341,-0.44938990473747253,0.8927212953567505,-0.0340472050011158,0.0009988633682951331,-0.4622713625431061,0.8860840797424316,-0.03511553257703781,0.001076814136467874,-0.47725221514701843,0.8780637979507446,-0.036261964589357376,0.001158819766715169,-0.493604838848114,0.8689292073249817,-0.037495844066143036,0.0012047080090269446,-0.5111820101737976,0.8586533665657043,-0.038658175617456436,0.001210117363370955,-0.5275977253913879,0.8486133813858032,-0.03947646915912628,0.0012083122273907065,-0.5390715003013611,0.8413335084915161,-0.03984881937503815,0.001217345823533833,-0.5441643595695496,0.8380308747291565,-0.040105197578668594,0.0012203296646475792,-0.5473906993865967,0.8359147906303406,-0.04086953029036522,0.0011884730774909258,-0.5572647452354431,0.8293276429176331,-0.04242141172289848,0.0011290869442746043,-0.5780735015869141,0.8148804306983948,-0.04453058913350105,0.0010403151391074061,-0.6063831448554993,0.7939240336418152,-0.04677248001098633,0.0009379183175042272,-0.6365522742271423,0.7698133587837219,-0.04890820384025574,0.0008326074457727373,-0.6654555201530457,0.7448330521583557,-0.050866398960351944,0.0007340704323723912,-0.6921429634094238,0.719965398311615,-0.05259478837251663,0.000648669374641031,-0.7157931327819824,0.6963286399841309,-0.05414879322052002,0.0005747906398028135,-0.7369354367256165,0.6737905740737915,-0.0558331124484539,0.0004957683268003166,-0.7594430446624756,0.6481733322143555,-0.057802360504865646,0.00038571280310861766,-0.7855014204978943,0.6161544322967529,-0.05962216481566429,0.00024031191424001008,-0.8096539378166199,0.5838713049888611,-0.06098382920026779,0.00009486210183240473,-0.8280583620071411,0.5573152899742126,-0.06197286397218704,-0.00003266265775891952,-0.8418017625808716,0.5362173914909363,-0.06260520964860916,-0.00014233171532396227,-0.8508420586585999,0.5216784477233887,-0.06288667023181915,-0.0002217282453784719,-0.8551520705223083,0.5145485401153564,-0.06283897906541824,-0.0002649641828611493,-0.8550192713737488,0.5147749185562134,-0.06251367926597595,-0.0002805628173518926,-0.8512779474258423,0.520977795124054,-0.062037430703639984,-0.0002751637948676944,-0.8456729650497437,0.5300834774971008,-0.06144995614886284,-0.00025706543237902224,-0.8386292457580566,0.541225254535675,-0.0607820488512516,-0.00023218987917061895,-0.8304887413978577,0.5537092685699463,-0.060085318982601166,-0.00020260116434656084,-0.8218240737915039,0.5665640830993652,-0.05942779779434204,-0.0001751822273945436,-0.8136191368103027,0.5783529877662659,-0.05877390503883362,-0.00014853141328785568,-0.8053211569786072,0.5899181962013245,-0.058110080659389496,-0.0001236332464031875,-0.7966904640197754,0.6015874743461609,-0.05751024931669235,-0.00010386089707026258,-0.7887772917747498,0.6119828224182129,-0.05699944868683815,-0.00008965151209849864,-0.7820297479629517,0.6206291317939758,-0.05658307671546936,-0.00007842712511774153,-0.7765945792198181,0.6274545788764954,-0.056347865611314774,-0.00006908947398187593,-0.773705244064331,0.6310349702835083,-0.05624108016490936,-0.000059934205637546256,-0.7725433707237244,0.6324663162231445,-0.0562027208507061,-0.000053513256716541946,-0.7722522020339966,0.6328252553939819,-0.05617644265294075,-0.00004970558438799344,-0.7720531225204468,0.6330704689025879,-0.05614207312464714,-0.00004702182923210785,-0.7716962695121765,0.6335084438323975,-0.05610877647995949,-0.00004440258635440841,-0.7713395953178406,0.6339455842971802,-0.05609472841024399,-0.00004162088589509949,-0.7712391018867493,0.6340690851211548,-0.056136731058359146,-0.00003977480446337722,-0.7719117999076843,0.6332463026046753,-0.05623500421643257,-0.00003933994594262913,-0.7733385562896729,0.6314943432807922,-0.056222885847091675,-0.00003996097075287253,-0.7731834650039673,0.6316852569580078,-0.05627314746379852,-0.000042609142838045955,-0.7739042639732361,0.6307975649833679,-0.056190039962530136,-0.00003991895937360823,-0.7727606892585754,0.6322053074836731,-0.05613067001104355,-0.00003723366171470843,-0.7719451785087585,0.6332060694694519,-0.05609307065606117,-0.00003486414789222181,-0.7714331746101379,0.633833110332489,-0.0560736246407032,-0.000033430569601478055,-0.7711665630340576,0.6341591477394104,-0.05606822296977043,-0.00003296386421425268,-0.7710906863212585,0.6342518925666809]},{"name":"Bip001 L Thigh.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.03252058103680611,-0.04403537139296532,-0.9917194843292236,0.11617125570774078,-0.03308558091521263,-0.04429936036467552,-0.9916461706161499,0.11653656512498856,-0.03334667161107063,-0.044935088604688644,-0.9915827512741089,0.11675839126110077,-0.03371709585189819,-0.045789044350385666,-0.9915042519569397,0.11698633432388306,-0.03446489945054054,-0.046505119651556015,-0.9914118647575378,0.11726886034011841,-0.035152968019247055,-0.046780429780483246,-0.9913433790206909,0.11753373593091965,-0.03530278429389,-0.04668150842189789,-0.9913118481636047,0.11779376864433289,-0.03534621372818947,-0.04669155925512314,-0.9912604689598083,0.11820857226848602,-0.03620384261012077,-0.04721060022711754,-0.9911215305328369,0.11890571564435959,-0.03829071670770645,-0.04801439493894577,-0.9908901453018188,0.11985492706298828,-0.04156868904829025,-0.04836640506982803,-0.9905913472175598,0.12108466029167175,-0.04607286676764488,-0.048292018473148346,-0.9901638031005859,0.12296658009290695,-0.05194515734910965,-0.04912837967276573,-0.9894476532936096,0.1260215938091278,-0.05889274924993515,-0.051611725240945816,-0.9883895516395569,0.13020747900009155,-0.0656934306025505,-0.054013755172491074,-0.9872828125953674,0.13431145250797272,-0.07055038213729858,-0.05373154208064079,-0.9866291880607605,0.13674208521842957,-0.07257717847824097,-0.051171667873859406,-0.986507773399353,0.13753680884838104,-0.07330639660358429,-0.04991903156042099,-0.9863622188568115,0.13864995539188385,-0.07502669095993042,-0.05181630328297615,-0.9856955409049988,0.14174044132232666,-0.07752187550067902,-0.055899616330862045,-0.9846595525741577,0.14598359167575836,-0.0790911391377449,-0.059957996010780334,-0.9837722182273865,0.14947189390659332,-0.07965711504220963,-0.062478676438331604,-0.9833099842071533,0.1511709839105606,-0.07987432181835175,-0.0637458935379982,-0.9832266569137573,0.1510692983865738,-0.07880418747663498,-0.06482979655265808,-0.9834493398666382,0.14971420168876648,-0.07608871906995773,-0.06448633223772049,-0.9840957522392273,0.14699529111385345,-0.07446638494729996,-0.05911125987768173,-0.985260009765625,0.14220894873142242,-0.07642878592014313,-0.04812418669462204,-0.9866301417350769,0.13565990328788757,-0.07962589710950851,-0.03744519501924515,-0.9876706600189209,0.12947700917720795,-0.08067990094423294,-0.03237203508615494,-0.9883106350898743,0.12523925304412842,-0.08024682104587555,-0.03126201778650284,-0.9887292385101318,0.12246493995189667,-0.08002416789531708,-0.030149899423122406,-0.989094078540802,0.11991690844297409,-0.07924337685108185,-0.02862497605383396,-0.9894983768463135,0.1174481213092804,-0.07638376206159592,-0.028596799820661545,-0.9899090528488159,0.11587823182344437,-0.07140927016735077,-0.030122341588139534,-0.9903015494346619,0.11530918627977371,-0.06538374722003937,-0.03175704553723335,-0.9907076954841614,0.11495538800954819,-0.059767093509435654,-0.03308802843093872,-0.9910686016082764,0.11452557891607285,-0.05589073896408081,-0.03449103981256485,-0.991257905960083,0.11443053185939789,-0.05367682874202728,-0.03588324040174484,-0.9912932515144348,0.11475574970245361,-0.0515231117606163,-0.03723166510462761,-0.9913106560707092,0.11516234278678894,-0.04824531823396683,-0.038895100355148315,-0.9913793802261353,0.11544031649827957,-0.04432858154177666,-0.04076091945171356,-0.9914897084236145,0.11541955173015594,-0.04085462540388107,-0.042193733155727386,-0.9916411638259888,0.11488410830497742,-0.038230009377002716,-0.043129172176122665,-0.9918076395988464,0.11399982869625092,-0.03672681376338005,-0.04407385364174843,-0.991910457611084,0.11323538422584534,-0.03696132451295853,-0.04514563828706741,-0.991908073425293,0.11275669187307358,-0.038595136255025864,-0.04611583054065704,-0.9918327331542969,0.1124793216586113,-0.03985128179192543,-0.04696452617645264,-0.9917481541633606,0.11243569850921631,-0.03950905799865723,-0.04777718335390091,-0.9916949272155762,0.11268361657857895,-0.03794792667031288,-0.04842745140194893,-0.9916754364967346,0.11311312764883041,-0.035927608609199524,-0.04866904392838478,-0.9916682243347168,0.11373055726289749,-0.03405650332570076,-0.048257309943437576,-0.9916874170303345,0.11431276798248291,-0.032840829342603683,-0.04755036532878876,-0.9916831851005554,0.1149996891617775,-0.032555218786001205,-0.04699111729860306,-0.9916207790374756,0.1158454641699791,-0.032967109233140945,-0.047074951231479645,-0.9915061593055725,0.11667316406965256,-0.033457085490226746,-0.047077711671590805,-0.9913966059684753,0.11746080964803696,-0.03358611464500427,-0.04685642570257187,-0.9913351535797119,0.11802969127893448,-0.033503253012895584,-0.04699786752462387,-0.9913151860237122,0.11816491186618805,-0.03343347832560539,-0.04719686135649681,-0.9912957549095154,0.11826849728822708,-0.03337892144918442,-0.04742615669965744,-0.9912780523300171,0.11834042519330978,-0.03334321454167366,-0.04761514067649841,-0.9912651181221008,0.11838265508413315,-0.033330414444208145,-0.04769468307495117,-0.9912600517272949,0.11839660257101059]},{"name":"Bip001 L Calf.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.2969572842121124,0.008482183329761028,-0.030670033767819405,0.9543604254722595,-0.29799747467041016,0.008636555634438992,-0.030771121382713318,0.9540314674377441,-0.2986457943916321,0.008866813965141773,-0.030883627012372017,0.9538229703903198,-0.2986598610877991,0.009043867699801922,-0.03095516748726368,0.9538145661354065,-0.29824382066726685,0.009091638028621674,-0.030972076579928398,0.9539437890052795,-0.2978689670562744,0.009030110202729702,-0.030955100432038307,0.9540619850158691,-0.2980831563472748,0.008884625509381294,-0.030923593789339066,0.9539974927902222,-0.29955151677131653,0.008665117435157299,-0.030902540311217308,0.9535401463508606,-0.3025573492050171,0.008389877155423164,-0.03091738559305668,0.952592670917511,-0.30652692914009094,0.008091305382549763,-0.03096386417746544,0.9513238072395325,-0.31036439538002014,0.007805661763995886,-0.03100389800965786,0.9500798583030701,-0.3137255609035492,0.007529822178184986,-0.031017035245895386,0.9489771127700806,-0.31727591156959534,0.007262414786964655,-0.031043291091918945,0.9477972388267517,-0.32148274779319763,0.007052029948681593,-0.031131764873862267,0.9463772773742676,-0.3257790505886078,0.006897368002682924,-0.03124961629509926,0.9449041485786438,-0.3288736939430237,0.006728867068886757,-0.0313020795583725,0.9438310265541077,-0.3310762345790863,0.006518768146634102,-0.03128977119922638,0.9430625438690186,-0.33450081944465637,0.006014760583639145,-0.031241828575730324,0.9418582320213318,-0.33843794465065,0.005445900838822126,-0.031204067170619965,0.940455436706543,-0.34068921208381653,0.00479535199701786,-0.03107103519141674,0.9396501779556274,-0.33962029218673706,0.004460850264877081,-0.030920162796974182,0.9400436878204346,-0.3352237641811371,0.004677961114794016,-0.030835077166557312,0.9416221976280212,-0.3284236192703247,0.004925791639834642,-0.030662428587675095,0.9440198540687561,-0.31996527314186096,0.005184580106288195,-0.030431203544139862,0.9469262361526489,-0.3102598190307617,0.005369536112993956,-0.03013266995549202,0.950158953666687,-0.2997075021266937,0.005459060426801443,-0.029757697135210037,0.9535512924194336,-0.2894887924194336,0.005407773423939943,-0.029332764446735382,0.9567165970802307,-0.2809452414512634,0.005351379979401827,-0.02895381860435009,0.959272027015686,-0.27451083064079285,0.005431854631751776,-0.02866162732243538,0.9611414074897766,-0.26976779103279114,0.0055905906483531,-0.028453953564167023,0.9624887108802795,-0.2660735547542572,0.005686286836862564,-0.028264963999390602,0.9635214805603027,-0.26284506916999817,0.005680135916918516,-0.02805740386247635,0.9644132852554321,-0.2602732181549072,0.005614050664007664,-0.027867034077644348,0.9651164412498474,-0.25891533493995667,0.005505976267158985,-0.027742255479097366,0.9654858112335205,-0.25900983810424805,0.005353021435439587,-0.02769993245601654,0.9654625654220581,-0.2604273855686188,0.005191305186599493,-0.027740472927689552,0.9650808572769165,-0.2627139389514923,0.005075640976428986,-0.02784733660519123,0.9644584655761719,-0.2651510238647461,0.005001373123377562,-0.02797095477581024,0.9637880921363831,-0.2673788070678711,0.00491537619382143,-0.028072457760572433,0.9631699323654175,-0.2696421146392822,0.004806410521268845,-0.028164424002170563,0.962536633014679,-0.2721301019191742,0.004708531312644482,-0.02826690673828125,0.961833655834198,-0.2746306359767914,0.004627656657248735,-0.02836802788078785,0.9611201286315918,-0.2772212028503418,0.004539879038929939,-0.028462868183851242,0.9603736996650696,-0.2803446352481842,0.004400746896862984,-0.028563007712364197,0.9594642519950867,-0.28360265493392944,0.004292761441320181,-0.028664346784353256,0.958503782749176,-0.286243736743927,0.0042325593531131744,-0.028738290071487427,0.9577164053916931,-0.28802797198295593,0.0041753919795155525,-0.02877935953438282,0.9571803212165833,-0.28935760259628296,0.004095546901226044,-0.02880273014307022,0.956778883934021,-0.2907637059688568,0.003971675410866737,-0.028820838779211044,0.9563524723052979,-0.29216936230659485,0.004447849467396736,-0.029025046154856682,0.9559156894683838,-0.2931478023529053,0.005134354345500469,-0.02927890606224537,0.9556049108505249,-0.29382243752479553,0.005905228201299906,-0.029547806829214096,0.9553849697113037,-0.29471421241760254,0.006550529971718788,-0.029786281287670135,0.9550986289978027,-0.29608863592147827,0.006939355283975601,-0.02996343933045864,0.9546651244163513,-0.29722270369529724,0.007161927875131369,-0.030077876523137093,0.9543074369430542,-0.297830730676651,0.007391022052615881,-0.03017299808561802,0.9541131258010864,-0.2987675666809082,0.007388797588646412,-0.030209556221961975,0.9538190364837646,-0.2996290922164917,0.00738576939329505,-0.0302430372685194,0.9535477161407471,-0.3003403842449188,0.0073826927691698074,-0.030270839110016823,0.9533230662345886,-0.3008248805999756,0.007381371222436428,-0.030290009453892708,0.9531696438789368,-0.3010055422782898,0.007381464820355177,-0.030297355726361275,0.9531124234199524]},{"name":"Bip001 L Foot.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.5223862528800964,-0.10252691805362701,-0.06608512252569199,0.8439393639564514,0.5231484174728394,-0.103171207010746,-0.06590668112039566,0.8434025049209595,0.5237952470779419,-0.10418876260519028,-0.06553829461336136,0.8429045081138611,0.5241017937660217,-0.1044815257191658,-0.06525742262601852,0.8426994681358337,0.524127185344696,-0.10350220650434494,-0.06527625024318695,0.8428030610084534,0.5241637825965881,-0.10200513154268265,-0.06550061702728271,0.842945396900177,0.524589478969574,-0.10106314718723297,-0.0656844824552536,0.8427797555923462,0.5257939100265503,-0.10075879842042923,-0.06575002521276474,0.8420601487159729,0.5278856158256531,-0.10041312128305435,-0.06579932570457458,0.8407880067825317,0.5304986834526062,-0.09975174069404602,-0.06589674204587936,0.839212954044342,0.5330644249916077,-0.09910640865564346,-0.06603758782148361,0.8376510739326477,0.5354809761047363,-0.09872610121965408,-0.06622517853975296,0.8361384868621826,0.5381509065628052,-0.09873713552951813,-0.06641184538602829,0.8344064354896545,0.5412775874137878,-0.09941449761390686,-0.06648250669240952,0.8322952389717102,0.5444347262382507,-0.10066673159599304,-0.066373810172081,0.8300912976264954,0.5468432307243347,-0.10201328247785568,-0.0660739466547966,0.8283658623695374,0.5486915111541748,-0.10347223281860352,-0.06555166095495224,0.8270031213760376,0.5511609315872192,-0.10482264310121536,-0.06338295340538025,0.8253583908081055,0.5540484189987183,-0.10608088970184326,-0.06107740104198456,0.82343590259552,0.5561738610267639,-0.1064877137541771,-0.058852434158325195,0.8221115469932556,0.5554264187812805,-0.10644295066595078,-0.057480257004499435,0.8227195143699646,0.5514355897903442,-0.107065349817276,-0.05713525414466858,0.8253431916236877,0.5462669730186462,-0.10874979943037033,-0.05614853650331497,0.8286212682723999,0.5412111282348633,-0.112095408141613,-0.055371079593896866,0.8315402269363403,0.5369912385940552,-0.11639375239610672,-0.0551627017557621,0.8336966037750244,0.5325296521186829,-0.11902514100074768,-0.05501829460263252,0.8361926674842834,0.52801513671875,-0.11745888739824295,-0.05475909262895584,0.8392882943153381,0.5240797400474548,-0.11312197148799896,-0.05522467941045761,0.8423147201538086,0.5210803151130676,-0.10989075899124146,-0.056771356612443924,0.8444976806640625,0.5187898278236389,-0.10898858308792114,-0.058410998433828354,0.8459118008613586,0.5168620944023132,-0.10904882848262787,-0.05915290117263794,0.8470317721366882,0.5153355002403259,-0.10903824865818024,-0.05886317044496536,0.8479829430580139,0.5145424604415894,-0.10903245955705643,-0.05775212123990059,0.8485414981842041,0.514487087726593,-0.10939226299524307,-0.05591202154755592,0.8486520051956177,0.5147990584373474,-0.11005458235740662,-0.05354313179850578,0.8485299348831177,0.5150638222694397,-0.11020117253065109,-0.051476381719112396,0.8484781384468079,0.5150286555290222,-0.10883737355470657,-0.050718553364276886,0.8487211465835571,0.514829695224762,-0.10611245036125183,-0.05102873593568802,0.8491681814193726,0.5149139761924744,-0.10332528501749039,-0.05106770619750023,0.8494583964347839,0.515453040599823,-0.10129828006029129,-0.050387024879455566,0.8494162559509277,0.5162414908409119,-0.09967412799596786,-0.04986938089132309,0.8491600751876831,0.5173071622848511,-0.09800118207931519,-0.04999435320496559,0.8486987948417664,0.5187743306159973,-0.09641341865062714,-0.05027773976325989,0.8479680418968201,0.5207703113555908,-0.0947919636964798,-0.05050929635763168,0.8469129800796509,0.5218065977096558,-0.09266594797372818,-0.05092128738760948,0.8464856147766113,0.5217883586883545,-0.09032454341650009,-0.05115218088030815,0.8467360138893127,0.5219951868057251,-0.08887821435928345,-0.050754573196172714,0.8467854857444763,0.5230339765548706,-0.08867374807596207,-0.04992338642477989,0.8462151885032654,0.5246396064758301,-0.08906488120555878,-0.04878944903612137,0.8452457189559937,0.5249885320663452,-0.09058845043182373,-0.05001543462276459,0.8447953462600708,0.5250422358512878,-0.09268461912870407,-0.05233702436089516,0.8443939089775085,0.5251083970069885,-0.09492592513561249,-0.05539202690124512,0.8439087271690369,0.5253172516822815,-0.09655575454235077,-0.058198682963848114,0.8434048295021057,0.5256883502006531,-0.09677589684724808,-0.06009267643094063,0.8430154323577881,0.525910496711731,-0.09663794934749603,-0.06141515076160431,0.8427973985671997,0.5259768962860107,-0.09694324433803558,-0.0627601370215416,0.8426218032836914,0.5262731313705444,-0.09694686532020569,-0.062737837433815,0.8424380421638489,0.5265573263168335,-0.09692418575286865,-0.062708280980587,0.842265248298645,0.5267966985702515,-0.09689370542764664,-0.06267993897199631,0.8421211838722229,0.5269627571105957,-0.09686850011348724,-0.06266332417726517,0.8420214056968689,0.527025580406189,-0.09685872495174408,-0.06265988945960999,0.841983437538147]},{"name":"Bip001 L Toe0.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.4060518741607666,0.023528864607214928,-0.015740158036351204,0.9134114980697632,0.4060141146183014,0.02362322248518467,-0.01583000086247921,0.9134242534637451,0.4059015214443207,0.023725751787424088,-0.01592152938246727,0.9134700298309326,0.4057634174823761,0.023750167340040207,-0.015934014692902565,0.9135305881500244,0.40573039650917053,0.023576337844133377,-0.015759890899062157,0.9135527610778809,0.40580660104751587,0.02340869978070259,-0.015601079910993576,0.9135259389877319,0.4058033227920532,0.023708991706371307,-0.015896786004304886,0.9135145545005798,0.4055982232093811,0.024413226172327995,-0.016573678702116013,0.9135750532150269,0.4053882360458374,0.024776224046945572,-0.016913827508687973,0.913652241230011,0.4054134786128998,0.024510709568858147,-0.01665426790714264,0.9136530160903931,0.40556004643440247,0.024212894961237907,-0.01637302152812481,0.9136009812355042,0.4055982530117035,0.024278247728943825,-0.01644064113497734,0.9135810732841492,0.4055423140525818,0.024434493854641914,-0.01659003645181656,0.9135990142822266,0.4054887592792511,0.024453001096844673,-0.016603758558630943,0.9136220216751099,0.40543892979621887,0.024431001394987106,-0.016577860340476036,0.9136452078819275,0.40540334582328796,0.024460893124341965,-0.016604341566562653,0.9136596918106079,0.40550169348716736,0.024420028552412987,-0.01657228171825409,0.913617730140686,0.4057445824146271,0.024200323969125748,-0.01637626811861992,0.9135193228721619,0.4059818387031555,0.023937147110700607,-0.01613682322204113,0.913425087928772,0.4062489867210388,0.023859456181526184,-0.016082806512713432,0.9133092761039734,0.4067516624927521,0.02404974214732647,-0.016312621533870697,0.9130764603614807,0.40747395157814026,0.02434723824262619,-0.016884658485651016,0.9127360582351685,0.40901464223861694,0.023465396836400032,-0.018301010131835938,0.912042498588562,0.40923643112182617,0.02339117042720318,-0.019375372678041458,0.9119226932525635,0.40767353773117065,0.024822909384965897,-0.01942991465330124,0.9125834703445435,0.4066770076751709,0.025521593168377876,-0.019283821806311607,0.9130118489265442,0.40639346837997437,0.025501815602183342,-0.018974870443344116,0.9131450653076172,0.40599173307418823,0.02562943659722805,-0.018485568463802338,0.9133302569389343,0.4052773714065552,0.026107557117938995,-0.018216729164123535,0.9136393070220947,0.40489035844802856,0.026341218501329422,-0.018414705991744995,0.9138002395629883,0.40469926595687866,0.026566777378320694,-0.018621034920215607,0.9138741493225098,0.40465715527534485,0.026695767417550087,-0.018744604662060738,0.9138864874839783,0.40452516078948975,0.02675735205411911,-0.018794259056448936,0.9139421582221985,0.40420448780059814,0.0266838688403368,-0.018694840371608734,0.9140881896018982,0.403798371553421,0.026415614411234856,-0.01839623972773552,0.9142814874649048,0.4035700559616089,0.02608572505414486,-0.018051790073513985,0.9143986701965332,0.40370652079582214,0.025810133665800095,-0.017791621387004852,0.9143513441085815,0.40402835607528687,0.02541867457330227,-0.01743277907371521,0.9142270684242249,0.40458178520202637,0.024310121312737465,-0.016920560970902443,0.9140220284461975,0.405305415391922,0.022555582225322723,-0.016296615824103355,0.9137577414512634,0.4056682884693146,0.02110605500638485,-0.015546339564025402,0.9136443734169006,0.40550118684768677,0.020310737192630768,-0.014851094223558903,0.9137481451034546,0.40536701679229736,0.019605139270424843,-0.01446092501282692,0.9138293862342834,0.40465909242630005,0.019832054153084755,-0.013881584629416466,0.914147138595581,0.40434199571609497,0.020177623257040977,-0.013750072568655014,0.9142818450927734,0.40444663166999817,0.02038574032485485,-0.014500522054731846,0.9142193794250488,0.4041275978088379,0.020824622362852097,-0.015564115718007088,0.9143331050872803,0.4033201336860657,0.021515604108572006,-0.016013093292713165,0.9146658182144165,0.40255096554756165,0.02239718660712242,-0.01565438322722912,0.9149896502494812,0.4026505649089813,0.02271064929664135,-0.015329406596720219,0.9149435758590698,0.40281039476394653,0.02286756969988346,-0.015142003074288368,0.9148724675178528,0.4027928411960602,0.02288958989083767,-0.015055891126394272,0.9148810505867004,0.4027382433414459,0.022877562791109085,-0.015007274225354195,0.9149062037467957,0.4027460217475891,0.022920580580830574,-0.014984984882175922,0.9149020314216614,0.4027723968029022,0.02299346588551998,-0.015022078529000282,0.9148879647254944,0.4027767479419708,0.0231038648635149,-0.015186618082225323,0.9148805737495422,0.4027769863605499,0.02310345508158207,-0.015186754986643791,0.9148804545402527,0.4027771055698395,0.023103274405002594,-0.015186821110546589,0.9148804545402527,0.40277743339538574,0.023102793842554092,-0.015187022276222706,0.9148802757263184,0.402777761220932,0.023102277889847755,-0.015187223441898823,0.9148801565170288,0.4027775526046753,0.02310255356132984,-0.01518712006509304,0.9148802161216736]},{"name":"Bip001 R Thigh.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.00027724687242880464,0.09583603590726852,0.9890747666358948,0.11201084405183792,0.0030612191185355186,0.09623055905103683,0.9891318082809448,0.11112416535615921,0.005714045837521553,0.09690672159194946,0.9891372919082642,0.1103806272149086,0.007431161589920521,0.09727520495653152,0.9891566038131714,0.10977919399738312,0.008138473145663738,0.09711510688066483,0.9892128109931946,0.10936370491981506,0.008427317254245281,0.09642087668180466,0.9893076419830322,0.10909787565469742,0.00924585945904255,0.09536199271678925,0.9894422888755798,0.10874057561159134,0.010907105170190334,0.09462302178144455,0.9895591139793396,0.10816790163516998,0.012750565074384212,0.09493537247180939,0.9895739555358887,0.10755515098571777,0.01478500198572874,0.09619101136922836,0.9894874095916748,0.1069735512137413,0.018354028463363647,0.09753081202507019,0.989397406578064,0.10603611171245575,0.0243130624294281,0.09876995533704758,0.9893521666526794,0.10409464687108994,0.03234872967004776,0.10105107724666595,0.9892529249191284,0.10060236603021622,0.0415334478020668,0.10510051250457764,0.9889701008796692,0.0957445427775383,0.04990159720182419,0.10937690734863281,0.9885566830635071,0.09111613035202026,0.05477817356586456,0.11124280095100403,0.9883125424385071,0.08867214620113373,0.05577588081359863,0.11049286276102066,0.9883760213851929,0.08827904611825943,0.056256189942359924,0.10973276197910309,0.9885171055793762,0.08733753859996796,0.059368718415498734,0.1105886697769165,0.9885475039482117,0.08378152549266815,0.0640348419547081,0.1118050366640091,0.9885480403900146,0.0785616859793663,0.06736109405755997,0.11129031330347061,0.9887252449989319,0.07415778189897537,0.06894729286432266,0.10865400731563568,0.9890960454940796,0.0716211199760437,0.07055822759866714,0.10548802465200424,0.9894130229949951,0.07039657235145569,0.07305921614170074,0.1022941917181015,0.9896144866943359,0.06972389668226242,0.07602590322494507,0.09823962301015854,0.9897851347923279,0.06996046751737595,0.07860090583562851,0.09211993217468262,0.9899685382843018,0.07278847694396973,0.0794357880949974,0.08430328220129013,0.9901416301727295,0.07875601202249527,0.07795035094022751,0.07725343853235245,0.9902839064598083,0.08540147542953491,0.07623188197612762,0.07300674170255661,0.990347146987915,0.08984041959047318,0.07682429254055023,0.07102614641189575,0.9902415871620178,0.09205920994281769,0.07923296838998795,0.06965257227420807,0.9899858236312866,0.09380176663398743,0.08070125430822372,0.06830205768346786,0.9897909760475159,0.09558235853910446,0.07936684042215347,0.0674503892660141,0.9898571968078613,0.09661276638507843,0.07523542642593384,0.0673561692237854,0.9901676774024963,0.09680282324552536,0.06922270357608795,0.0678359866142273,0.9905533194541931,0.0970083475112915,0.06213036924600601,0.0687251016497612,0.9908923506736755,0.09771928936243057,0.05411333590745926,0.06991954892873764,0.9911693930625916,0.09882408380508423,0.04525288566946983,0.07143791019916534,0.9913689494132996,0.10018210858106613,0.03634769096970558,0.07328534871339798,0.991462767124176,0.1015363410115242,0.028557009994983673,0.07510079443454742,0.9914774298667908,0.10255187004804611,0.022063810378313065,0.07647938281297684,0.9914385080337524,0.10350723564624786,0.015898944810032845,0.07765766978263855,0.9912992119789124,0.10508269816637039,0.009735483676195145,0.07921108603477478,0.9910241961479187,0.10724692791700363,0.004522532690316439,0.08124063163995743,0.9906597137451172,0.10942064970731735,0.0010288039920851588,0.08346383273601532,0.9902638792991638,0.11140099912881851,-0.0013601119862869382,0.08570546656847,0.9898689985275269,0.11319070309400558,-0.0035299500450491905,0.08767145872116089,0.9895528554916382,0.11439584195613861,-0.0054728989489376545,0.08899375051259995,0.9893916249275208,0.11469244211912155,-0.006988673470914364,0.08970250934362411,0.9893519878387451,0.11439963430166245,-0.008354702964425087,0.09010511636734009,0.989338219165802,0.11411002278327942,-0.009656754322350025,0.09040514379739761,0.9893129467964172,0.11398936808109283,-0.010417959652841091,0.09072347730398178,0.9893001317977905,0.11378014087677002,-0.010348792187869549,0.09101059287786484,0.9893248081207275,0.1133420318365097,-0.009874904528260231,0.0911482572555542,0.9893707036972046,0.11287189275026321,-0.009892557747662067,0.09115664660930634,0.9894146919250488,0.11247724294662476,-0.010029582306742668,0.09101847559213638,0.9894556999206543,0.11221589148044586,-0.01010528951883316,0.09111776202917099,0.9894458055496216,0.11221598088741302,-0.010208871215581894,0.09125328809022903,0.9894295930862427,0.1122392937541008,-0.01031363382935524,0.09141553938388824,0.989409863948822,0.1122715175151825,-0.010394359938800335,0.09155131876468658,0.9893931746482849,0.11230055242776871,-0.010426837019622326,0.09160904586315155,0.9893860816955566,0.1123129278421402]},{"name":"Bip001 R Calf.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[-0.33135607838630676,-0.0035666953772306442,0.029010260477662086,0.9430529475212097,-0.33156082034111023,-0.003397491993382573,0.028969546779990196,0.9429828524589539,-0.33109787106513977,-0.003502641338855028,0.02899886481463909,0.9431442022323608,-0.32991039752960205,-0.003567150328308344,0.028975849971175194,0.9435607194900513,-0.3285805284976959,-0.003579004667699337,0.02892318367958069,0.9440262317657471,-0.3275998532772064,-0.003661994356662035,0.02890698052942753,0.9443671703338623,-0.32705527544021606,-0.0038028545677661896,0.028920872136950493,0.9445549249649048,-0.3271523714065552,-0.003923213575035334,0.028943117707967758,0.944520115852356,-0.32812508940696716,-0.004031066317111254,0.02899361401796341,0.9441806077957153,-0.3297877013683319,-0.004163229838013649,0.029084965586662292,0.9435977935791016,-0.33142605423927307,-0.0042976997792720795,0.029179539531469345,0.9430201053619385,-0.33228275179862976,-0.004376578610390425,0.029220212250947952,0.9427169561386108,-0.33190470933914185,-0.004359014332294464,0.02917087823152542,0.942851722240448,-0.3303990364074707,-0.004250457976013422,0.029042238369584084,0.9433848261833191,-0.32882261276245117,-0.0041072554886341095,0.02890722081065178,0.9439402222633362,-0.3281775116920471,-0.00399059010669589,0.02883237786591053,0.9441674947738647,-0.32840198278427124,-0.0039061747957021,0.028809333220124245,0.9440904855728149,-0.32790324091911316,-0.003836043644696474,0.028757184743881226,0.9442657232284546,-0.32427626848220825,-0.0037690619938075542,0.028574690222740173,0.9455232620239258,-0.316410094499588,-0.0037113926373422146,0.02822495810687542,0.948195219039917,-0.3046361804008484,-0.003905604826286435,0.02780388481914997,0.9520548582077026,-0.2901516258716583,-0.0040958113968372345,0.02727244608104229,0.9565832018852234,-0.27455705404281616,-0.004082302562892437,0.02665378339588642,0.9611926674842834,-0.2578548491001129,-0.0040822699666023254,0.025825461372733116,0.9658298492431641,-0.2420303374528885,-0.004106658510863781,0.02490316331386566,0.9699403643608093,-0.2317916303873062,-0.004166115075349808,0.02428220957517624,0.9724534153938293,-0.22982828319072723,-0.004253120627254248,0.0241813063621521,0.9729214310646057,-0.2315666824579239,-0.004320900421589613,0.024311870336532593,0.9725055694580078,-0.2309812754392624,-0.004333871882408857,0.02428225427865982,0.9726454615592957,-0.22802452743053436,-0.004324997775256634,0.024096712470054626,0.973347544670105,-0.22592127323150635,-0.004356938414275646,0.02397901378571987,0.9738406538963318,-0.225486621260643,-0.004456516820937395,0.02398815006017685,0.9739407300949097,-0.22547923028469086,-0.004614031873643398,0.02404090017080307,0.973940372467041,-0.22616873681545258,-0.00481232488527894,0.024147486314177513,0.9737768769264221,-0.22908255457878113,-0.005013252608478069,0.024399489164352417,0.973088264465332,-0.23439247906208038,-0.005166724324226379,0.02479989267885685,0.9718119502067566,-0.24096056818962097,-0.005265503190457821,0.025268804281949997,0.9701915979385376,-0.24812057614326477,-0.0053445156663656235,0.0257615577429533,0.9683718085289001,-0.25541314482688904,-0.0054243686608970165,0.026244057342410088,0.9664605259895325,-0.2620871961116791,-0.005493493285030127,0.026661956682801247,0.9646601676940918,-0.268215537071228,-0.005538504105061293,0.027014726772904396,0.9629641771316528,-0.27483654022216797,-0.005561250727623701,0.02735108509659767,0.9610857963562012,-0.282340407371521,-0.005569016095250845,0.02767094597220421,0.9588989615440369,-0.2899278998374939,-0.005564376711845398,0.02797279879450798,0.9566234350204468,-0.296773225069046,-0.005554665811359882,0.02824348583817482,0.9545140862464905,-0.30252134799957275,-0.005543157458305359,0.028470713645219803,0.9527011513710022,-0.30672308802604675,-0.005523053463548422,0.02863406576216221,0.9513519406318665,-0.30937400460243225,-0.005487672984600067,0.02872953936457634,0.9504905343055725,-0.3113422691822052,-0.005451982840895653,0.02879742719233036,0.9498457908630371,-0.313468337059021,-0.005436096340417862,0.028880294412374496,0.9491438269615173,-0.31576627492904663,-0.005426215007901192,0.028972752392292023,0.9483790397644043,-0.3177688717842102,-0.005404271651059389,0.029047908261418343,0.9477077126502991,-0.3191806375980377,-0.005372977815568447,0.029094064608216286,0.9472319483757019,-0.32007071375846863,-0.005346355959773064,0.029119987040758133,0.9469309449195862,-0.32081156969070435,-0.005324683152139187,0.029141869395971298,0.9466796517372131,-0.321380615234375,-0.005302399396896362,0.029159409925341606,0.9464861750602722,-0.32206010818481445,-0.005299496930092573,0.029185809195041656,0.9462544322013855,-0.3226650059223175,-0.0052971020340919495,0.02920941449701786,0.9460476040840149,-0.32315394282341003,-0.005295879207551479,0.029228609055280685,0.945880115032196,-0.3234803378582001,-0.005295412614941597,0.02924155630171299,0.9457681179046631,-0.3236004412174225,-0.005295185837894678,0.029246265068650246,0.9457268714904785]},{"name":"Bip001 R Foot.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.494439035654068,0.0654541477560997,0.06977703422307968,0.8639311194419861,0.4947528541088104,0.06657446175813675,0.06822386384010315,0.8637898564338684,0.4939432144165039,0.06800437718629837,0.06800452619791031,0.8641590476036072,0.49332115054130554,0.06907633692026138,0.06806793808937073,0.8644243478775024,0.4932069480419159,0.07009211927652359,0.06807593256235123,0.8644071221351624,0.49322402477264404,0.07144202291965485,0.06795801222324371,0.8642961382865906,0.4934050440788269,0.07303842157125473,0.06786597520112991,0.8640666007995605,0.49396979808807373,0.07418770343065262,0.0680321529507637,0.8636327981948853,0.4947156608104706,0.07410288602113724,0.06856362521648407,0.8631710410118103,0.4953469932079315,0.07320725917816162,0.06930095702409744,0.862826406955719,0.49578577280044556,0.07310440391302109,0.06989835202693939,0.8625348806381226,0.495951384305954,0.0744955986738205,0.07019863277673721,0.8622962236404419,0.4955037832260132,0.07659849524497986,0.07031845301389694,0.8623595237731934,0.4941626787185669,0.07832609117031097,0.07046020776033401,0.8629621267318726,0.4924108684062958,0.07873603701591492,0.07088123261928558,0.8638911843299866,0.4911004304885864,0.0770992860198021,0.07177618145942688,0.8647105097770691,0.4904908537864685,0.07390110194683075,0.07307693362236023,0.8652266263961792,0.48965150117874146,0.07124055176973343,0.07438970357179642,0.865813136100769,0.4873185157775879,0.07126803696155548,0.07520744204521179,0.8670555949211121,0.4833102822303772,0.07434362173080444,0.07548782974481583,0.8690142631530762,0.47719696164131165,0.07936616986989975,0.07677990198135376,0.8718307614326477,0.46992936730384827,0.08421187102794647,0.07796090841293335,0.8752124309539795,0.46192917227745056,0.0882355347275734,0.07713759690523148,0.8791391849517822,0.4534183442592621,0.09333740174770355,0.07599430531263351,0.8831335306167603,0.445773720741272,0.09959432482719421,0.07488328963518143,0.8864306211471558,0.44112691283226013,0.1043403223156929,0.07458294928073883,0.8882328271865845,0.440413236618042,0.10519472509622574,0.07533615082502365,0.8884226083755493,0.44141265749931335,0.10402480512857437,0.07596874237060547,0.8880103826522827,0.44125425815582275,0.10477514564990997,0.07531493902206421,0.8880565762519836,0.43986764550209045,0.10817676782608032,0.07375698536634445,0.8884673118591309,0.43907392024993896,0.11209791898727417,0.072355717420578,0.8884890675544739,0.4396977126598358,0.11530306935310364,0.07159356027841568,0.8878319263458252,0.44098424911499023,0.11787120997905731,0.0716024786233902,0.8868553042411804,0.4422804117202759,0.11928123980760574,0.07258027791976929,0.8859413862228394,0.4439893364906311,0.11869415640830994,0.07416441291570663,0.8850337862968445,0.4467574954032898,0.11618608981370926,0.0754401907324791,0.8838649988174438,0.4502996504306793,0.11216188222169876,0.07624861598014832,0.8825169205665588,0.4537779986858368,0.1067650318145752,0.07724018394947052,0.8813176155090332,0.45684656500816345,0.1008242815732956,0.07864373177289963,0.8803072571754456,0.45983439683914185,0.09613402187824249,0.07994719594717026,0.8791581392288208,0.4632250964641571,0.09335073083639145,0.08077603578567505,0.877600908279419,0.4670492708683014,0.09094581753015518,0.08138342946767807,0.8757685422897339,0.4707608222961426,0.08763673901557922,0.08204180002212524,0.8740556240081787,0.47374677658081055,0.08387762308120728,0.08266563713550568,0.8727513551712036,0.4759666621685028,0.08053426444530487,0.08309532701969147,0.8718171119689941,0.4777953624725342,0.07761679589748383,0.08329924941062927,0.8710616827011108,0.47941023111343384,0.07507540285587311,0.08322523534297943,0.8704040050506592,0.4807852804660797,0.07327760756015778,0.08282983303070068,0.8698362708091736,0.4820393919944763,0.07221021503210068,0.08250471949577332,0.869262158870697,0.48326367139816284,0.07119730859994888,0.08262582868337631,0.868654191493988,0.4843275845050812,0.06974520534276962,0.08283599466085434,0.868159294128418,0.48525744676589966,0.06820042431354523,0.08274008333683014,0.8677718639373779,0.4861787259578705,0.0671439915895462,0.08241891860961914,0.8673690557479858,0.4870673716068268,0.0671134814620018,0.08213751763105392,0.8668994307518005,0.4876827597618103,0.06681831926107407,0.08193226903676987,0.8665956258773804,0.4880507290363312,0.06644625216722488,0.08167213946580887,0.8664416074752808,0.4881688058376312,0.06616701930761337,0.08174805343151093,0.866389274597168,0.48828426003456116,0.06591250002384186,0.08182884752750397,0.8663359880447388,0.4883824586868286,0.0657077357172966,0.08190234005451202,0.8662892580032349,0.48845037817955017,0.06557223200798035,0.08195842057466507,0.8662559390068054,0.4884762167930603,0.06552224606275558,0.08197811245918274,0.866243302822113]},{"name":"Bip001 R Toe0.quaternion","type":"quaternion","times":[0,0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2],"values":[0.4453660249710083,-0.013012758456170559,0.007651119027286768,0.8952213525772095,0.44549620151519775,-0.012791810557246208,0.007466444745659828,0.8951613306999207,0.4458116590976715,-0.012390161864459515,0.0071297516115009785,0.895012617111206,0.4461829364299774,-0.011846012435853481,0.006674293894320726,0.8948385119438171,0.44611236453056335,-0.011600211262702942,0.0064719910733401775,0.8948783874511719,0.44564661383628845,-0.01173123624175787,0.0065870583057403564,0.8951078653335571,0.4454021453857422,-0.011687392368912697,0.006554191000759602,0.8952303528785706,0.4456371068954468,-0.011219332925975323,0.006163613870739937,0.8951221704483032,0.44594675302505493,-0.010727804154157639,0.005752583499997854,0.8949767351150513,0.44600653648376465,-0.010392808355391026,0.00547453761100769,0.8949525952339172,0.44592490792274475,-0.009998335503041744,0.00514924107119441,0.8949996829032898,0.44599300622940063,-0.009473562240600586,0.004714175593107939,0.894973874092102,0.4464394748210907,-0.008890981785953045,0.004225832875818014,0.8947596549987793,0.4472140669822693,-0.008249394595623016,0.0036839747335761786,0.8943813443183899,0.4479027986526489,-0.007688141893595457,0.003209745977073908,0.8940434455871582,0.44812190532684326,-0.007491787895560265,0.003044240875169635,0.8939358592033386,0.44792455434799194,-0.007723772432655096,0.003239011624827981,0.8940321207046509,0.4476096034049988,-0.008123097941279411,0.0035739124286919832,0.8941850662231445,0.447375625371933,-0.008450884371995926,0.0038484446704387665,0.8942979574203491,0.4471670687198639,-0.008772118017077446,0.004117351491004229,0.8943979740142822,0.4468730092048645,-0.009235475212335587,0.004505203105509281,0.894538402557373,0.4465387463569641,-0.009768741205334663,0.004951675422489643,0.8946972489356995,0.4462631344795227,-0.01016344502568245,0.0052806115709245205,0.8948284983634949,0.4460327923297882,-0.010355714708566666,0.005443006753921509,0.8949401378631592,0.4457061290740967,-0.01042646262794733,0.005506263580173254,0.8951016664505005,0.4453391432762146,-0.010427736677229404,0.005512389354407787,0.8952842950820923,0.4451562464237213,-0.010335585102438927,0.005438933148980141,0.8953767418861389,0.4451214671134949,-0.010167213156819344,0.005299881566315889,0.8953967690467834,0.4448273181915283,-0.010060795582830906,0.005216146819293499,0.8955446481704712,0.44407084584236145,-0.010103707201778889,0.005262344144284725,0.8959192633628845,0.44328805804252625,-0.010162414982914925,0.005322278011590242,0.8963057994842529,0.4429149627685547,-0.010157379321753979,0.005323321558535099,0.8964902758598328,0.44291213154792786,-0.010276040062308311,0.0054214708507061005,0.8964897394180298,0.4431527554988861,-0.010600464418530464,0.005686463788151741,0.8963654041290283,0.4434710443019867,-0.010940260253846645,0.00596305588260293,0.8962020874023438,0.44355279207229614,-0.01117515005171299,0.006156242918223143,0.896157443523407,0.44340136647224426,-0.011304033920168877,0.0062650772742927074,0.8962299823760986,0.44337549805641174,-0.011144746094942093,0.00613372353836894,0.8962457180023193,0.44364556670188904,-0.01055221538990736,0.005639572162181139,0.8961224555969238,0.4440072476863861,-0.00989123061299324,0.005087520461529493,0.8959541320800781,0.44415295124053955,-0.009652094915509224,0.004887640941888094,0.8958856463432312,0.4440281093120575,-0.009812737815082073,0.005022394470870495,0.8959450721740723,0.4438573718070984,-0.009994152002036572,0.005174899473786354,0.8960267901420593,0.4438597559928894,-0.009978446178138256,0.005163646303117275,0.8960258364677429,0.44398412108421326,-0.009892330504953861,0.005090244580060244,0.895965576171875,0.44401270151138306,-0.009992544539272785,0.005172566510736942,0.8959498405456543,0.443885862827301,-0.010280181653797626,0.005412230268120766,0.8960080146789551,0.4437962770462036,-0.010521761141717434,0.00561318825930357,0.896048367023468,0.44377779960632324,-0.010671437717974186,0.005737236235290766,0.8960549831390381,0.44377657771110535,-0.01081771869212389,0.005858187098056078,0.8960530161857605,0.443867951631546,-0.010888033546507359,0.005915028043091297,0.8960065245628357,0.443928986787796,-0.010834688320755959,0.005870011635124683,0.895977258682251,0.44377681612968445,-0.010825859382748604,0.0058649335987865925,0.8960527777671814,0.4435424506664276,-0.011045355349779129,0.006049733143299818,0.8961648941040039,0.44339168071746826,-0.011405099183321,0.006349337752908468,0.8962329030036926,0.4433043301105499,-0.011690542101860046,0.0065867723897099495,0.896270751953125,0.44330427050590515,-0.011690547689795494,0.006586841307580471,0.896270751953125,0.44330424070358276,-0.01169058308005333,0.006586848292499781,0.896270751953125,0.4433041512966156,-0.011690507642924786,0.00658676540479064,0.8962708115577698,0.4433041214942932,-0.011690507642924786,0.00658677751198411,0.8962708115577698,0.44330406188964844,-0.011690493673086166,0.006586697418242693,0.8962708711624146]}],"userData":{"forgeRole":"reload","source":"drawing-gun","note":"Baked flintlock powder reload (drawing-gun tracks) until dedicated Mixamo reload FBX"}} \ No newline at end of file diff --git a/public/anims/baked/reactions/knocked-up.json b/public/anims/baked/reactions/knocked-up.json new file mode 100644 index 0000000..7414c50 --- /dev/null +++ b/public/anims/baked/reactions/knocked-up.json @@ -0,0 +1 @@ +{"name":"reactions/knocked-up","duration":3.0333333015441895,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[1.7336310148239136,67.55338287353516,-5.615248680114746,1.708412766456604,67.52295684814453,-5.554146766662598,1.6974940299987793,67.51538848876953,-5.483654022216797,1.6830198764801025,67.53266906738281,-5.42177677154541,1.6536214351654053,67.57474517822266,-5.350118637084961,1.6185414791107178,67.63401794433594,-5.274981498718262,1.5778913497924805,67.71056365966797,-5.1962690353393555,1.5346630811691284,67.7993392944336,-5.113256931304932,1.4923702478408813,67.8929672241211,-5.025248050689697,1.4591784477233887,67.99384307861328,-4.933277606964111,1.43019437789917,68.08379364013672,-4.842763423919678,1.3993152379989624,68.16610717773438,-4.7605671882629395,1.3840378522872925,68.24987030029297,-4.677301406860352,1.3830668926239014,68.32635498046875,-4.592912673950195,1.3866136074066162,68.39019775390625,-4.513092994689941,1.3964035511016846,68.44435119628906,-4.439029693603516,1.4117401838302612,68.48929595947266,-4.377600193023682,1.4323556423187256,68.52208709716797,-4.325728893280029,1.457527995109558,68.53701782226562,-4.278876781463623,1.484586238861084,68.5354232788086,-4.23831844329834,1.5117380619049072,68.52377319335938,-4.206855773925781,1.5371007919311523,68.50904083251953,-4.186371326446533,1.5601871013641357,68.50382232666016,-4.1825127601623535,1.5822136402130127,68.49303436279297,-4.193780422210693,1.599869728088379,68.49530029296875,-4.228412628173828,1.6105644702911377,68.49240112304688,-4.2822699546813965,1.615122675895691,68.48725891113281,-4.348412990570068,1.6122961044311523,68.47936248779297,-4.430131435394287,1.609007477760315,68.46720886230469,-4.47310209274292,1.6061762571334839,68.44635772705078,-4.512808322906494,1.5940546989440918,68.41071319580078,-4.554113388061523,1.5769883394241333,68.35055541992188,-4.595663070678711,1.5620108842849731,68.26801300048828,-4.635960102081299,1.547582983970642,68.1858139038086,-4.676095962524414,1.5330324172973633,68.10686492919922,-4.7231340408325195,1.517133116722107,68.03079986572266,-4.77484130859375,1.4996951818466187,67.96200561523438,-4.824472427368164,1.480963110923767,67.88966369628906,-4.871505260467529,1.461897373199463,67.82241821289062,-4.916807651519775,1.442249059677124,67.75701141357422,-4.959418296813965,1.413824200630188,67.7005386352539,-4.997544765472412,1.3919544219970703,67.65782928466797,-5.034800052642822,1.3732304573059082,67.62108612060547,-5.072450160980225,1.3567445278167725,67.5888442993164,-5.107773780822754,1.3415966033935547,67.56300354003906,-5.140780925750732,1.3253334760665894,67.5457534790039,-5.172674655914307,1.3079633712768555,67.53631591796875,-5.204481601715088,1.289729356765747,67.53077697753906,-5.235482215881348,1.2740883827209473,67.53115844726562,-5.2643585205078125,1.2616835832595825,67.5383071899414,-5.2889323234558105,1.256820797920227,67.55256652832031,-5.305591106414795,1.258936882019043,67.57229614257812,-5.321035385131836,1.2649606466293335,67.60187530517578,-5.326220989227295,1.2750239372253418,67.63907623291016,-5.334343910217285,1.286670446395874,67.68606567382812,-5.338680744171143,1.3231592178344727,67.73820495605469,-5.340182781219482,1.3621586561203003,67.80036926269531,-5.339583873748779,1.4047003984451294,67.8674545288086,-5.335191249847412,1.452835202217102,67.9417953491211,-5.328793048858643,1.5043597221374512,68.022216796875,-5.322910785675049,1.545438528060913,68.11080932617188,-5.3199687004089355,1.5854034423828125,68.20516967773438,-5.327633380889893,1.6185214519500732,68.30242919921875,-5.3560285568237305,1.6455625295639038,68.39947509765625,-5.385016441345215,1.6664924621582031,68.50167846679688,-5.411978721618652,1.6732065677642822,68.60603332519531,-5.449322700500488,1.6730072498321533,68.70207977294922,-5.478846073150635,1.6687899827957153,68.7921371459961,-5.506896018981934,1.664233922958374,68.87451171875,-5.534276008605957,1.6625330448150635,68.94831085205078,-5.561803340911865,1.664645791053772,69.01274871826172,-5.590771198272705,1.664064884185791,69.07250213623047,-5.618495464324951,1.6678279638290405,69.11397552490234,-5.646039009094238,1.6846297979354858,69.13582611083984,-5.685233116149902,1.7083756923675537,69.12909698486328,-5.720620155334473,1.7301061153411865,69.09800720214844,-5.752264976501465,1.7479863166809082,69.04393005371094,-5.779262542724609,1.7636034488677979,68.9698715209961,-5.796360015869141,1.7767226696014404,68.87801361083984,-5.805150985717773,1.7879869937896729,68.77021789550781,-5.810757637023926,1.797495722770691,68.66083526611328,-5.812839984893799,1.8057279586791992,68.54518127441406,-5.810052871704102,1.8112947940826416,68.42475891113281,-5.798782825469971,1.8098785877227783,68.30023956298828,-5.792031764984131,1.8031890392303467,68.1697998046875,-5.785001277923584,1.7918721437454224,68.03956604003906,-5.7757568359375,1.7754170894622803,67.91305541992188,-5.758720397949219,1.7597558498382568,67.79642486572266,-5.731549263000488,1.7494523525238037,67.69419860839844,-5.69572639465332,1.7380964756011963,67.61172485351562,-5.657690048217773,1.7336347103118896,67.55339813232422,-5.615250110626221],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.5732452869415283,0.05757293477654457,0.1303519606590271,0.8068975210189819,0.5735357403755188,0.05712542682886124,0.13001033663749695,0.806778073310852,0.5739409327507019,0.05640653520822525,0.12941543757915497,0.8066360950469971,0.5743848085403442,0.05559227243065834,0.12873496115207672,0.8064855933189392,0.5748217105865479,0.05480324476957321,0.12810118496418,0.8063291311264038,0.5752673745155334,0.05400605872273445,0.12744617462158203,0.8061689138412476,0.5757178664207458,0.053192202001810074,0.12676705420017242,0.806008517742157,0.5761688947677612,0.052373990416526794,0.12608343362808228,0.8058470487594604,0.5766177177429199,0.05155535787343979,0.12539905309677124,0.8056855201721191,0.5770617723464966,0.05073976889252663,0.12471785396337509,0.8055251240730286,0.57750403881073,0.049912940710783005,0.12400610744953156,0.8053696751594543,0.5779741406440735,0.049019139260053635,0.12328673154115677,0.8051977753639221,0.5783575773239136,0.04822252318263054,0.12264192849397659,0.8050689697265625,0.5787034630775452,0.04743976145982742,0.12201390415430069,0.8049623370170593,0.5790362358093262,0.04667485132813454,0.12141316384077072,0.8048585653305054,0.5793512463569641,0.04593342915177345,0.12084262818098068,0.8047604560852051,0.5796458125114441,0.045231956988573074,0.12031923979520798,0.8046663999557495,0.5799190402030945,0.04455944523215294,0.11983057856559753,0.80458003282547,0.5801699161529541,0.04386945813894272,0.11934264749288559,0.8045096397399902,0.5803965926170349,0.04319663718342781,0.11887644976377487,0.804451584815979,0.5805935859680176,0.04258408024907112,0.11845764517784119,0.8044038414955139,0.5807412266731262,0.04210018366575241,0.11813328415155411,0.8043704032897949,0.5808150768280029,0.04190525785088539,0.11801868677139282,0.804344117641449,0.5807905197143555,0.04182085022330284,0.11800551414489746,0.8043681383132935,0.5806732177734375,0.042020224034786224,0.11822214722633362,0.8044106364250183,0.5805020928382874,0.04233632981777191,0.11855906993150711,0.8044679760932922,0.5802884101867676,0.04273051396012306,0.11898696422576904,0.8045381307601929,0.5800535678863525,0.04315312206745148,0.11946232616901398,0.8046144843101501,0.5798146724700928,0.043569665402173996,0.11994365602731705,0.8046925663948059,0.579553484916687,0.04398409649729729,0.12044216692447662,0.8047837615013123,0.5792723894119263,0.04439225420355797,0.12095438688993454,0.8048869371414185,0.578974187374115,0.04479217901825905,0.12147132307291031,0.805001437664032,0.5786625742912292,0.045186519622802734,0.12198518216609955,0.8051257133483887,0.5783388614654541,0.04558286815881729,0.12250208109617233,0.8052574992179871,0.5779997110366821,0.0460120290517807,0.12307924777269363,0.8053886294364929,0.5776464939117432,0.04646826162934303,0.12371625006198883,0.8055182099342346,0.5772827863693237,0.046951521188020706,0.12440112978219986,0.8056454062461853,0.5769191980361938,0.04739519953727722,0.12503278255462646,0.8057820796966553,0.5765495300292969,0.047868598252534866,0.12570977210998535,0.8059133887290955,0.5761783719062805,0.048353150486946106,0.12640151381492615,0.8060417175292969,0.5758101344108582,0.04882362112402916,0.1270766258239746,0.8061702847480774,0.5754507184028625,0.049267396330833435,0.12769922614097595,0.8063015341758728,0.5751023292541504,0.04969266057014465,0.12827551364898682,0.8064325451850891,0.5747660994529724,0.05008829012513161,0.12879934906959534,0.8065642714500427,0.5744433999061584,0.05045945197343826,0.12927639484405518,0.8066946268081665,0.5741353631019592,0.05080617964267731,0.1297161728143692,0.8068215847015381,0.5738428235054016,0.0511290468275547,0.13012179732322693,0.806943953037262,0.5735712051391602,0.05141862481832504,0.13046622276306152,0.8070630431175232,0.5733222961425781,0.05169140547513962,0.1307547241449356,0.8071758151054382,0.5730972290039062,0.0519564151763916,0.13099835813045502,0.807279109954834,0.5728984475135803,0.05222442373633385,0.13119685649871826,0.807370662689209,0.5727246403694153,0.05246817693114281,0.13135871291160583,0.8074519038200378,0.5725793242454529,0.05270426720380783,0.13148073852062225,0.8075196743011475,0.5724554061889648,0.052904386073350906,0.13156118988990784,0.8075813055038452,0.5723446011543274,0.05309058353304863,0.13164912164211273,0.8076333403587341,0.5722472667694092,0.0532732754945755,0.13173922896385193,0.8076755404472351,0.5721614956855774,0.053446125239133835,0.1318352222442627,0.807709276676178,0.5720856189727783,0.0536302886903286,0.13195185363292694,0.8077317476272583,0.5720189809799194,0.05381808429956436,0.13208691775798798,0.8077443838119507,0.5719632506370544,0.05399497225880623,0.1322091966867447,0.8077520132064819,0.5719155073165894,0.05416197329759598,0.13233879208564758,0.8077534437179565,0.5718841552734375,0.054289139807224274,0.13239145278930664,0.8077584505081177,0.5718713402748108,0.0544017069041729,0.13235996663570404,0.8077651262283325,0.5718660950660706,0.05452859774231911,0.13232962787151337,0.8077652454376221,0.5718685388565063,0.05462215095758438,0.1322709619998932,0.8077667951583862,0.5718795657157898,0.05471930652856827,0.13218525052070618,0.8077664375305176,0.5718904733657837,0.05483558028936386,0.1321389228105545,0.8077584505081177,0.5719044208526611,0.054955076426267624,0.1320909708738327,0.8077483177185059,0.5719210505485535,0.0550839938223362,0.13204514980316162,0.8077352046966553,0.5719385743141174,0.05522990599274635,0.1320105642080307,0.8077184557914734,0.571956992149353,0.05539551377296448,0.1319802701473236,0.8076990246772766,0.571979284286499,0.05554023012518883,0.13190829753875732,0.807685136795044,0.5720009803771973,0.05569101497530937,0.13181480765342712,0.8076745867729187,0.5720342397689819,0.05591362714767456,0.13170035183429718,0.8076543211936951,0.572077751159668,0.056134529411792755,0.13158033788204193,0.80762779712677,0.5721304416656494,0.05635131523013115,0.13145875930786133,0.8075951337814331,0.572190523147583,0.05656389892101288,0.13133884966373444,0.807557225227356,0.5722530484199524,0.056764114648103714,0.13124702870845795,0.8075137734413147,0.5723180770874023,0.056959785521030426,0.13118010759353638,0.8074648380279541,0.572387158870697,0.05716658756136894,0.13112817704677582,0.8074097037315369,0.5724596381187439,0.05738117918372154,0.13108983635902405,0.8073492646217346,0.5725340843200684,0.05759776383638382,0.13106228411197662,0.8072855472564697,0.572615385055542,0.057792603969573975,0.1309971809387207,0.8072245121002197,0.5726901888847351,0.05794147774577141,0.1309642791748047,0.8071660995483398,0.5727619528770447,0.05804280564188957,0.13093416392803192,0.8071128129959106,0.5728306770324707,0.05808421969413757,0.13089615106582642,0.8070672154426575,0.5728965997695923,0.058069679886102676,0.13084031641483307,0.8070305585861206,0.5729612112045288,0.058009758591651917,0.13076335191726685,0.8070014119148254,0.5730159282684326,0.05791473761200905,0.13065150380134583,0.8069875240325928,0.5730852484703064,0.05780361220240593,0.13055419921875,0.8069620132446289,0.5732452869415283,0.057572878897190094,0.13035184144973755,0.8068975210189819],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,0.9999997615814209,0.9999997615814209,0.9999999403953552,0.9999998211860657,0.9999997615814209],"interpolation":2300,"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,3.0333333015441895],"values":[8.3056001663208,-6.7115912437438965,5.719303131103516,8.305598258972168,-6.711598873138428,5.719305992126465],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.42739975452423096,0.08407263457775116,-0.10196788609027863,0.894351065158844,0.42500004172325134,0.07945239543914795,-0.1013881042599678,0.8959814310073853,0.42210060358047485,0.0743311271071434,-0.10084346681833267,0.8978511095046997,0.4198625981807709,0.06540427356958389,-0.10269810259342194,0.899383544921875,0.4187198877334595,0.05136391147971153,-0.10738912969827652,0.9002794623374939,0.41760653257369995,0.03426193445920944,-0.11326956748962402,0.900888979434967,0.4163348972797394,0.014972436241805553,-0.11945301294326782,0.9012058973312378,0.41486838459968567,-0.005312772002071142,-0.12552803754806519,0.9011651873588562,0.41335955262184143,-0.025761431083083153,-0.13068987429141998,0.9007721543312073,0.4121481478214264,-0.04557599499821663,-0.13426272571086884,0.9000168442726135,0.4113430082798004,-0.06368538737297058,-0.13577207922935486,0.899058997631073,0.41357308626174927,-0.07923086732625961,-0.13458965718746185,0.8969756960868835,0.4155988395214081,-0.09598500281572342,-0.13313834369182587,0.8946164846420288,0.41763556003570557,-0.11259406805038452,-0.1307707130908966,0.8920773863792419,0.42056897282600403,-0.12777481973171234,-0.12685410678386688,0.8892150521278381,0.42373254895210266,-0.14050592482089996,-0.12125720083713531,0.8865694999694824,0.42690223455429077,-0.15023526549339294,-0.11389186233282089,0.8844277858734131,0.430340051651001,-0.15738104283809662,-0.10495650768280029,0.8826227188110352,0.4340760111808777,-0.1624300181865692,-0.09465531259775162,0.881041944026947,0.43783798813819885,-0.1656118482351303,-0.0826660543680191,0.879793643951416,0.44154003262519836,-0.1673613041639328,-0.0689515769481659,0.8787936568260193,0.4445989727973938,-0.1688653826713562,-0.05441708117723465,0.8779835104942322,0.44761818647384644,-0.17039576172828674,-0.04008985683321953,0.8769242167472839,0.4509618580341339,-0.17358306050300598,-0.025130735710263252,0.8751404285430908,0.4561377465724945,-0.1771833300590515,-0.01092841662466526,0.8720234632492065,0.4637211561203003,-0.18178632855415344,0.0023215441033244133,0.8671280145645142,0.4728038012981415,-0.18700097501277924,0.0146934287622571,0.8609710931777954,0.4820604622364044,-0.19272057712078094,0.025300851091742516,0.8543046116828918,0.49100184440612793,-0.19811944663524628,0.032546572387218475,0.84770667552948,0.500549852848053,-0.20251664519309998,0.037371013313531876,0.8408568501472473,0.5103447437286377,-0.2068595588207245,0.042249470949172974,0.8336499929428101,0.5201064944267273,-0.21013261377811432,0.04664973169565201,0.8265332579612732,0.5297778248786926,-0.21090543270111084,0.04981363192200661,0.8199834823608398,0.5393385291099548,-0.2095852494239807,0.052853625267744064,0.8138761520385742,0.5488383769989014,-0.20685195922851562,0.05632532015442848,0.8079703450202942,0.5586020946502686,-0.20315580070018768,0.06015722081065178,0.8019179701805115,0.568875789642334,-0.1985750049352646,0.06405474990606308,0.7955157160758972,0.5792131423950195,-0.19295910000801086,0.06780737638473511,0.7891014218330383,0.5896366238594055,-0.18606312572956085,0.07154274731874466,0.7826817631721497,0.5998978018760681,-0.17741291224956512,0.07486909627914429,0.7765577435493469,0.6098844408988953,-0.16847111284732819,0.07589959353208542,0.7706475257873535,0.6196618676185608,-0.16119146347045898,0.07575833797454834,0.7643933296203613,0.6292346119880676,-0.15411648154258728,0.07516223192214966,0.7580649256706238,0.6386411190032959,-0.14672446250915527,0.0734151303768158,0.7518108487129211,0.6476977467536926,-0.13937315344810486,0.07082439959049225,0.745685338973999,0.6567624807357788,-0.1323910653591156,0.06807558238506317,0.739257276058197,0.6660385131835938,-0.125488743185997,0.06501347571611404,0.732405960559845,0.6751981377601624,-0.11867415904998779,0.06165512651205063,0.7254119515419006,0.6835587024688721,-0.1118219792842865,0.05751563236117363,0.7189821004867554,0.6908808350563049,-0.10513684153556824,0.05261805281043053,0.7133451700210571,0.6960254907608032,-0.09874015301465988,0.04666532576084137,0.7096627354621887,0.7001580595970154,-0.09219337999820709,0.03927898406982422,0.7069202065467834,0.7012649774551392,-0.08627039939165115,0.030223311856389046,0.707015872001648,0.702065110206604,-0.08005956560373306,0.019554896280169487,0.7073278427124023,0.7017908692359924,-0.07327418774366379,0.006771663203835487,0.708572268486023,0.7003812193870544,-0.06562157720327377,-0.008600898087024689,0.7106940150260925,0.6978510022163391,-0.05913371965289116,-0.024170592427253723,0.7133883833885193,0.6942408680915833,-0.0536610409617424,-0.03997858613729477,0.7166253924369812,0.6902108788490295,-0.04910080507397652,-0.0558164082467556,0.7197795510292053,0.6860451102256775,-0.045304905623197556,-0.07150868326425552,0.722617506980896,0.6813443899154663,-0.04237701743841171,-0.0870896652340889,0.7255269885063171,0.6757664680480957,-0.04014917090535164,-0.10258014500141144,0.7288381457328796,0.6697060465812683,-0.039040595293045044,-0.11859441548585892,0.7320553660392761,0.6629818677902222,-0.03704695776104927,-0.13307246565818787,0.7357814908027649,0.6558447480201721,-0.03299727663397789,-0.14513452351093292,0.7400776147842407,0.6480663418769836,-0.02816346473991871,-0.1553528904914856,0.7450385093688965,0.6404920220375061,-0.022792693227529526,-0.16250820457935333,0.7502276301383972,0.6329115033149719,-0.017541971057653427,-0.16644468903541565,0.7559176683425903,0.6252403855323792,-0.011662287637591362,-0.16842146217823029,0.7619531750679016,0.617389440536499,-0.0047474997118115425,-0.16955846548080444,0.7681521773338318,0.6092522740364075,0.0028612546157091856,-0.17008686065673828,0.7745153903961182,0.6008741855621338,0.010999158024787903,-0.17010492086410522,0.7809568047523499,0.592157244682312,0.01933364011347294,-0.16966542601585388,0.787521243095398,0.5813594460487366,0.030224986374378204,-0.16658972203731537,0.7958363890647888,0.5700008869171143,0.04055497795343399,-0.16321095824241638,0.8042490482330322,0.5586406588554382,0.049559086561203,-0.1588401347398758,0.812548041343689,0.5474404692649841,0.056733231991529465,-0.15327221155166626,0.8207301497459412,0.5363079905509949,0.06296838074922562,-0.14827865362167358,0.8285059928894043,0.5250590443611145,0.06842255592346191,-0.143650084733963,0.8360598683357239,0.513480007648468,0.07324028015136719,-0.1394849717617035,0.8435153365135193,0.5015437006950378,0.07731005549430847,-0.136056587100029,0.8508617281913757,0.48916009068489075,0.08073224127292633,-0.13352708518505096,0.8581231236457825,0.4762159287929535,0.08333275467157364,-0.13193228840827942,0.865371584892273,0.46590301394462585,0.08525316417217255,-0.12880633771419525,0.871249258518219,0.4571281671524048,0.0864560678601265,-0.12512531876564026,0.8763006329536438,0.45025011897087097,0.08692728728055954,-0.12132362276315689,0.8803402781486511,0.444673627614975,0.08683979511260986,-0.11790767312049866,0.8836413621902466,0.4400349259376526,0.08660446852445602,-0.1140209212899208,0.8864920735359192,0.4358823597431183,0.08621882647275925,-0.1095743402838707,0.8891379833221436,0.43209439516067505,0.08490430563688278,-0.10557246208190918,0.8915941715240479,0.42739948630332947,0.08407177776098251,-0.10197129100561142,0.8943508863449097],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999997615814209,0.9999998807907104,0.9999992847442627,0.9999997019767761,1.0000003576278687,0.9999995231628418],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,3.0333333015441895],"values":[-9.629152373236138e-7,25.812702178955078,0.0000016813967249618145,6.581848879250174e-7,25.812705993652344,-0.000007559851383120986],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.732982337474823,0.009039843454957008,0.053191669285297394,0.6781046390533447,0.7261741757392883,0.005280173849314451,0.05119512602686882,0.6855816841125488,0.7193483114242554,0.004557570442557335,0.052587270736694336,0.6926411986351013,0.7129018902778625,0.004914271645247936,0.0551179014146328,0.6990771293640137,0.7062217593193054,0.0020735652651637793,0.05452676862478256,0.7058848142623901,0.6981687545776367,-0.0010281502036377788,0.05386903136968613,0.71390300989151,0.6899904608726501,-0.0028686164878308773,0.05437853932380676,0.721767246723175,0.681465208530426,-0.0026194353122264147,0.056694041937589645,0.7296465635299683,0.6725205779075623,-0.00014407849812414497,0.06075350195169449,0.7375805377960205,0.6628544926643372,0.003839012235403061,0.0656987801194191,0.7458504438400269,0.6523088216781616,0.009081299416720867,0.07063353806734085,0.7546002864837646,0.6416921019554138,0.013931972905993462,0.07501078397035599,0.7631583213806152,0.6299073100090027,0.01830815151333809,0.07892616093158722,0.7724326848983765,0.6170973777770996,0.02157975547015667,0.08194886147975922,0.7823103666305542,0.6044372320175171,0.022943034768104553,0.08345309644937515,0.7919374108314514,0.5927167534828186,0.0229451023042202,0.08388742804527283,0.8007017374038696,0.584444522857666,0.02171383611857891,0.08314146101474762,0.8068708777427673,0.5780757665634155,0.019723551347851753,0.08173388987779617,0.8116397261619568,0.5719631910324097,0.0172254778444767,0.08001483976840973,0.816185712814331,0.5664008855819702,0.014172961935400963,0.07794088870286942,0.820313572883606,0.5619707703590393,0.010622306726872921,0.07549893110990524,0.8236358761787415,0.559281051158905,0.006863171700388193,0.07283515483140945,0.8257437348365784,0.5587190985679626,0.0038449992425739765,0.0703245997428894,0.8263611197471619,0.5583697557449341,0.0009954455308616161,0.06795904785394669,0.8268033266067505,0.5599954724311829,-0.0009431930957362056,0.06661976128816605,0.8258123397827148,0.5624482035636902,-0.0026562551502138376,0.06557223945856094,0.824224054813385,0.5652955770492554,-0.0051047117449343204,0.06416735798120499,0.8223730325698853,0.5680441856384277,-0.007962584495544434,0.06263155490159988,0.8205727934837341,0.5714272260665894,-0.01082262396812439,0.06105577573180199,0.8183068037033081,0.5747832655906677,-0.013609234243631363,0.05953729897737503,0.8160234689712524,0.5780529379844666,-0.0162812527269125,0.0580657459795475,0.8137678503990173,0.5812134742736816,-0.018681500107049942,0.05670323967933655,0.811558187007904,0.5843586325645447,-0.020605437457561493,0.0555369108915329,0.8093306422233582,0.5874179601669312,-0.02199018932878971,0.05459101125597954,0.8071408867835999,0.5939001441001892,-0.023150835186243057,0.05291213095188141,0.8024631142616272,0.6028575301170349,-0.024119006469845772,0.0506310909986496,0.7958753108978271,0.611286997795105,-0.024655023589730263,0.048578228801488876,0.7895318269729614,0.6181970238685608,-0.024987811222672462,0.04681535065174103,0.7842297554016113,0.6252642273902893,-0.025144649669528008,0.04504629224538803,0.7787061333656311,0.6322982311248779,-0.02487325109541416,0.04356023296713829,0.7730994820594788,0.6394151449203491,-0.023089198395609856,0.04331233352422714,0.7672934532165527,0.646091103553772,-0.02119111642241478,0.043317146599292755,0.7617354393005371,0.6522901058197021,-0.01962115988135338,0.04323938116431236,0.7564806938171387,0.658078670501709,-0.0176125168800354,0.043775130063295364,0.7514691948890686,0.663486123085022,-0.015322134830057621,0.044831521809101105,0.7466870546340942,0.6684121489524841,-0.013450268656015396,0.04589337483048439,0.7422520518302917,0.6728588342666626,-0.012037352658808231,0.04704027622938156,0.738175630569458,0.6765937805175781,-0.011466498486697674,0.04758468270301819,0.7347278594970703,0.6797228455543518,-0.011514517478644848,0.04769984260201454,0.7318257689476013,0.6815059185028076,-0.013140917755663395,0.04659581929445267,0.7302094101905823,0.6819305419921875,-0.017110511660575867,0.04354076087474823,0.7299193143844604,0.683354377746582,-0.02163318172097206,0.03977896645665169,0.7286813259124756,0.6852452158927917,-0.025995537638664246,0.03604292869567871,0.7269554138183594,0.6872944831848145,-0.028850028291344643,0.0336286798119545,0.7250263094902039,0.6899688839912415,-0.029360339045524597,0.03323015943169594,0.7224795818328857,0.6932260394096375,-0.028114551678299904,0.03435199707746506,0.7193519473075867,0.6967939734458923,-0.027157306671142578,0.03512493520975113,0.7158958911895752,0.7003781795501709,-0.02598697319626808,0.03607188165187836,0.7123861312866211,0.7033331990242004,-0.025726040825247765,0.0362074077129364,0.7094714045524597,0.705242931842804,-0.026079565286636353,0.03587843105196953,0.7075768709182739,0.707263171672821,-0.026763124391436577,0.035130541771650314,0.7055695652961731,0.7094078063964844,-0.028138533234596252,0.0336395688354969,0.7034324407577515,0.7115260362625122,-0.029274480417370796,0.032348331063985825,0.701304018497467,0.7136794328689575,-0.030859556049108505,0.03052416816353798,0.6991263628005981,0.7156055569648743,-0.033033713698387146,0.028057478368282318,0.697158694267273,0.7176066040992737,-0.035615306347608566,0.02506067417562008,0.6950858235359192,0.7197240591049194,-0.03749673441052437,0.022705422714352608,0.6928749680519104,0.721891462802887,-0.03869592025876045,0.021047621965408325,0.6906029582023621,0.724155604839325,-0.038791198283433914,0.02051694318652153,0.6882390379905701,0.7265101075172424,-0.03795679286122322,0.020942693576216698,0.6857870221138,0.7289108633995056,-0.03683102875947952,0.021676767617464066,0.6832733750343323,0.7311950325965881,-0.03549503907561302,0.022678032517433167,0.6808668971061707,0.733516275882721,-0.033962804824113846,0.023987343534827232,0.6783989071846008,0.736214280128479,-0.03145074471831322,0.02448303997516632,0.6755738258361816,0.7390744686126709,-0.028147993609309196,0.024313069880008698,0.6725960969924927,0.7419852614402771,-0.024324337020516396,0.02384881302714348,0.6695501804351807,0.7448712587356567,-0.02010802924633026,0.023206472396850586,0.6665012836456299,0.7477861642837524,-0.01563505455851555,0.022465454414486885,0.6633752584457397,0.7506674528121948,-0.01103783119469881,0.02174762636423111,0.6602299809455872,0.7534323930740356,-0.006454866379499435,0.021171556785702705,0.6571527719497681,0.7559173107147217,-0.0020267574582248926,0.020863158628344536,0.6543315052986145,0.7580275535583496,0.0021156182046979666,0.020942969247698784,0.6518828272819519,0.7598063349723816,0.005903999321162701,0.02159164473414421,0.6497640013694763,0.760906457901001,0.009239871986210346,0.023016098886728287,0.6483874320983887,0.7609631419181824,0.011833865195512772,0.025133982300758362,0.6482000946998596,0.7596772909164429,0.013279955834150314,0.02818121947348118,0.649553656578064,0.7569892406463623,0.013453657738864422,0.03325719013810158,0.6524417996406555,0.7526503205299377,0.012994606979191303,0.04017357900738716,0.6570651531219482,0.746595561504364,0.013867776840925217,0.049103472381830215,0.6633186340332031,0.7399845719337463,0.012660675682127476,0.05441083014011383,0.6702999472618103,0.7329824566841125,0.00904143787920475,0.053193118423223495,0.6781044006347656],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,1,1.0000001192092896,1,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,3.0333333015441895],"values":[0.0000022675681066175457,22.74271011352539,-3.18039269586734e-7,0.0000054596594054601155,22.742708206176758,-7.164456405917008e-7],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.5139148235321045,-0.08396413922309875,-0.21361961960792542,0.8265641331672668,-0.5190602540969849,-0.06817439943552017,-0.20637261867523193,0.8266432881355286,-0.516157329082489,-0.056373462080955505,-0.2079833298921585,0.8289430737495422,-0.510578989982605,-0.0468466579914093,-0.2113364189863205,0.8321366906166077,-0.5046448111534119,-0.040143806487321854,-0.21377865970134735,0.8354763388633728,-0.4979427456855774,-0.03554939478635788,-0.21544374525547028,0.8392694592475891,-0.4906044006347656,-0.0324583575129509,-0.21628691256046295,0.8434890508651733,-0.4832829236984253,-0.030851241201162338,-0.21503810584545135,0.8480828404426575,-0.47649306058883667,-0.03024394065141678,-0.21147432923316956,0.8528295755386353,-0.4690912365913391,-0.03257695585489273,-0.207079216837883,0.8579104542732239,-0.46029019355773926,-0.03578604757785797,-0.20173414051532745,0.8638030290603638,-0.45181766152381897,-0.03926047682762146,-0.19598954916000366,0.8694294095039368,-0.4403207302093506,-0.042238932102918625,-0.19153648614883423,0.8761548399925232,-0.4253096580505371,-0.044580768793821335,-0.18850752711296082,0.8840752840042114,-0.41072630882263184,-0.04631195217370987,-0.18611665070056915,0.8913583755493164,-0.396328330039978,-0.04763365164399147,-0.1831779181957245,0.8983878493309021,-0.3826228678226471,-0.04885232448577881,-0.17856314778327942,0.9051676392555237,-0.36944901943206787,-0.049692846834659576,-0.17251212894916534,0.9117442965507507,-0.3536342978477478,-0.04943501949310303,-0.1661989688873291,0.9191718697547913,-0.3375966250896454,-0.048323843628168106,-0.15977446734905243,0.926372230052948,-0.32408004999160767,-0.04628993570804596,-0.15296100080013275,0.932433545589447,-0.31584617495536804,-0.042229682207107544,-0.14502279460430145,0.9367103576660156,-0.31784969568252563,-0.04631911590695381,-0.12879855930805206,0.9382095336914062,-0.32551297545433044,-0.03350142389535904,-0.12017735838890076,0.9372707009315491,-0.3417474031448364,-0.029598865658044815,-0.10264011472463608,0.9337010383605957,-0.36281970143318176,-0.02407890371978283,-0.08362934738397598,0.9277867078781128,-0.3870689272880554,-0.01697883941233158,-0.06398413330316544,0.919671356678009,-0.412804514169693,-0.008945880457758904,-0.04602644592523575,0.9096119999885559,-0.43863415718078613,-0.0009520907187834382,-0.03004464879631996,0.898162841796875,-0.4630019962787628,0.007365803234279156,-0.014354084618389606,0.8862104415893555,-0.4843984842300415,0.015941618010401726,0.0007710087229497731,0.8747019171714783,-0.5013225674629211,0.025712119415402412,0.01563313603401184,0.8647370934486389,-0.5137086510658264,0.03597518056631088,0.03002895601093769,0.8566840291023254,-0.5223848223686218,0.04593444988131523,0.04308174550533295,0.8503811955451965,-0.5283669233322144,0.05481332913041115,0.05393945798277855,0.8455261588096619,-0.5324373841285706,0.06168936565518379,0.0622217021882534,0.8419224619865417,-0.539678156375885,0.06604322791099548,0.06816912442445755,0.8365039229393005,-0.5391039848327637,0.06738708168268204,0.07046902179718018,0.8365763425827026,-0.5417938232421875,0.06756067276000977,0.0712965875864029,0.8347525596618652,-0.5441080331802368,0.06662638485431671,0.06990920752286911,0.8334387540817261,-0.546698272228241,0.06529384851455688,0.06538259983062744,0.8322156071662903,-0.547553300857544,0.06307019293308258,0.05912018567323685,0.8322934508323669,-0.5467679500579834,0.05996433272957802,0.0518127977848053,0.8335254192352295,-0.5438811779022217,0.056521907448768616,0.04272879660129547,0.8361655473709106,-0.5391721129417419,0.05349211394786835,0.030927330255508423,0.8399259448051453,-0.5342578887939453,0.050185561180114746,0.019183741882443428,0.8436124324798584,-0.5303239822387695,0.04616721346974373,0.009239067323505878,0.8464866876602173,-0.5250402688980103,0.04123229160904884,0.0016160717932507396,0.8500764966011047,-0.5187957882881165,0.03617016226053238,-0.004893788136541843,0.8541187047958374,-0.5127103924751282,0.03148168325424194,-0.010643740184605122,0.8579182624816895,-0.5071786642074585,0.02647370845079422,-0.014908014796674252,0.8613052368164062,-0.5003922581672668,0.02143322490155697,-0.019768156111240387,0.8653077483177185,-0.4931497275829315,0.01671573705971241,-0.025364389643073082,0.8694139122962952,-0.485879123210907,0.013389140367507935,-0.03135247528553009,0.8733608722686768,-0.4821590185165405,0.011476736515760422,-0.03831992670893669,0.8751700520515442,-0.47997942566871643,0.01068616658449173,-0.04639548808336258,0.8759869337081909,-0.4791586995124817,0.010524798184633255,-0.05495825409889221,0.8759427666664124,-0.4798051118850708,0.010636438615620136,-0.0636393129825592,0.8749994039535522,-0.4824332892894745,0.010724293068051338,-0.07221114635467529,0.8728852868080139,-0.4854710102081299,0.010670965537428856,-0.08078944683074951,0.870446503162384,-0.4910436272621155,0.010408177971839905,-0.08958744257688522,0.8664536476135254,-0.4936154782772064,0.009726938791573048,-0.09834626317024231,0.8640469908714294,-0.493617445230484,0.007976760156452656,-0.10588237643241882,0.863172709941864,-0.49292224645614624,0.005711500532925129,-0.11367366462945938,0.862596869468689,-0.49311307072639465,0.0023994874209165573,-0.12100578844547272,0.8615052700042725,-0.49361464381217957,-0.00005662505282089114,-0.13206085562705994,0.859595537185669,-0.4946964979171753,-0.004523791838437319,-0.1407528519630432,0.8575800657272339,-0.4957696795463562,-0.009795443154871464,-0.1491696536540985,0.855491042137146,-0.4968356192111969,-0.016084201633930206,-0.15650267899036407,0.8534650802612305,-0.497910737991333,-0.023302357643842697,-0.16256539523601532,0.8515365123748779,-0.4987546503543854,-0.030763763934373856,-0.16841372847557068,0.8496671319007874,-0.4991653859615326,-0.03823051229119301,-0.17417332530021667,0.8479599356651306,-0.4990576207637787,-0.04557160288095474,-0.17974086105823517,0.8464974761009216,-0.49780741333961487,-0.05248467996716499,-0.18530218303203583,0.8456336259841919,-0.4952429533004761,-0.058814216405153275,-0.19078899919986725,0.8455027341842651,-0.49381446838378906,-0.06484952569007874,-0.19587582349777222,0.8447334170341492,-0.4935483932495117,-0.07158735394477844,-0.2009192407131195,0.8431587815284729,-0.49443894624710083,-0.07986418902873993,-0.2061423659324646,0.8406290411949158,-0.49623727798461914,-0.08901660889387131,-0.21128970384597778,0.8373657464981079,-0.49866798520088196,-0.09841827303171158,-0.21617214381694794,0.8336148858070374,-0.5014955401420593,-0.10744819790124893,-0.22061379253864288,0.829630434513092,-0.5045318007469177,-0.11550790816545486,-0.22440238296985626,0.8256810307502747,-0.5077604055404663,-0.12201454490423203,-0.22734351456165314,0.8219530582427979,-0.5101866126060486,-0.12626856565475464,-0.22951699793338776,0.8191994428634644,-0.5116013884544373,-0.1269250512123108,-0.23091644048690796,0.8178213238716125,-0.5116350650787354,-0.12537387013435364,-0.23207296431064606,0.817712128162384,-0.5110174417495728,-0.12306568771600723,-0.23286859691143036,0.8182225823402405,-0.5106173157691956,-0.11946293711662292,-0.23181258141994476,0.8193054795265198,-0.5103423595428467,-0.11344970017671585,-0.22864572703838348,0.821219265460968,-0.5112385153770447,-0.10393531620502472,-0.2231421023607254,0.8234319686889648,-0.5139144659042358,-0.0839630514383316,-0.21362170577049255,0.8265639543533325],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998807907104,0.9999998211860657,0.9999998807907104,0.9999998807907104,0.9999998211860657,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,3.0333333015441895],"values":[-2.5185033791785827e-7,13.415111541748047,-0.0000011839505305033526,0.000003073059133384959,13.415108680725098,-7.715581062939236e-8],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[-0.30698150396347046,-0.005662623327225447,-0.0018292294116690755,0.9516968727111816,-0.3069814443588257,-0.005662654992192984,-0.0018292147433385253,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,0.9999999403953552,1.0000001192092896,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,3.0333333015441895],"values":[-11.139503479003906,-4.020101070404053,0.4203105568885803,-11.139498710632324,-4.020099639892578,0.42030754685401917],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.6886920928955078,-0.17020148038864136,0.15264606475830078,0.6880652904510498,0.6856892108917236,-0.181200310587883,0.15900269150733948,0.6868150234222412,0.6814005970954895,-0.19008103013038635,0.1633051335811615,0.6876728534698486,0.6785831451416016,-0.19856491684913635,0.16680368781089783,0.6872215867042542,0.6741389036178589,-0.20699237287044525,0.1697389930486679,0.6883891224861145,0.6700958013534546,-0.21512049436569214,0.17211879789829254,0.6892531514167786,0.6660181283950806,-0.22290126979351044,0.17418916523456573,0.6902123689651489,0.6617003679275513,-0.2303934544324875,0.17618191242218018,0.6913982033729553,0.656815767288208,-0.23748591542243958,0.17812401056289673,0.6931561827659607,0.6513703465461731,-0.24413366615772247,0.18014906346797943,0.6954579949378967,0.6453385353088379,-0.25018155574798584,0.18223229050636292,0.698383092880249,0.6386865377426147,-0.25642839074134827,0.18532565236091614,0.701411783695221,0.6318071484565735,-0.2616453766822815,0.1888752430677414,0.7047606706619263,0.6249946355819702,-0.2651815116405487,0.1932714879512787,0.7083125710487366,0.6179800033569336,-0.26867932081222534,0.19746887683868408,0.7119818925857544,0.6107994914054871,-0.2719840407371521,0.20155595242977142,0.7157680988311768,0.6034302115440369,-0.27513253688812256,0.2054957151412964,0.7196844220161438,0.595988392829895,-0.27801617980003357,0.2092551589012146,0.7236830592155457,0.5885168313980103,-0.2805090844631195,0.21276189386844635,0.7278015613555908,0.5810179710388184,-0.2826497256755829,0.21599332988262177,0.7320343255996704,0.5734021663665771,-0.2846328914165497,0.2189609259366989,0.7363764643669128,0.5656698346138,-0.28658342361450195,0.22167786955833435,0.7407743334770203,0.5579512715339661,-0.2886579632759094,0.22421894967556,0.74504554271698,0.5502536296844482,-0.29081040620803833,0.2265021950006485,0.7492310404777527,0.5427370667457581,-0.29314544796943665,0.2285918891429901,0.7531587481498718,0.5354409217834473,-0.2954789400100708,0.2303803414106369,0.7569149136543274,0.5284719467163086,-0.2974400818347931,0.2317311316728592,0.7606231570243835,0.5221598148345947,-0.29891616106033325,0.23227202892303467,0.7642303705215454,0.5158328413963318,-0.30056318640708923,0.23195604979991913,0.767967939376831,0.5101028680801392,-0.30284467339515686,0.23041069507598877,0.7713567018508911,0.5046289563179016,-0.30517810583114624,0.22863003611564636,0.7745606899261475,0.49945682287216187,-0.30761638283729553,0.22659383714199066,0.7775412201881409,0.49463480710983276,-0.310115247964859,0.22429299354553223,0.780293345451355,0.4902985990047455,-0.3125776946544647,0.22169449925422668,0.7827860713005066,0.48638856410980225,-0.31496384739875793,0.21873563528060913,0.7850978970527649,0.48286718130111694,-0.317232221364975,0.21536476910114288,0.787287175655365,0.4798647165298462,-0.31933075189590454,0.21162626147270203,0.7892858386039734,0.47730034589767456,-0.3212242126464844,0.20749816298484802,0.7911661863327026,0.47530341148376465,-0.323018342256546,0.2030162811279297,0.7927989959716797,0.4739294648170471,-0.3249936103820801,0.19774697721004486,0.7941449284553528,0.4726116359233856,-0.3281668424606323,0.19281569123268127,0.7948377132415771,0.47238674759864807,-0.3281850218772888,0.18816515803337097,0.7960774302482605,0.47266215085983276,-0.3258817493915558,0.18547025322914124,0.7974913716316223,0.47300785779953003,-0.32303547859191895,0.18247371912002563,0.7991339564323425,0.4734285771846771,-0.31997358798980713,0.17903999984264374,0.8008913993835449,0.473959356546402,-0.3166438639163971,0.17535503208637238,0.8027140498161316,0.4749104380607605,-0.31286895275115967,0.1713554710149765,0.8044939041137695,0.47622522711753845,-0.30855992436408997,0.16708220541477203,0.8062778115272522,0.4779242277145386,-0.30365657806396484,0.16263613104820251,0.8080412745475769,0.4799651503562927,-0.29811403155326843,0.1581348031759262,0.8097869753837585,0.4827404320240021,-0.2913757264614105,0.1529097557067871,0.8115913271903992,0.48597532510757446,-0.2834998369216919,0.1472117155790329,0.8135014176368713,0.48928675055503845,-0.27484381198883057,0.14179149270057678,0.8154475092887878,0.49214255809783936,-0.26574137806892395,0.13731157779693604,0.8175101280212402,0.49419745802879333,-0.256538063287735,0.13463503122329712,0.8196527361869812,0.497622549533844,-0.24343283474445343,0.12913434207439423,0.8224576711654663,0.5002789497375488,-0.2285764515399933,0.1248575896024704,0.825762927532196,0.5023506283760071,-0.2120932936668396,0.12101951986551285,0.8294664621353149,0.5042641758918762,-0.19379287958145142,0.11651597172021866,0.8334183096885681,0.5062687993049622,-0.1737656444311142,0.11064449697732925,0.8374097943305969,0.5085960030555725,-0.15241968631744385,0.10307333618402481,0.84111487865448,0.5116192102432251,-0.12980952858924866,0.09314572811126709,0.8442269563674927,0.5178038477897644,-0.10774584859609604,0.08321654796600342,0.844597578048706,0.5250208377838135,-0.0869382694363594,0.07355966418981552,0.8434357643127441,0.5324057340621948,-0.06836836040019989,0.06465029716491699,0.841243326663971,0.5414128303527832,-0.054860372096300125,0.05607642978429794,0.837088942527771,0.5490514636039734,-0.04312603175640106,0.04825311154127121,0.8332792520523071,0.556378960609436,-0.03273377567529678,0.04041912406682968,0.82929927110672,0.5635793805122375,-0.02378169447183609,0.03226262703537941,0.8250889778137207,0.5708548426628113,-0.016013022512197495,0.024216173216700554,0.8205376267433167,0.5787217020988464,-0.009347465820610523,0.017196621745824814,0.8152902126312256,0.585626482963562,-0.005569911561906338,0.012866741046309471,0.8104597926139832,0.5927862524986267,-0.003237365745007992,0.010324455797672272,0.8052871227264404,0.604705810546875,-0.002554369857534766,0.00992259569466114,0.7963830232620239,0.6162388920783997,-0.0032347477972507477,0.010644854046404362,0.7874807715415955,0.6273865699768066,-0.005392900668084621,0.011277648620307446,0.7786076068878174,0.6379574537277222,-0.008920247666537762,0.01258447952568531,0.7699171304702759,0.6462656259536743,-0.014002397656440735,0.015315894037485123,0.7628303170204163,0.6528196334838867,-0.020871300250291824,0.019864056259393692,0.7569652199745178,0.6592881083488464,-0.0296119824051857,0.026062658056616783,0.7508549094200134,0.6655533909797668,-0.03997479006648064,0.033694054931402206,0.7445169687271118,0.6712005138397217,-0.05155447497963905,0.04256454482674599,0.7382549047470093,0.6764907240867615,-0.06477170437574387,0.05268391966819763,0.7317030429840088,0.6819996237754822,-0.0795523151755333,0.06414603441953659,0.7241776585578918,0.6867809891700745,-0.09521026164293289,0.07657786458730698,0.7165213227272034,0.690079927444458,-0.11116982251405716,0.08953408151865005,0.709517240524292,0.691709578037262,-0.12688776850700378,0.10243891924619675,0.7035223841667175,0.6918708086013794,-0.14036478102207184,0.11555936187505722,0.6987550258636475,0.6909946799278259,-0.15131382644176483,0.12836101651191711,0.6950927972793579,0.6897986531257629,-0.16110146045684814,0.14088666439056396,0.6916467547416687,0.688693642616272,-0.17020215094089508,0.15263870358467102,0.6880652904510498],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1.000000238418579,1,1.0000001192092896,0.9999999403953552,1,1.0000001192092896],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,3.0333333015441895],"values":[-0.0000017537113308208063,25.527099609375,0.0000013938905567556503,8.233031962845416e-7,25.527103424072266,-0.0000015667845900679822],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.580320417881012,-0.11118900775909424,-0.0970376506447792,0.8009051084518433,0.5737590789794922,-0.11630793660879135,-0.10047546774148941,0.8044735789299011,0.5699077844619751,-0.11945385485887527,-0.10256864130496979,0.8064835071563721,0.5673394203186035,-0.12023507803678513,-0.1030752882361412,0.8081120252609253,0.5643599033355713,-0.12097577005624771,-0.10349106788635254,0.8100324273109436,0.5617837309837341,-0.12138848006725311,-0.10364308953285217,0.8117401003837585,0.5595310926437378,-0.1183931976556778,-0.10155191272497177,0.8139995336532593,0.5574590563774109,-0.1131097823381424,-0.09798943251371384,0.8166049718856812,0.5558235049247742,-0.10724764317274094,-0.09408809244632721,0.8189662098884583,0.5544612407684326,-0.10130412131547928,-0.0901702344417572,0.8210843801498413,0.5536826252937317,-0.09585773944854736,-0.08660870790481567,0.8226456046104431,0.5521190166473389,-0.09196159988641739,-0.08410521596670151,0.8243991732597351,0.551281750202179,-0.0881408229470253,-0.0816526710987091,0.8256224989891052,0.5510214567184448,-0.08422533422708511,-0.07912793010473251,0.8264504075050354,0.5508638620376587,-0.08035791665315628,-0.07663518190383911,0.8271751403808594,0.5507597327232361,-0.07651250809431076,-0.07415767014026642,0.8278346657752991,0.5506421327590942,-0.07266495376825333,-0.071682408452034,0.8284773230552673,0.5505595803260803,-0.068783700466156,-0.06918592751026154,0.8290755152702332,0.5506128072738647,-0.06498266756534576,-0.06673627346754074,0.8295475840568542,0.550738513469696,-0.061381034553050995,-0.06441181898117065,0.8299220204353333,0.5512317419052124,-0.05758132040500641,-0.06192133575677872,0.8300564885139465,0.5520131587982178,-0.05364115536212921,-0.05929938331246376,0.8299925923347473,0.5526160597801208,-0.04957094416022301,-0.05658833310008049,0.8300337195396423,0.5531439781188965,-0.045562706887722015,-0.05392139405012131,0.8300893306732178,0.5529882907867432,-0.04158550128340721,-0.05134273320436478,0.8305652141571045,0.5522032976150513,-0.037740308791399,-0.048932697623968124,0.831416130065918,0.5511468052864075,-0.03382890671491623,-0.046522680670022964,0.8324232697486877,0.5494511127471924,-0.029709910973906517,-0.04408273473381996,0.8338330388069153,0.549186110496521,-0.025362448766827583,-0.041284993290901184,0.8342942595481873,0.5485580563545227,-0.019545000046491623,-0.03758573159575462,0.8350385427474976,0.5475406050682068,-0.014757427386939526,-0.03464213013648987,0.8359315395355225,0.5462298393249512,-0.011648261919617653,-0.03286468982696533,0.8369093537330627,0.544719398021698,-0.008937427774071693,-0.03139879181981087,0.837982714176178,0.5427581667900085,-0.006440864410251379,-0.030173614621162415,0.8393221497535706,0.5402980446815491,-0.0041225203312933445,-0.02917538397014141,0.8409577012062073,0.5373879075050354,-0.001957866596058011,-0.02837962657213211,0.8428553342819214,0.534018337726593,0.00004076030745636672,-0.027797814458608627,0.845015823841095,0.5305094122886658,0.0018150185933336616,-0.02741410955786705,0.8472337126731873,0.5265911817550659,0.003041613847017288,-0.027466250583529472,0.8496693968772888,0.5224384665489197,0.003269646782428026,-0.02819061651825905,0.8522046208381653,0.5178963541984558,0.0036036286037415266,-0.02895687147974968,0.8549455404281616,0.513422966003418,0.0048661441542208195,-0.02914174646139145,0.8576269745826721,0.5088750123977661,0.006116199307143688,-0.029364319518208504,0.8603177070617676,0.5044858455657959,0.007553611416369677,-0.029459824785590172,0.8628841638565063,0.5002017617225647,0.00905461423099041,-0.029513642191886902,0.8653584122657776,0.49591463804244995,0.010493735782802105,-0.029622655361890793,0.8678024411201477,0.4917670488357544,0.011839492246508598,-0.02977006323635578,0.8701372146606445,0.48803168535232544,0.012952079996466637,-0.029980650171637535,0.8722147345542908,0.48477301001548767,0.013684434816241264,-0.030302386730909348,0.8740078210830688,0.48205840587615967,0.013879666104912758,-0.030796784907579422,0.8754876852035522,0.4805648624897003,0.013629820197820663,-0.031243987381458282,0.8762964606285095,0.48004066944122314,0.01275067962706089,-0.03180176019668579,0.8765769004821777,0.4802565574645996,0.010963665321469307,-0.03269360587000847,0.8764500021934509,0.4822578430175781,0.007758268620818853,-0.03398080915212631,0.8753356337547302,0.4851151406764984,0.0036039797123521566,-0.03564058244228363,0.8737162351608276,0.48919206857681274,-0.0010432959534227848,-0.037334736436605453,0.8713759779930115,0.4946618378162384,-0.00741345901042223,-0.03979739546775818,0.8681422472000122,0.5007986426353455,-0.0165014136582613,-0.043799933046102524,0.8642973899841309,0.5075509548187256,-0.026861250400543213,-0.04861138388514519,0.8598299026489258,0.51491379737854,-0.037322141230106354,-0.05361998826265335,0.8547489643096924,0.5239956974983215,-0.046932052820920944,-0.05816182494163513,0.8484356999397278,0.5363173484802246,-0.05455581471323967,-0.06144476309418678,0.8400070667266846,0.5504571795463562,-0.060687530785799026,-0.06396368891000748,0.8301942944526672,0.5627738833427429,-0.06435582786798477,-0.06532686948776245,0.8215085864067078,0.5740085244178772,-0.06265146285295486,-0.0632639154791832,0.813994288444519,0.5854611992835999,-0.0590987429022789,-0.05983428284525871,0.8063265085220337,0.5949203968048096,-0.055868469178676605,-0.056698936969041824,0.7998335361480713,0.6034732460975647,-0.0522000715136528,-0.053225595504045486,0.7938905954360962,0.6112592220306396,-0.048983003944158554,-0.050070639699697495,0.7883246541023254,0.6182309985160828,-0.04610024765133858,-0.04717765003442764,0.7832238078117371,0.6244034767150879,-0.043685201555490494,-0.04467272385954857,0.7785989046096802,0.6312636733055115,-0.04150668904185295,-0.042231958359479904,0.7733045220375061,0.6376379728317261,-0.03986620157957077,-0.04022962599992752,0.7682512998580933,0.6424227952957153,-0.040394943207502365,-0.04016919434070587,0.7642301321029663,0.6460219025611877,-0.04339626803994179,-0.04232795909047127,0.7609079480171204,0.6486225128173828,-0.04759962111711502,-0.04563785716891289,0.7582482099533081,0.6503154039382935,-0.05311008542776108,-0.050170376896858215,0.7561429142951965,0.6517580151557922,-0.060246169567108154,-0.05614801496267319,0.7539425492286682,0.6528785228729248,-0.06888967007398605,-0.063478022813797,0.7516477704048157,0.6529372334480286,-0.078424371778965,-0.07163604348897934,0.749927282333374,0.6517775058746338,-0.08830424398183823,-0.08009416610002518,0.7489815950393677,0.6495393514633179,-0.09772249311208725,-0.08810454607009888,0.7488568425178528,0.6456586122512817,-0.0999462828040123,-0.09007342159748077,0.7516797780990601,0.6386544108390808,-0.10204516351222992,-0.09179704636335373,0.7571530342102051,0.630342423915863,-0.1042172908782959,-0.09342413395643234,0.7635961771011353,0.6221821904182434,-0.10697943717241287,-0.09530521929264069,0.7696503400802612,0.6140832304954529,-0.10926833003759384,-0.09675705432891846,0.7756289839744568,0.6060471534729004,-0.11135208606719971,-0.09803584963083267,0.781470775604248,0.5984749794006348,-0.11321336030960083,-0.09910585731267929,0.7868853211402893,0.5899261832237244,-0.11508182436227798,-0.10009422153234482,0.7929214835166931,0.5803211331367493,-0.11118408292531967,-0.09703417122364044,0.800905704498291],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998807907104,0.9999997019767761,0.9999998211860657,0.9999999403953552,0.9999998807907104,0.9999997019767761],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,3.0333333015441895],"values":[-5.535321747629496e-7,23.10919952392578,-0.00000430916315963259,1.6156427307123522e-7,23.10919761657715,0.0000016170458820852218],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.4743829369544983,0.030031166970729828,-0.021408531814813614,0.8795456886291504,-0.4778803884983063,0.0388341024518013,-0.027575405314564705,0.877132773399353,-0.48186805844306946,0.04581712931394577,-0.032087985426187515,0.8744566440582275,-0.48613494634628296,0.051447056233882904,-0.03563346341252327,0.8716399669647217,-0.48998698592185974,0.05627245455980301,-0.03915735334157944,0.8690298795700073,-0.49338340759277344,0.060220107436180115,-0.04217324033379555,0.8666993975639343,-0.4963153898715973,0.06366437673568726,-0.04517851397395134,0.8646252155303955,-0.4976010322570801,0.06676900386810303,-0.04795769229531288,0.8635016679763794,-0.49785616993904114,0.06938716769218445,-0.05020149424672127,0.8630205392837524,-0.49759602546691895,0.07146519422531128,-0.0517582893371582,0.8629090189933777,-0.4971267879009247,0.07290301471948624,-0.05225135758519173,0.8630295395851135,-0.4956464469432831,0.07388660311698914,-0.05180491507053375,0.8638238906860352,-0.4945025146007538,0.07412856072187424,-0.049928296357393265,0.8645688891410828,-0.49375075101852417,0.0738527923822403,-0.04695184528827667,0.8651886582374573,-0.49293404817581177,0.0733601525425911,-0.043293215334415436,0.8658868670463562,-0.49196138978004456,0.07210567593574524,-0.03928925096988678,0.8667359352111816,-0.4907320737838745,0.07002381235361099,-0.035103801637887955,0.8677825331687927,-0.48946520686149597,0.06779935210943222,-0.030462827533483505,0.8688492774963379,-0.4882458448410034,0.06527918577194214,-0.025241954252123833,0.8698951005935669,-0.4869915246963501,0.06234529986977577,-0.019215840846300125,0.8709667325019836,-0.4859551787376404,0.05934992805123329,-0.013091498054564,0.8718679547309875,-0.4849778115749359,0.056308843195438385,-0.006897615268826485,0.8726845383644104,-0.48372769355773926,0.05322089046239853,-0.0006407490000128746,0.8735986948013306,-0.48249104619026184,0.05015607923269272,0.005562884267419577,0.8744460344314575,-0.4806607663631439,0.04705839976668358,0.011811807751655579,0.875563383102417,-0.4782155454158783,0.04390685632824898,0.018161697313189507,0.8769562244415283,-0.47575104236602783,0.040654223412275314,0.024671494960784912,0.8782935738563538,-0.47361084818840027,0.03728151321411133,0.031337544322013855,0.8793866634368896,-0.47160881757736206,0.03435415029525757,0.03722291439771652,0.8803518414497375,-0.46980854868888855,0.03208232298493385,0.041794553399086,0.8811945915222168,-0.46782535314559937,0.02978658303618431,0.04672429338097572,0.8820822238922119,-0.46545910835266113,0.028238635510206223,0.052245188504457474,0.8830746412277222,-0.4629259407520294,0.027407456189393997,0.05817469209432602,0.8840611577033997,-0.46051010489463806,0.0268706064671278,0.0642237588763237,0.8849201798439026,-0.4586227238178253,0.026576168835163116,0.07030987739562988,0.8854464888572693,-0.45694586634635925,0.026478715240955353,0.07634977996349335,0.8858160972595215,-0.45544004440307617,0.026546910405158997,0.08221748471260071,0.8860642910003662,-0.45420825481414795,0.026766641065478325,0.08775053173303604,0.8861593008041382,-0.4530748128890991,0.02706514298915863,0.09286532551050186,0.8862091898918152,-0.4519747793674469,0.027399132028222084,0.09740620106458664,0.886273205280304,-0.45140159130096436,0.027801040560007095,0.1014590859413147,0.8860980868339539,-0.45162010192871094,0.028390102088451385,0.10547712445259094,0.8854987025260925,-0.4521493911743164,0.02942703478038311,0.10893642902374268,0.8847755789756775,-0.4527515769004822,0.03156815469264984,0.11107412725687027,0.8841278553009033,-0.4535207152366638,0.0352005660533905,0.11093778163194656,0.8836134076118469,-0.4544219374656677,0.039416346698999405,0.1099076122045517,0.8831010460853577,-0.4554193317890167,0.04337504878640175,0.10924806445837021,0.8824833035469055,-0.4566374123096466,0.04716096818447113,0.10845884680747986,0.8817566633224487,-0.45810940861701965,0.05085112527012825,0.1070343554019928,0.8809617757797241,-0.45992404222488403,0.054516058415174484,0.10447967052459717,0.880103349685669,-0.46216386556625366,0.05821995809674263,0.10029515624046326,0.8791791200637817,-0.46481853723526,0.06199204549193382,0.09401852637529373,0.8782148361206055,-0.46814119815826416,0.06599937379360199,0.08499433845281601,0.8770768642425537,-0.4721728265285492,0.07065857201814651,0.07252507656812668,0.87567138671875,-0.47684386372566223,0.0755101814866066,0.05749787017703056,0.873849093914032,-0.4815249741077423,0.07991211861371994,0.04109055548906326,0.8718138337135315,-0.4861321747303009,0.08332901448011398,0.024420425295829773,0.8695604801177979,-0.49017876386642456,0.08524955809116364,0.008595280349254608,0.8674004077911377,-0.49366140365600586,0.08521267771720886,-0.0052255732007324696,0.8654536008834839,-0.4968036413192749,0.08317160606384277,-0.01680564694106579,0.8637049198150635,-0.5004303455352783,0.07979419082403183,-0.027883172035217285,0.8616408705711365,-0.5040777921676636,0.07461579889059067,-0.03710956498980522,0.8596283793449402,-0.5070065855979919,0.06802364438772202,-0.04491189122200012,0.8580792546272278,-0.5078763365745544,0.06037482991814613,-0.05150887742638588,0.8577665090560913,-0.5091766715049744,0.050976794213056564,-0.05578646436333656,0.8573379516601562,-0.5105040669441223,0.04020843654870987,-0.058733269572257996,0.8569243550300598,-0.51092129945755,0.02734508365392685,-0.0626610741019249,0.8569045066833496,-0.5112163424491882,0.012441263534128666,-0.06585957854986191,0.8568345904350281,-0.5098105669021606,-0.0021166701335459948,-0.06643547862768173,0.8577150106430054,-0.5063725113868713,-0.018181774765253067,-0.06572315096855164,0.8596143126487732,-0.5019755959510803,-0.035416774451732635,-0.06496832519769669,0.8617106676101685,-0.49738410115242004,-0.053968071937561035,-0.06223311647772789,0.8636108040809631,-0.4928380846977234,-0.0731121301651001,-0.05773923546075821,0.8651193380355835,-0.4886728525161743,-0.09184593707323074,-0.0522427037358284,0.8660449981689453,-0.4845733940601349,-0.10927121341228485,-0.04632101580500603,0.8666619062423706,-0.4808175265789032,-0.1244770735502243,-0.04043881595134735,0.8669975399971008,-0.4775671064853668,-0.13652615249156952,-0.035082362592220306,0.8672136664390564,-0.4747653603553772,-0.14444252848625183,-0.030812634155154228,0.867631733417511,-0.47257694602012634,-0.1472761034965515,-0.02808815985918045,0.8684422373771667,-0.47120821475982666,-0.1438330113887787,-0.02641182765364647,0.8698145747184753,-0.4705122113227844,-0.13434292376041412,-0.024973765015602112,0.8717491626739502,-0.4703601002693176,-0.12001216411590576,-0.02368934452533722,0.8739549517631531,-0.47141948342323303,-0.10215536504983902,-0.022182142361998558,0.8756916522979736,-0.47259679436683655,-0.08184204995632172,-0.020816968753933907,0.8772233724594116,-0.47372639179229736,-0.06028818339109421,-0.0195827204734087,0.8783878684043884,-0.474620521068573,-0.03873132914304733,-0.018447525799274445,0.8791444301605225,-0.4752967059612274,-0.018453218042850494,-0.01727474480867386,0.8794624209403992,-0.4757085144519806,-0.0007162071415223181,-0.01596926897764206,0.8794577717781067,-0.47580164670944214,0.013415532186627388,-0.014830468222498894,0.879325270652771,-0.4752534031867981,0.023800918832421303,-0.01611410453915596,0.8793793320655823,-0.4743832051753998,0.030031073838472366,-0.021408285945653915,0.8795455694198608],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998211860657,1.0000001192092896,1.0000001192092896,0.9999998211860657,1,0.9999998807907104],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,3.0333333015441895],"values":[1.5612528159181238e-7,13.547395706176758,7.71289535350661e-7,9.597192729415838e-7,13.547393798828125,-2.536199303904141e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[-0.30788561701774597,0.014501913450658321,0.00469183037057519,0.9513012766838074,-0.3078855872154236,0.014501920901238918,0.004691829904913902,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998807907104,1.0000001192092896,1,0.9999998807907104,0.9999998807907104,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,3.0333333015441895],"values":[2.833996295928955,7.754294395446777,-6.139588356018066,2.833997964859009,7.754298686981201,-6.139592170715332],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[-0.3320007026195526,-0.09571804106235504,-0.11373621225357056,0.9314922094345093,-0.3320007026195526,-0.09571805596351624,-0.11373622715473175,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998211860657,0.9999998211860657,0.9999998211860657,0.9999998211860657,1,0.9999999403953552],"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,3.0333333015441895],"values":[-2.173761970425403e-7,10.28860092163086,-0.000002567889168858528,-1.740389343751758e-8,10.288605690002441,-0.000002862187329810695],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[-2.207707261447922e-8,-1.4402757209097672e-8,1.3357748684938997e-8,1,-2.204614979461894e-8,-1.4562828276609707e-8,1.3353201211430132e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,1.0000001192092896,1,1,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,3.0333333015441895],"values":[-4.0820864910529053e-7,10.28861141204834,-0.000004069798706041183,-4.723202451373254e-8,10.28860855102539,-0.000004357061698101461],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.027165383100509644,0.005808618385344744,0.0035752379335463047,0.9996076822280884,-0.02738240547478199,0.006037859711796045,0.0036606728099286556,0.9996001124382019,-0.027684083208441734,0.006401752587407827,0.003924407996237278,0.9995885491371155,-0.02797424979507923,0.006813596934080124,0.004317618440836668,0.9995760917663574,-0.028095213696360588,0.007213862147182226,0.004795988090336323,0.9995676875114441,-0.02804253250360489,0.00762284966185689,0.005409877747297287,0.9995630383491516,-0.027903487905859947,0.008051601238548756,0.0061388034373521805,0.9995593428611755,-0.027811199426651,0.008489333093166351,0.006930777803063393,0.9995530843734741,-0.027796920388936996,0.008935738354921341,0.007767762988805771,0.9995434880256653,-0.027856754139065742,0.009392744861543179,0.00862827431410551,0.999530553817749,-0.02797773852944374,0.009868430905044079,0.009536126628518105,0.9995143413543701,-0.028057347983121872,0.010372169315814972,0.010424809530377388,0.9994981288909912,-0.02808818593621254,0.010885014198720455,0.011251544579863548,0.9994828701019287,-0.028142482042312622,0.011422834359109402,0.012077879160642624,0.9994657039642334,-0.028177060186862946,0.011973626911640167,0.012915680184960365,0.999447762966156,-0.02819129452109337,0.01254778541624546,0.013723856769502163,0.9994295239448547,-0.028181077912449837,0.013123857788741589,0.014484094455838203,0.9994117617607117,-0.028144042938947678,0.013707433827221394,0.015206298790872097,0.9993942379951477,-0.0280776247382164,0.014337926171720028,0.01592528261244297,0.9993760585784912,-0.027977295219898224,0.014978122897446156,0.016613418236374855,0.9993582963943481,-0.02783908322453499,0.01558819878846407,0.017242878675460815,0.9993420839309692,-0.02765538915991783,0.016120735555887222,0.01777707412838936,0.9993293881416321,-0.027414321899414062,0.016445612534880638,0.018106652423739433,0.9993248581886292,-0.027112070471048355,0.01675623655319214,0.01837851107120514,0.9993230104446411,-0.02674291469156742,0.016873423010110855,0.01843450963497162,0.9993299245834351,-0.026315120980143547,0.01690184324979782,0.01834944263100624,0.9993423819541931,-0.02583685703575611,0.016858592629432678,0.018138468265533447,0.9993594288825989,-0.02506629005074501,0.016768688336014748,0.017832890152931213,0.9993860721588135,-0.02501177228987217,0.016619374975562096,0.017486339434981346,0.999396026134491,-0.025020811706781387,0.016451269388198853,0.01711101643741131,0.9994050860404968,-0.02506629191339016,0.016262458637356758,0.016712432727217674,0.999413788318634,-0.02517588809132576,0.016016172245144844,0.016304822638630867,0.9994217753410339,-0.025382956489920616,0.015666110441088676,0.01590009406208992,0.9994285702705383,-0.025667831301689148,0.015230506658554077,0.015486827120184898,0.9994345307350159,-0.026010161265730858,0.014711867086589336,0.014999889768660069,0.9994408488273621,-0.02639070525765419,0.014143802225589752,0.014435380697250366,0.9994474053382874,-0.026789238676428795,0.013553210534155369,0.01380071323364973,0.9994539618492126,-0.027189575135707855,0.013025281019508839,0.013198901899158955,0.9994583129882812,-0.02756873518228531,0.012526487931609154,0.012520181015133858,0.9994630813598633,-0.02790975011885166,0.012117307633161545,0.011790814809501171,0.9994674921035767,-0.028205007314682007,0.011810487136244774,0.011048310436308384,0.9994713664054871,-0.028433799743652344,0.011434509418904781,0.010356741957366467,0.9994766116142273,-0.02857808768749237,0.010958664119243622,0.009712106548249722,0.9994843006134033,-0.028621185570955276,0.010413005948066711,0.00911701936274767,0.9994944930076599,-0.02855619788169861,0.009808341041207314,0.00856116134673357,0.9995074272155762,-0.028405947610735893,0.009161354973912239,0.008030966855585575,0.9995222091674805,-0.028184043243527412,0.00848737545311451,0.007519220933318138,0.9995384812355042,-0.02790004201233387,0.00780878821387887,0.007049937732517719,0.9995553493499756,-0.027564184740185738,0.007125325500965118,0.006612598430365324,0.99957275390625,-0.027185112237930298,0.00644484581425786,0.006191355641931295,0.9995905160903931,-0.0267725121229887,0.005772449541836977,0.005782060790807009,0.9996081590652466,-0.0263355765491724,0.005152350291609764,0.005375567823648453,0.9996254444122314,-0.025883981958031654,0.004583778791129589,0.004970845300704241,0.9996420741081238,-0.025425367057323456,0.004120850004255772,0.004577395506203175,0.9996578097343445,-0.02496735192835331,0.0037685518618673086,0.004134898539632559,0.9996726512908936,-0.024517787620425224,0.0035268503706902266,0.0036732524167746305,0.9996864199638367,-0.02408287301659584,0.0033545016776770353,0.0032256152480840683,0.9996991157531738,-0.02367081493139267,0.003217723686248064,0.0027761126402765512,0.9997107982635498,-0.023289935663342476,0.0031215844210237265,0.0023237564601004124,0.9997211694717407,-0.022948073223233223,0.0030746490228921175,0.0018725721165537834,0.9997301697731018,-0.022648885846138,0.0030749677680432796,0.0013925399398431182,0.9997377991676331,-0.022453634068369865,0.0031411147210747004,0.0009817869868129492,0.9997424483299255,-0.022314025089144707,0.003244883380830288,0.0006500414456240833,0.9997455477714539,-0.022181246429681778,0.0033630940597504377,0.00031381906592287123,0.9997482299804688,-0.0220551248639822,0.0035382958594709635,0.000013755862710240763,0.9997504949569702,-0.02193494141101837,0.0037309241015464067,-0.00024811376351863146,0.999752402305603,-0.021834250539541245,0.003928744699805975,-0.0005328101105988026,0.9997537136077881,-0.021848151460289955,0.0041388338431715965,-0.0007882044883444905,0.999752402305603,-0.02195833809673786,0.004356518853455782,-0.0010130044538527727,0.9997488856315613,-0.022080589085817337,0.004537832923233509,-0.0012126490473747253,0.9997451305389404,-0.02222740277647972,0.004669181536883116,-0.001380925183184445,0.9997410774230957,-0.022377140820026398,0.0048143137246370316,-0.001372006256133318,0.9997370839118958,-0.02254015952348709,0.00494431285187602,-0.0011941171251237392,0.999733030796051,-0.022728808224201202,0.004990782588720322,-0.000910663977265358,0.999728798866272,-0.022945422679185867,0.0050195991061627865,-0.0005490472540259361,0.9997239708900452,-0.023188553750514984,0.00503492821007967,-0.00011631500092335045,0.999718427658081,-0.023457631468772888,0.005038351286202669,0.0003560696786735207,0.9997120499610901,-0.02375028096139431,0.005041528958827257,0.0008135161479003727,0.999704897403717,-0.024065375328063965,0.005037242546677589,0.0012296343920752406,0.9996969699859619,-0.024403119459748268,0.005009381100535393,0.0015839345287531614,0.9996883869171143,-0.024752456694841385,0.004963274113833904,0.0018814891809597611,0.9996795654296875,-0.025095971301198006,0.004907715599983931,0.0021340148523449898,0.9996707439422607,-0.025439871475100517,0.004862014204263687,0.0023923201952129602,0.9996616840362549,-0.025792961940169334,0.004857192747294903,0.002588039031252265,0.9996521472930908,-0.02614261955022812,0.00489200372248888,0.0027510407380759716,0.9996424913406372,-0.026378095149993896,0.0049795350059866905,0.0028902010526508093,0.9996354579925537,-0.02653522603213787,0.005114336963742971,0.00301346555352211,0.9996302723884583,-0.026681147515773773,0.005284534767270088,0.003133981954306364,0.9996250867843628,-0.026853054761886597,0.005477055441588163,0.0033193372655659914,0.9996188879013062,-0.027010958641767502,0.005640678573399782,0.0034632585011422634,0.9996132254600525,-0.027165422216057777,0.005808645393699408,0.003575335256755352,0.9996076822280884],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998807907104,0.9999998211860657,1,0.9999998807907104,0.9999997615814209,1],"interpolation":2300,"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,3.0333333015441895],"values":[4.366399765014648,7.562910079956055,-0.17679841816425323,4.366400241851807,7.562911033630371,-0.17679527401924133],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.5728919506072998,0.5138150453567505,-0.42223528027534485,0.47906821966171265,0.5718583464622498,0.5144246816635132,-0.4227946400642395,0.47915560007095337,0.5706309676170349,0.515227198600769,-0.42336392402648926,0.4792538285255432,0.5693596005439758,0.5161662697792053,-0.4238429665565491,0.4793320894241333,0.5680993795394897,0.5172631740570068,-0.4241531491279602,0.47937050461769104,0.5668684244155884,0.5185476541519165,-0.4242814779281616,0.47932642698287964,0.5656474828720093,0.5199548006057739,-0.42429473996162415,0.47923269867897034,0.5643964409828186,0.5213913917541504,-0.42429274320602417,0.4791485369205475,0.5631185173988342,0.5228385329246521,-0.4242980182170868,0.4790705740451813,0.5618252754211426,0.5243015885353088,-0.42431125044822693,0.4789782762527466,0.5605357885360718,0.5257552266120911,-0.42434147000312805,0.47886887192726135,0.5592615008354187,0.5272531509399414,-0.42435652017593384,0.4786985516548157,0.5582388043403625,0.52860426902771,-0.4243687391281128,0.47839125990867615,0.5573717951774597,0.529809832572937,-0.4244363009929657,0.4780084788799286,0.5565810799598694,0.531014084815979,-0.42453739047050476,0.4775035083293915,0.5558134913444519,0.5322474837303162,-0.4246862232685089,0.4768916368484497,0.5550877451896667,0.5334843397140503,-0.42487025260925293,0.4761905372142792,0.5544136166572571,0.534706711769104,-0.4250786006450653,0.4754182994365692,0.5538010597229004,0.5358970165252686,-0.4253006875514984,0.47459256649017334,0.5532609820365906,0.5370378494262695,-0.42552587389945984,0.4737301766872406,0.5528101325035095,0.5381093621253967,-0.42574065923690796,0.47284695506095886,0.5524864196777344,0.5390816926956177,-0.42592477798461914,0.4719509780406952,0.5523328185081482,0.5399237275123596,-0.42605599761009216,0.47104892134666443,0.5523953437805176,0.5406028032302856,-0.4261104166507721,0.47014665603637695,0.552669107913971,0.5411098003387451,-0.42608168721199036,0.46926695108413696,0.5531020164489746,0.5414558053016663,-0.4259812831878662,0.468448281288147,0.5536808371543884,0.5416271686553955,-0.42581066489219666,0.4677209258079529,0.5545005202293396,0.5417745113372803,-0.4253986179828644,0.4669535458087921,0.5549728870391846,0.5413919687271118,-0.4253939092159271,0.4668402373790741,0.5554822087287903,0.5408190488815308,-0.42537611722946167,0.4669148027896881,0.5560399293899536,0.5400627255439758,-0.4253193140029907,0.46717798709869385,0.5566481351852417,0.5390201210975647,-0.42518892884254456,0.4677760601043701,0.5573095083236694,0.5375685691833496,-0.42494985461235046,0.46887511014938354,0.5580099821090698,0.5357829332351685,-0.42463573813438416,0.47036805748939514,0.5587339401245117,0.5337492227554321,-0.42428308725357056,0.47213563323020935,0.5594664812088013,0.5315524339675903,-0.4239276051521301,0.47406190633773804,0.5601938366889954,0.5292784571647644,-0.42360547184944153,0.4760310649871826,0.5609040856361389,0.5270133018493652,-0.4233526587486267,0.47792893648147583,0.5615864992141724,0.5248443484306335,-0.42320334911346436,0.4796436131000519,0.5622003078460693,0.5228844285011292,-0.42324021458625793,0.4810306429862976,0.5627662539482117,0.5211487412452698,-0.42342081665992737,0.48209232091903687,0.5635837316513062,0.519386887550354,-0.4232509434223175,0.48318663239479065,0.5647127628326416,0.5175977349281311,-0.42264240980148315,0.4843197166919708,0.5661510825157166,0.5158445835113525,-0.42161765694618225,0.48540282249450684,0.5678851008415222,0.5141682028770447,-0.42020729184150696,0.48637789487838745,0.5698438882827759,0.5125559568405151,-0.41850364208221436,0.48725664615631104,0.5719754099845886,0.5110122561454773,-0.4165799915790558,0.48802849650382996,0.5742337703704834,0.5095492601394653,-0.4145018458366394,0.488675057888031,0.5765752792358398,0.5081804394721985,-0.41233474016189575,0.48917660117149353,0.5789536833763123,0.5069209337234497,-0.41014528274536133,0.4895147681236267,0.5813246965408325,0.5057870745658875,-0.4079994857311249,0.4896707236766815,0.5836427211761475,0.5047975182533264,-0.4059639275074005,0.48962637782096863,0.5858633518218994,0.5039716958999634,-0.4041041433811188,0.4893634021282196,0.5879479050636292,0.503328263759613,-0.4024868607521057,0.48885804414749146,0.5898860096931458,0.502853512763977,-0.40119925141334534,0.4880698025226593,0.5916048288345337,0.5026044845581055,-0.40023499727249146,0.4870363473892212,0.5931088924407959,0.5025449991226196,-0.3994300663471222,0.4859280288219452,0.5944147109985352,0.5026233196258545,-0.3987736999988556,0.484789103269577,0.5955166220664978,0.5028154253959656,-0.3982785642147064,0.4836432933807373,0.5964261293411255,0.5030767321586609,-0.39797136187553406,0.48250237107276917,0.5971507430076599,0.5033841729164124,-0.3978584408760071,0.4813773036003113,0.5975805521011353,0.5036383867263794,-0.3980473577976227,0.480420857667923,0.597813606262207,0.5039105415344238,-0.39843007922172546,0.4795273244380951,0.5979464054107666,0.5042707920074463,-0.39889928698539734,0.4785921573638916,0.5979865789413452,0.5047155618667603,-0.3994404971599579,0.4776207208633423,0.597939670085907,0.5052395462989807,-0.4000413715839386,0.47662144899368286,0.597781777381897,0.5058135986328125,-0.4007205069065094,0.4756391644477844,0.5973389148712158,0.5062799453735352,-0.4016582667827606,0.47490790486335754,0.5966479778289795,0.5066695213317871,-0.4028140902519226,0.4743817150592804,0.5959241390228271,0.5070434808731079,-0.4039190709590912,0.4739522337913513,0.5951643586158752,0.5073754191398621,-0.4049561619758606,0.4736666679382324,0.5943450331687927,0.5076606869697571,-0.4060341417789459,0.4734669029712677,0.5935243368148804,0.5078585743904114,-0.40714743733406067,0.4733281433582306,0.5926782488822937,0.508019745349884,-0.4082820415496826,0.4732379615306854,0.5917794108390808,0.5081743001937866,-0.409422904253006,0.4732113182544708,0.5908127427101135,0.5083451271057129,-0.41054773330688477,0.47326117753982544,0.5897783041000366,0.5085373520851135,-0.41164928674697876,0.47338804602622986,0.5886770486831665,0.508755624294281,-0.4127202033996582,0.47359177470207214,0.5875090956687927,0.5090053677558899,-0.41375288367271423,0.473872572183609,0.5862758755683899,0.509292483329773,-0.41473856568336487,0.4742295742034912,0.5849478244781494,0.5096367597579956,-0.41566428542137146,0.4746890664100647,0.5835068225860596,0.5100285410881042,-0.4165476858615875,0.47526705265045166,0.5819887518882751,0.5104468464851379,-0.4173981547355652,0.4759327173233032,0.5804329514503479,0.5108722448348999,-0.41822168231010437,0.4766526520252228,0.5788941979408264,0.5112963318824768,-0.4190111756324768,0.4773753881454468,0.5774839520454407,0.5117605328559875,-0.4197005033493042,0.4779801368713379,0.576230525970459,0.5122310519218445,-0.4203135073184967,0.4784497916698456,0.5751237869262695,0.5126683115959167,-0.42089277505874634,0.478803813457489,0.5742930769920349,0.5130431652069092,-0.4213712513446808,0.4789786636829376,0.5736490488052368,0.5133896470069885,-0.4217730164527893,0.4790254235267639,0.5728917717933655,0.51381516456604,-0.4222352206707001,0.4790683686733246],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999997615814209,0.9999998807907104,0.9999999403953552,0.9999996423721313,0.9999997615814209,0.9999997019767761],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,3.0333333015441895],"values":[-0.0000034173276617366355,17.630794525146484,-0.0000020107147520320723,-0.000008189549589587841,17.630794525146484,4.1502121916892065e-7],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.0018584482604637742,-0.1948564201593399,0.2557886242866516,0.9468895196914673,-0.0010295690735802054,-0.19500362873077393,0.25052711367607117,0.9482661485671997,0.00011556167737580836,-0.1949017494916916,0.2443685680627823,0.9498933553695679,0.0015556860016658902,-0.19455751776695251,0.23759043216705322,0.9516805410385132,0.0032814019359648228,-0.19405819475650787,0.23046927154064178,0.9535274505615234,0.0052042570896446705,-0.19358877837657928,0.22265812754631042,0.9554682970046997,0.00725954957306385,-0.19300667941570282,0.21439355611801147,0.9574608206748962,0.009402461349964142,-0.1920793503522873,0.20628225803375244,0.9594085812568665,0.011539296247065067,-0.1908971518278122,0.1983477622270584,0.961292564868927,0.01363755390048027,-0.1894460767507553,0.19077247381210327,0.9630836248397827,0.015603926964104176,-0.1878674328327179,0.1835906058549881,0.9647574424743652,0.017411213368177414,-0.18641121685504913,0.1764269322156906,0.9663442969322205,0.018747352063655853,-0.185395747423172,0.16989968717098236,0.9676833748817444,0.01948516257107258,-0.1846780627965927,0.163641557097435,0.9688838124275208,0.01995738036930561,-0.184145987033844,0.15831156075000763,0.9698604941368103,0.02053440362215042,-0.18363870680332184,0.15431606769561768,0.9705883264541626,0.021160151809453964,-0.18317772448062897,0.15122246742248535,0.9711487889289856,0.02163488231599331,-0.1828296035528183,0.14872780442237854,0.9715890884399414,0.021800437942147255,-0.18263234198093414,0.14672811329364777,0.9719264507293701,0.022135505452752113,-0.18242987990379333,0.14538410305976868,0.9721588492393494,0.022552303969860077,-0.1820680797100067,0.1446400284767151,0.9723281264305115,0.022883331403136253,-0.18184566497802734,0.1443491131067276,0.972405195236206,0.02315814048051834,-0.18185649812221527,0.14447516202926636,0.9723779559135437,0.023381363600492477,-0.18188835680484772,0.14520545303821564,0.9722578525543213,0.023646235466003418,-0.1818208545446396,0.146256685256958,0.9721065163612366,0.024049337953329086,-0.18160311877727509,0.14758239686489105,0.9719369411468506,0.02421415038406849,-0.18125474452972412,0.14916269481182098,0.9717565774917603,0.024086326360702515,-0.18110965192317963,0.15071861445903778,0.9715467095375061,0.024396365508437157,-0.18036045134067535,0.15226025879383087,0.9714379906654358,0.024936171248555183,-0.17952975630760193,0.1537231206893921,0.9713477492332458,0.025589944794774055,-0.1785958856344223,0.15517687797546387,0.9712717533111572,0.026282241567969322,-0.17739084362983704,0.15649622678756714,0.9712624549865723,0.027100373059511185,-0.17586618661880493,0.15748323500156403,0.9713577032089233,0.028266694396734238,-0.17396661639213562,0.15867650508880615,0.9714722633361816,0.029426252469420433,-0.17183063924312592,0.16026146709918976,0.9715577960014343,0.030402306467294693,-0.1696113646030426,0.16226716339588165,0.9715847969055176,0.03111133724451065,-0.16743288934230804,0.16456936299800873,0.9715530276298523,0.031552691012620926,-0.16534487903118134,0.16718266904354095,0.9714502096176147,0.03173639997839928,-0.1633698046207428,0.17025750875473022,0.9712443351745605,0.03165639191865921,-0.1613549143075943,0.1742611527442932,0.9708735942840576,0.031426362693309784,-0.1591225266456604,0.17917762696743011,0.9703544974327087,0.031268294900655746,-0.15725770592689514,0.18344971537590027,0.9698652029037476,0.031173599883913994,-0.1557011902332306,0.18646308779716492,0.9695446491241455,0.031080057844519615,-0.15470576286315918,0.1896357238292694,0.9690915942192078,0.030979644507169724,-0.153983011841774,0.19250185787677765,0.9686446785926819,0.030818063765764236,-0.15347300469875336,0.19555461406707764,0.9681191444396973,0.03054298833012581,-0.15313170850276947,0.19896316528320312,0.9674872159957886,0.03011479601264,-0.152927428483963,0.20279693603515625,0.9667366147041321,0.029510406777262688,-0.15282653272151947,0.20709462463855743,0.9658597707748413,0.028787631541490555,-0.15272560715675354,0.21171142160892487,0.9648960828781128,0.028004009276628494,-0.15254753828048706,0.21659225225448608,0.9638634920120239,0.027033770456910133,-0.1522194892168045,0.221469447016716,0.9628341794013977,0.026240894570946693,-0.1519855558872223,0.22667650878429413,0.9616805911064148,0.025293760001659393,-0.1515989452600479,0.23238606750965118,0.9604034423828125,0.0243387334048748,-0.15124212205410004,0.23773247003555298,0.9591750502586365,0.023488061502575874,-0.15077786147594452,0.24273531138896942,0.9580156803131104,0.02276734635233879,-0.15007364749908447,0.24759311974048615,0.9568998217582703,0.022162053734064102,-0.14920948445796967,0.2521953880786896,0.9558467268943787,0.021610403433442116,-0.14833799004554749,0.2565861642360687,0.9548258185386658,0.021064238622784615,-0.14748892188072205,0.2609238922595978,0.9537935256958008,0.02050935849547386,-0.14680981636047363,0.26501336693763733,0.9527823328971863,0.0200983677059412,-0.14593344926834106,0.2689305543899536,0.9518276453018188,0.01970352604985237,-0.14516916871070862,0.27269068360328674,0.9508824944496155,0.0191680658608675,-0.14486747980117798,0.2762618064880371,0.9499081373214722,0.018436817452311516,-0.14510081708431244,0.2796521484851837,0.9488943815231323,0.017506876960396767,-0.14587073028087616,0.2828565537929535,0.947843611240387,0.016429493203759193,-0.14699827134609222,0.2859635055065155,0.9467557668685913,0.01536310650408268,-0.1479325294494629,0.28921863436698914,0.9456387162208557,0.014264507219195366,-0.14883960783481598,0.292513370513916,0.9444994330406189,0.01303735189139843,-0.15028715133666992,0.2953073978424072,0.9434179067611694,0.011724823154509068,-0.1522040069103241,0.2975119948387146,0.9424346685409546,0.01038524229079485,-0.15463940799236298,0.2992137670516968,0.9415147304534912,0.00896556954830885,-0.1576090306043625,0.30034947395324707,0.940674901008606,0.007440553512424231,-0.16100844740867615,0.3009531497955322,0.9399192333221436,0.0058547379449009895,-0.1647116243839264,0.30103158950805664,0.9392634630203247,0.004278372507542372,-0.16857801377773285,0.30058491230010986,0.9387288689613342,0.0027383300475776196,-0.17248958349227905,0.2996698021888733,0.9383165240287781,0.0012615432497113943,-0.17633076012134552,0.2983497977256775,0.9380263090133667,-0.0001265853934455663,-0.17998933792114258,0.2966848611831665,0.9378603100776672,-0.0014042826369404793,-0.18336406350135803,0.2947312891483307,0.9378215074539185,-0.0024738688953220844,-0.18627794086933136,0.2924952507019043,0.9379450678825378,-0.003278838936239481,-0.1886344999074936,0.28999364376068115,0.938248336315155,-0.0037736936938017607,-0.19036291539669037,0.2872309684753418,0.9387471079826355,-0.0038006361573934555,-0.1915847659111023,0.2832615375518799,0.9397040605545044,-0.003626312827691436,-0.19233766198158264,0.27921223640441895,0.9407622218132019,-0.003342648735269904,-0.19292394816875458,0.27510571479797363,0.9418524503707886,-0.002991768531501293,-0.19334784150123596,0.2710568904876709,0.9429399967193604,-0.0026331725530326366,-0.1936328262090683,0.2671818137168884,0.9439879655838013,-0.002340179169550538,-0.19396309554576874,0.2634929120540619,0.9449573755264282,-0.002096545184031129,-0.19442398846149445,0.25977402925491333,0.9458923935890198,-0.0020354760345071554,-0.1947297304868698,0.2557152807712555,0.9469349980354309],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,1.0000001192092896,0.9999998807907104,1.0000001192092896,1.000000238418579,1],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,3.0333333015441895],"values":[2.352830676954909e-7,17.82779884338379,-0.000018769809685181826,-0.000004593372523231665,17.827802658081055,-0.000011387212907720823],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.11910507082939148,-0.09258092194795609,0.6554743647575378,0.739997386932373,-0.1187218576669693,-0.09281785786151886,0.653366208076477,0.7418912649154663,-0.11825558543205261,-0.09310445189476013,0.6508001685142517,0.7441816926002502,-0.11775101721286774,-0.09341224282979965,0.6480231285095215,0.7466424107551575,-0.11730290204286575,-0.09368383884429932,0.6455562114715576,0.7488127946853638,-0.11684482544660568,-0.09395932406187057,0.6430358290672302,0.7510152459144592,-0.1163128986954689,-0.09427701681852341,0.640108585357666,0.7535543441772461,-0.11570341885089874,-0.09463799744844437,0.6367542743682861,0.7564393281936646,-0.11498454213142395,-0.09505944699048996,0.6327981352806091,0.759808361530304,-0.11415623873472214,-0.09553961455821991,0.6282394528388977,0.7636463642120361,-0.11321442574262619,-0.09607840329408646,0.6230571866035461,0.7679526209831238,-0.11212194710969925,-0.09669429063796997,0.6170438528060913,0.7728750705718994,-0.11092163622379303,-0.09735941886901855,0.6104381680488586,0.7781919836997986,-0.10932613909244537,-0.09822570532560349,0.6016584038734436,0.7851157188415527,-0.10771434754133224,-0.09908066689968109,0.5927878022193909,0.7919490337371826,-0.10649998486042023,-0.09971150755882263,0.5861048102378845,0.7969921827316284,-0.10555431246757507,-0.10019519925117493,0.5809012651443481,0.8008576035499573,-0.1046348586678505,-0.10065918415784836,0.5758407711982727,0.8045661449432373,-0.10357388854026794,-0.10118690878152847,0.5700011849403381,0.8087844848632812,-0.10288329422473907,-0.10152595490217209,0.5662010312080383,0.8114948868751526,-0.10234858095645905,-0.1017862856388092,0.5632579922676086,0.813575267791748,-0.10195443779230118,-0.10197670757770538,0.5610895156860352,0.8150979280471802,-0.10182105749845505,-0.10204096138477325,0.5603556632995605,0.8156112432479858,-0.10170851647853851,-0.10209508240222931,0.5597359538078308,0.8160439133644104,-0.10184591263532639,-0.10202910006046295,0.560491681098938,0.8155161738395691,-0.10212484747171402,-0.10189454257488251,0.5620267391204834,0.8144409656524658,-0.10199024528265,-0.10195955634117126,0.561286449432373,0.8149600028991699,-0.10157892853021622,-0.10215730220079422,0.5590229630470276,0.8165408372879028,-0.10168702155351639,-0.10210544615983963,0.5596175193786621,0.8161265254020691,-0.10230416059494019,-0.10180775076150894,0.563014030456543,0.8137470483779907,-0.10322721302509308,-0.10135745257139206,0.5680937767028809,0.810148298740387,-0.10422689467668533,-0.1008630245923996,0.5735949277877808,0.8061962127685547,-0.10543252527713776,-0.10025697946548462,0.5802306532859802,0.8013519644737244,-0.10712189227342606,-0.09938976168632507,0.5895269513130188,0.7944208979606628,-0.10890519618988037,-0.09845095872879028,0.5993411540985107,0.7869163155555725,-0.11053073406219482,-0.09757354110479355,0.6082872748374939,0.7799031734466553,-0.11182814091444016,-0.09685809910297394,0.6154280304908752,0.7741843461990356,-0.11282312124967575,-0.0963001549243927,0.6209033131599426,0.7697247862815857,-0.11359497904777527,-0.09586157649755478,0.6251514554023743,0.7662196159362793,-0.11422319710254669,-0.09550096094608307,0.628608226776123,0.7633376717567444,-0.11472465842962265,-0.09521079063415527,0.6313679814338684,0.7610175609588623,-0.11527375131845474,-0.09489040821790695,0.6343897581100464,0.7584573030471802,-0.11575411260128021,-0.09460808336734772,0.6370334625244141,0.7562001943588257,-0.11663161218166351,-0.09408707916736603,0.6418622136116028,0.7520356774330139,-0.11759989708662033,-0.09350410103797913,0.647191047668457,0.7473760843276978,-0.11869076639413834,-0.09283718466758728,0.6531945466995239,0.7420449256896973,-0.11984938383102417,-0.0921165943145752,0.6595709919929504,0.7362858057022095,-0.12101626396179199,-0.09137798100709915,0.6659929156303406,0.73038250207901,-0.12214421480894089,-0.09065145254135132,0.672200083732605,0.7245758771896362,-0.12316585332155228,-0.0899825170636177,0.6778231859207153,0.7192281484603882,-0.12405312806367874,-0.08939296007156372,0.6827051043510437,0.7145162224769592,-0.12478366494178772,-0.08890143036842346,0.6867262125015259,0.7105861902236938,-0.1253785789012909,-0.08849679678678513,0.6900002956390381,0.7073529362678528,-0.12585090100765228,-0.08817292004823685,0.6925999522209167,0.7047640681266785,-0.1261305958032608,-0.08798002451658249,0.694138765335083,0.7032225728034973,-0.12623409926891327,-0.087908536195755,0.6947082877159119,0.7026503682136536,-0.12624602019786835,-0.08790022134780884,0.6947742700576782,0.7025840282440186,-0.12618522346019745,-0.08794232457876205,0.6944389939308167,0.7029210925102234,-0.12607452273368835,-0.0880187526345253,0.6938299536705017,0.7035324573516846,-0.12592650949954987,-0.08812101185321808,0.6930155754089355,0.7043483853340149,-0.1257484406232834,-0.08824340254068375,0.6920357346534729,0.7053276300430298,-0.12554675340652466,-0.08838174492120743,0.6909255981445312,0.7064335942268372,-0.12534482777118683,-0.08851996809244156,0.689814031124115,0.7075377106666565,-0.125153049826622,-0.08865048736333847,0.6887590885162354,0.7085822224617004,-0.12497664242982864,-0.08877040445804596,0.6877883672714233,0.709540605545044,-0.12482229620218277,-0.08887512981891632,0.6869386434555054,0.7103773355484009,-0.12467791885137558,-0.08897282183170319,0.6861440539360046,0.7111579775810242,-0.12447882443666458,-0.08910709619522095,0.6850486397743225,0.7122312188148499,-0.12424241006374359,-0.08926611393690109,0.6837475299835205,0.7135017514228821,-0.12405665963888168,-0.08939068764448166,0.6827253699302673,0.7144966125488281,-0.12391652911901474,-0.08948418498039246,0.6819545030593872,0.7152450084686279,-0.12380725145339966,-0.08955712616443634,0.6813521981239319,0.7158284783363342,-0.12372589111328125,-0.0896112397313118,0.6809049248695374,0.7162612676620483,-0.12366005778312683,-0.08965512365102768,0.6805424690246582,0.7166115641593933,-0.12360238283872604,-0.08969353884458542,0.6802248954772949,0.7169181704521179,-0.1235404834151268,-0.08973454684019089,0.6798840165138245,0.717246949672699,-0.12346401065587997,-0.08978530764579773,0.6794640421867371,0.7176516652107239,-0.12336540222167969,-0.08985066413879395,0.6789208650588989,0.7181743383407593,-0.12323811650276184,-0.08993477374315262,0.6782203316688538,0.7188471555709839,-0.12307807803153992,-0.09004046022891998,0.6773393750190735,0.7196915149688721,-0.1228799968957901,-0.09017077833414078,0.6762493252754211,0.7207334041595459,-0.12264449894428253,-0.0903252586722374,0.6749526858329773,0.7219685316085815,-0.12237600982189178,-0.09050064533948898,0.6734762191772461,0.7233695387840271,-0.12199927866458893,-0.09074568003416061,0.6714023947715759,0.725327730178833,-0.12158825248479843,-0.09101123362779617,0.6691400408744812,0.727450966835022,-0.121190644800663,-0.09126656502485275,0.6669526100158691,0.7294912338256836,-0.12079712003469467,-0.09151770174503326,0.6647869944572449,0.7314990758895874,-0.12038969993591309,-0.09177626669406891,0.6625444293022156,0.7335654497146606,-0.11998700350522995,-0.09203013777732849,0.6603280901908875,0.7355952858924866,-0.11958292126655579,-0.09228331595659256,0.658104658126831,0.737619161605835,-0.11910632252693176,-0.09258013963699341,0.6554818153381348,0.7399907112121582],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998211860657,1,1,0.9999999403953552,1,0.9999997019767761],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,3.0333333015441895],"values":[5.583569304690172e-7,18.751102447509766,-0.000006984762421780033,-0.0000030523619898303878,18.751096725463867,0.000005491619504027767],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[0.055037710815668106,0.05196233466267586,-0.01394517719745636,0.9970337748527527,0.055037714540958405,0.05196233466267586,-0.01394519954919815,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999999403953552,1.0000001192092896,1,0.9999999403953552,1.0000001192092896,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,3.0333333015441895],"values":[-4.513199329376221,8.24639892578125,-0.08129705488681793,-4.513199329376221,8.246408462524414,-0.08129362016916275],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.5019245743751526,-0.5723239183425903,0.4897362291812897,0.42505931854248047,0.5025429129600525,-0.5712893009185791,0.4898368716239929,0.42560431361198425,0.5033561587333679,-0.570061981678009,0.489951491355896,0.4261566698551178,0.504306435585022,-0.5687937140464783,0.49004673957824707,0.42661812901496887,0.5054149031639099,-0.5675401091575623,0.4901026487350464,0.42691147327423096,0.5067113041877747,-0.5663204193115234,0.49007633328437805,0.4270246624946594,0.5081311464309692,-0.5651131272315979,0.49000120162963867,0.4270232617855072,0.5095811486244202,-0.5638758540153503,0.489935964345932,0.4270056188106537,0.5110415816307068,-0.5626118779182434,0.48987722396850586,0.4269949495792389,0.5125179290771484,-0.5613330602645874,0.48980408906936646,0.4269924461841583,0.513985276222229,-0.560057520866394,0.48971399664878845,0.4270069897174835,0.5154975056648254,-0.5587977170944214,0.4895634055137634,0.4270072281360626,0.5168622136116028,-0.5577881336212158,0.48927387595176697,0.4270094037055969,0.5180809497833252,-0.5569320917129517,0.4889075458049774,0.42706936597824097,0.5192996263504028,-0.5561513304710388,0.4884197413921356,0.42716485261917114,0.5205480456352234,-0.5553938150405884,0.4878253936767578,0.4273099899291992,0.5218009948730469,-0.5546774864196777,0.48714277148246765,0.42749109864234924,0.523039698600769,-0.554012656211853,0.48638835549354553,0.42769843339920044,0.5242465734481812,-0.5534089803695679,0.48558032512664795,0.42792049050331116,0.5254037976264954,-0.5528767108917236,0.48473528027534485,0.4281471073627472,0.5264908671379089,-0.5524333715438843,0.483868271112442,0.4283646047115326,0.5274779796600342,-0.5521168112754822,0.482986718416214,0.4285532534122467,0.5283334851264954,-0.5519692897796631,0.4820973575115204,0.42869070172309875,0.5290235280990601,-0.5520374774932861,0.48120471835136414,0.42875489592552185,0.5295388102531433,-0.5523167848587036,0.48033106327056885,0.4287388026714325,0.5298915505409241,-0.5527541041374207,0.47951608896255493,0.42865175008773804,0.5300666689872742,-0.5533368587493896,0.47878918051719666,0.4284956753253937,0.5302159786224365,-0.5541629195213318,0.47801998257637024,0.4281020760536194,0.5298307538032532,-0.5546315908432007,0.4779016077518463,0.42810437083244324,0.529251754283905,-0.5551356673240662,0.4779684245586395,0.4280925691127777,0.5284868478775024,-0.5556867122650146,0.47822117805480957,0.42804038524627686,0.5274308323860168,-0.5562862753868103,0.47880440950393677,0.42791199684143066,0.5259584188461304,-0.5569370985031128,0.47988161444664,0.4276709258556366,0.5241469144821167,-0.557624340057373,0.4813482165336609,0.4273512065410614,0.5220837593078613,-0.5583329796791077,0.48308584094047546,0.42699065804481506,0.5198549628257751,-0.5590488910675049,0.48497965931892395,0.4266262352466583,0.5175485014915466,-0.559758722782135,0.4869156777858734,0.42629435658454895,0.5152524709701538,-0.5604500770568848,0.488781601190567,0.42603206634521484,0.5130543112754822,-0.5611140727996826,0.4904657304286957,0.4258752167224884,0.511070966720581,-0.5617081522941589,0.49182742834091187,0.42590633034706116,0.5093165040016174,-0.562254786491394,0.4928685128688812,0.4260833263397217,0.5075321793556213,-0.5630578398704529,0.49393752217292786,0.42591410875320435,0.5057176351547241,-0.5641775131225586,0.4950410723686218,0.4253091514110565,0.5039364695549011,-0.5656124949455261,0.4960908889770508,0.42429280281066895,0.5022314190864563,-0.567348062992096,0.4970305263996124,0.4228954613208771,0.5005888938903809,-0.5693135857582092,0.49787136912345886,0.421208918094635,0.4990151524543762,-0.5714545249938965,0.49860456585884094,0.41930556297302246,0.49752211570739746,-0.5737258791923523,0.49921146035194397,0.41725093126296997,0.4961243271827698,-0.576082170009613,0.4996738135814667,0.41510969400405884,0.49483776092529297,-0.57847660779953,0.49997419118881226,0.41294825077056885,0.4936792552471161,-0.5808645486831665,0.5000941157341003,0.4108322262763977,0.49266818165779114,-0.5831992626190186,0.5000171661376953,0.4088270664215088,0.49182450771331787,-0.5854358077049255,0.49972498416900635,0.4069993793964386,0.491167813539505,-0.5875349044799805,0.4991956353187561,0.4054141342639923,0.4906846582889557,-0.5894848704338074,0.4983886480331421,0.4041595757007599,0.49043264985084534,-0.591213583946228,0.4973425269126892,0.4032278060913086,0.49037328362464905,-0.59272700548172,0.496226042509079,0.40245306491851807,0.49045389890670776,-0.5940420031547546,0.4950816035270691,0.40182504057884216,0.4906502068042755,-0.5951518416404724,0.4939337968826294,0.4013552963733673,0.4909176230430603,-0.5960676074028015,0.4927932620048523,0.4010712802410126,0.49123263359069824,-0.5967965722084045,0.49167123436927795,0.40097853541374207,0.49149513244628906,-0.5972263813018799,0.49071985483169556,0.40118226408958435,0.4917767643928528,-0.597456693649292,0.48983410000801086,0.40157660841941833,0.4921475946903229,-0.597586989402771,0.48890793323516846,0.4020567238330841,0.49260446429252625,-0.5976244211196899,0.48794758319854736,0.40260782837867737,0.49314212799072266,-0.5975744128227234,0.4869612753391266,0.40321752429008484,0.4937300980091095,-0.5974134802818298,0.48599299788475037,0.4039042294025421,0.4942101538181305,-0.5969628691673279,0.48527684807777405,0.404843270778656,0.49461281299591064,-0.5962609648704529,0.48476696014404297,0.40599527955055237,0.4949994385242462,-0.5955260992050171,0.48435330390930176,0.4070948362350464,0.4953424334526062,-0.5947561264038086,0.4840821921825409,0.4081243574619293,0.4956381320953369,-0.5939252972602844,0.483896940946579,0.4091935455799103,0.4958454668521881,-0.5930923819541931,0.483771413564682,0.41029736399650574,0.4960160553455353,-0.5922331213951111,0.4836944341659546,0.4114215672016144,0.496179461479187,-0.5913209319114685,0.48368099331855774,0.4125509262084961,0.49635881185531616,-0.5903413891792297,0.4837440252304077,0.4136626720428467,0.4965594410896301,-0.58929443359375,0.4838843047618866,0.4147491157054901,0.496785432100296,-0.5881816148757935,0.4841011166572571,0.4158037006855011,0.49704280495643616,-0.587002694606781,0.48439526557922363,0.4168183505535126,0.49733689427375793,-0.5857597589492798,0.48476579785346985,0.41778427362442017,0.497687965631485,-0.5844232439994812,0.4852384924888611,0.4186881482601166,0.4980858564376831,-0.582974910736084,0.48583003878593445,0.41954731941223145,0.4985106885433197,-0.5814495086669922,0.48650988936424255,0.42037099599838257,0.49894124269485474,-0.5798876285552979,0.487243115901947,0.42116767168045044,0.4993705451488495,-0.5783430337905884,0.48797905445098877,0.4219299554824829,0.4998404085636139,-0.5769284963607788,0.48859652876853943,0.42259499430656433,0.5003167986869812,-0.5756718516349792,0.4890783131122589,0.423187255859375,0.5007602572441101,-0.5745617747306824,0.4894438087940216,0.42374855279922485,0.5011404156684875,-0.5737288594245911,0.48962798714637756,0.42421436309814453,0.5014925003051758,-0.5730831027030945,0.48968321084976196,0.4246074855327606,0.5019246339797974,-0.5723239183425903,0.4897362291812897,0.42505931854248047],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,0.9999999403953552,1,0.9999998807907104,0.9999999403953552,0.9999999403953552],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,3.0333333015441895],"values":[-0.000015234800230246037,17.92140007019043,0.000011828984497697093,-0.000007210742751340149,17.921398162841797,-0.000014469202142208815],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[0.09305327385663986,0.11809612065553665,-0.16182370483875275,0.9752986431121826,0.09029214829206467,0.12079726159572601,-0.15979304909706116,0.9755621552467346,0.08765388280153275,0.12353523075580597,-0.1587696671485901,0.9756270051002502,0.08496465533971786,0.12626805901527405,-0.15876148641109467,0.9755163788795471,0.08192650228738785,0.12935082614421844,-0.15955139696598053,0.9752434492111206,0.07848448306322098,0.13272707164287567,-0.16108545660972595,0.974820613861084,0.07481896132230759,0.13610947132110596,-0.16331227123737335,0.9742717742919922,0.0708480030298233,0.13902752101421356,-0.16636492311954498,0.9736399054527283,0.06696512550115585,0.1416013538837433,-0.16984741389751434,0.9729422926902771,0.0630129873752594,0.14385506510734558,-0.17377300560474396,0.9721821546554565,0.05822472646832466,0.14613421261310577,-0.17963577806949615,0.9710745215415955,0.053333643823862076,0.1484842598438263,-0.18602149188518524,0.969795823097229,0.04875526204705238,0.15064170956611633,-0.19259151816368103,0.9684206247329712,0.0444568507373333,0.1525128185749054,-0.1992652267217636,0.9669833779335022,0.04019587114453316,0.15441808104515076,-0.20589497685432434,0.9654774069786072,0.03698699176311493,0.15580818057060242,-0.21165166795253754,0.9641366004943848,0.03467004746198654,0.15691475570201874,-0.21696923673152924,0.9628604054450989,0.03310820832848549,0.15769760310649872,-0.22191378474235535,0.961659848690033,0.033011727035045624,0.15791338682174683,-0.22595146298408508,0.9606870412826538,0.03398728743195534,0.15803495049476624,-0.22878898680210114,0.9599611759185791,0.036096930503845215,0.15791679918766022,-0.23076511919498444,0.959430456161499,0.03837941586971283,0.15761315822601318,-0.23315328359603882,0.9588142037391663,0.040837980806827545,0.15740767121315002,-0.23608852922916412,0.9580278396606445,0.043562501668930054,0.15700775384902954,-0.23921677470207214,0.9571970701217651,0.04634210839867592,0.15640789270401,-0.24266457557678223,0.956296443939209,0.04948892444372177,0.15571343898773193,-0.24585624039173126,0.9554365277290344,0.0533512607216835,0.15485501289367676,-0.24812035262584686,0.9547826647758484,0.05731390416622162,0.15405496954917908,-0.25009578466415405,0.9541667699813843,0.061928946524858475,0.15140382945537567,-0.25332093238830566,0.9534517526626587,0.06634752452373505,0.14830392599105835,-0.2576406002044678,0.9524837732315063,0.07041493058204651,0.14472930133342743,-0.2632162570953369,0.9512162804603577,0.0739436000585556,0.14063528180122375,-0.2703780233860016,0.9495524168014526,0.07690229266881943,0.1359107345342636,-0.27979227900505066,0.9472754001617432,0.07931551337242126,0.13089822232723236,-0.2906644940376282,0.9445045590400696,0.08141163736581802,0.12577247619628906,-0.30209314823150635,0.94143146276474,0.08319780230522156,0.12091194838285446,-0.3136995732784271,0.9381103515625,0.08479982614517212,0.11596840620040894,-0.32566288113594055,0.9345073699951172,0.08626694232225418,0.1111566424369812,-0.33723098039627075,0.930847704410553,0.08765630424022675,0.10633600503206253,-0.3483501076698303,0.9271791577339172,0.08894297480583191,0.10140931606292725,-0.35908928513526917,0.9235042929649353,0.09009253233671188,0.09646293520927429,-0.36930379271507263,0.9198874831199646,0.09089624136686325,0.09156320989131927,-0.3801715672016144,0.9158731698989868,0.0912587121129036,0.08686362951993942,-0.3917016386985779,0.9114254713058472,0.09141924977302551,0.08271005004644394,-0.4022310674190521,0.9071999788284302,0.09137625992298126,0.07896625250577927,-0.4119149148464203,0.9031837582588196,0.09110855311155319,0.07543610781431198,-0.42120981216430664,0.8992168307304382,0.09073523432016373,0.07217811048030853,-0.42957931756973267,0.8955551981925964,0.09028712660074234,0.06913440674543381,-0.4370036721229553,0.892242431640625,0.08986306190490723,0.06637867540121078,-0.4431110620498657,0.8894780278205872,0.08958249539136887,0.063931405544281,-0.4475567042827606,0.8874574899673462,0.08961572498083115,0.061751075088977814,-0.4499557316303253,0.886394739151001,0.0902009978890419,0.05960671976208687,-0.44999876618385315,0.8864603638648987,0.09134215116500854,0.057349540293216705,-0.4477373957633972,0.8876367211341858,0.09297803044319153,0.05502983182668686,-0.4433487057685852,0.8898138999938965,0.09508127719163895,0.05266708508133888,-0.4369661808013916,0.8928864598274231,0.09758736938238144,0.050341878086328506,-0.42891010642051697,0.8966485261917114,0.10030428320169449,0.04810892045497894,-0.4199158251285553,0.9007192850112915,0.10307061672210693,0.04606601595878601,-0.41050782799720764,0.9048413038253784,0.10573193430900574,0.04430404305458069,-0.4010835289955139,0.9088398814201355,0.10787413269281387,0.04318934679031372,-0.39241936802864075,0.9124170541763306,0.10958000272512436,0.042535845190286636,-0.384500652551651,0.9156102538108826,0.11119189113378525,0.0418345145881176,-0.3769221901893616,0.9185945391654968,0.11264809221029282,0.04133796691894531,-0.3696325421333313,0.9213975071907043,0.11391439288854599,0.041120853275060654,-0.3624511957168579,0.9241005182266235,0.11523578315973282,0.04115261510014534,-0.35444116592407227,0.9270375967025757,0.11661936342716217,0.041563861072063446,-0.3457508683204651,0.930122971534729,0.11799988150596619,0.0421394445002079,-0.3369644284248352,0.9331427216529846,0.11941874027252197,0.04255903139710426,-0.32835787534713745,0.9360069632530212,0.12081333994865417,0.04296937584877014,-0.3198540508747101,0.9387497901916504,0.12201714515686035,0.04373868927359581,-0.31149035692214966,0.9413673877716064,0.1231246367096901,0.04495643079280853,-0.30288901925086975,0.9439690113067627,0.12405010312795639,0.04645407944917679,-0.2942042648792267,0.94651859998703,0.12470556795597076,0.048441629856824875,-0.28552186489105225,0.9489884972572327,0.1250506341457367,0.05123491585254669,-0.27660995721817017,0.9514327645301819,0.1250828057527542,0.05455620586872101,-0.2677293121814728,0.9537814259529114,0.1246674507856369,0.05822904407978058,-0.2592243254184723,0.9559655785560608,0.1239791214466095,0.06234734505414963,-0.25072914361953735,0.9580588936805725,0.12301761656999588,0.06680284440517426,-0.24231737852096558,0.9600449800491333,0.12179242074489594,0.07148193567991257,-0.2340245544910431,0.9619197249412537,0.12027469277381897,0.07623447477817535,-0.22587770223617554,0.9636916518211365,0.11858660727739334,0.08078163862228394,-0.21814529597759247,0.9653103947639465,0.1169121265411377,0.08557861298322678,-0.20991156995296478,0.9669255614280701,0.1151430755853653,0.09031904488801956,-0.2018953263759613,0.9684125185012817,0.11329032480716705,0.09472819417715073,-0.1944168359041214,0.9697391390800476,0.11120640486478806,0.09874868392944336,-0.18747930228710175,0.9709445834159851,0.10886701941490173,0.1025824025273323,-0.18112333118915558,0.9720181226730347,0.10612838715314865,0.10627255588769913,-0.175641268491745,0.9729301929473877,0.10301845520734787,0.10973414778709412,-0.17106880247592926,0.9736945629119873,0.09965940564870834,0.1127348244190216,-0.1672753095626831,0.9743601679801941,0.09627478569746017,0.11540895700454712,-0.1642339676618576,0.9749047160148621,0.09300591796636581,0.11805541813373566,-0.161808580160141,0.9753106236457825],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,0.9999998807907104,0.9999998807907104,1,0.9999998807907104,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,3.0333333015441895],"values":[-0.000002007238663281896,17.203704833984375,0.000003526464524838957,-0.0000043630066102196,17.203704833984375,-7.276186124727246e-7],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.12623147666454315,0.11169782280921936,-0.6745014190673828,0.7187747359275818,-0.12345748394727707,0.11383792757987976,-0.6596789360046387,0.7325454950332642,-0.1205318421125412,0.11600212007761002,-0.6440460681915283,0.7464719414710999,-0.11754580587148666,0.11811807751655579,-0.6280903220176697,0.7600879669189453,-0.11435534060001373,0.12028060853481293,-0.6110425591468811,0.7740041613578796,-0.11099562793970108,0.12245412170886993,-0.593089759349823,0.7879908084869385,-0.10763449221849442,0.12452720105648041,-0.5751314759254456,0.801331102848053,-0.10443120449781418,0.12641367316246033,-0.5580143928527832,0.8134701251983643,-0.10181228816509247,0.1278935968875885,-0.54402095079422,0.822993814945221,-0.09948471188545227,0.1291637420654297,-0.5315831899642944,0.8311671614646912,-0.0969356894493103,0.13050715625286102,-0.5179637670516968,0.8398124575614929,-0.09442213177680969,0.131785050034523,-0.504531979560852,0.8480358123779297,-0.09200020879507065,0.13297344744205475,-0.4915909469127655,0.8556824326515198,-0.08960845321416855,0.1341065913438797,-0.47881120443344116,0.8629748821258545,-0.08708757162094116,0.13525895774364471,-0.4653414785861969,0.8703896403312683,-0.08530572056770325,0.13604776561260223,-0.4558199644088745,0.8754668235778809,-0.08407499641180038,0.13658075034618378,-0.44924384355545044,0.8788954019546509,-0.08326379954814911,0.1369265764951706,-0.44490915536880493,0.8811209201812744,-0.08330386877059937,0.1369095891714096,-0.44512274861335754,0.8810119032859802,-0.0841025859117508,0.1365688443183899,-0.44939157366752625,0.8788190484046936,-0.085544653236866,0.13594332337379456,-0.4570964276790619,0.8747938871383667,-0.08703962713479996,0.13528038561344147,-0.46508532762527466,0.870527982711792,-0.08847951143980026,0.13462801277637482,-0.4727782905101776,0.8663298487663269,-0.09012072533369064,0.13386723399162292,-0.4815484583377838,0.8614342212677002,-0.09185269474983215,0.13304445147514343,-0.49080225825309753,0.8561398386955261,-0.09385630488395691,0.13206638395786285,-0.5015089511871338,0.8498460650444031,-0.09634386748075485,0.13081230223178864,-0.5148010849952698,0.8417754173278809,-0.0994112566113472,0.12920312583446503,-0.5311914086341858,0.8314202427864075,-0.10261693596839905,0.12744466960430145,-0.5483203530311584,0.8201051354408264,-0.10580731183290482,0.12561380863189697,-0.5653671026229858,0.8083229660987854,-0.10889434069395065,0.12376172095537186,-0.5818623304367065,0.7964051961898804,-0.11180488020181656,0.12193994224071503,-0.597415030002594,0.7846818566322327,-0.11443664878606796,0.12022668123245239,-0.6114767789840698,0.773657500743866,-0.1164521798491478,0.11887041479349136,-0.6222462058067322,0.7649303078651428,-0.11793337762355804,0.11784844100475311,-0.6301618218421936,0.7583531737327576,-0.11921478062868118,0.11694654822349548,-0.6370084881782532,0.7525500655174255,-0.12029225379228592,0.11617527902126312,-0.642765998840332,0.7475860714912415,-0.12110431492328644,0.11558587104082108,-0.6471049785614014,0.7437935471534729,-0.12175517529249191,0.11510837078094482,-0.6505832076072693,0.7407207489013672,-0.1224236711859703,0.1146133542060852,-0.654154360294342,0.737535297870636,-0.12309832125902176,0.11410873383283615,-0.657759428024292,0.7342877984046936,-0.1236746609210968,0.11367350816726685,-0.660839855670929,0.7314873337745667,-0.12417396157979965,0.11329346150159836,-0.6635079979896545,0.7290421724319458,-0.12458780407905579,0.11297661811113358,-0.6657183170318604,0.7270029783248901,-0.12500923871994019,0.11265169829130173,-0.6679699420928955,0.724912703037262,-0.12547221779823303,0.11229241639375687,-0.6704443693161011,0.722600519657135,-0.12596732378005981,0.11190549284219742,-0.673089861869812,0.7201107144355774,-0.12651656568050385,0.1114729642868042,-0.6760245561599731,0.7173270583152771,-0.12710686028003693,0.11100397258996964,-0.6791783571243286,0.7143099904060364,-0.1277460902929306,0.11049152910709381,-0.6825945377349854,0.7110114693641663,-0.1284678727388382,0.10990674048662186,-0.6864513158798218,0.7072489857673645,-0.1293688863515854,0.10916786640882492,-0.6912662982940674,0.70249342918396,-0.13045533001422882,0.10826316475868225,-0.6970714926719666,0.6966720223426819,-0.13167491555213928,0.1072293147444725,-0.70358806848526,0.690019965171814,-0.13297821581363678,0.10610291361808777,-0.7105510830879211,0.6827709674835205,-0.13431349396705627,0.10492422431707382,-0.7176867127418518,0.6751863956451416,-0.13562160730361938,0.10374525189399719,-0.7246755957603455,0.6675994396209717,-0.13686823844909668,0.10259798914194107,-0.7313371896743774,0.6602171063423157,-0.13798898458480835,0.10154643654823303,-0.7373262643814087,0.6534503698348999,-0.13882651925086975,0.10074806213378906,-0.7418007254600525,0.6483122706413269,-0.13946041464805603,0.1001359224319458,-0.7451883554458618,0.6443740129470825,-0.1399700790643692,0.09963930398225784,-0.7479110360145569,0.6411782503128052,-0.1403632014989853,0.09925325959920883,-0.7500119209289551,0.6386932730674744,-0.14069382846355438,0.09892657399177551,-0.7517781853675842,0.6365912556648254,-0.1409851610660553,0.0986369326710701,-0.7533355951309204,0.6347278952598572,-0.14125956594944,0.0983630046248436,-0.7548016905784607,0.632965087890625,-0.14152872562408447,0.09809299558401108,-0.7562398314476013,0.6312279105186462,-0.14172931015491486,0.09789109975099564,-0.757311999797821,0.6299275755882263,-0.1418858915567398,0.09773284941911697,-0.7581483721733093,0.6289100050926208,-0.1420406848192215,0.09757626801729202,-0.7589749693870544,0.627901554107666,-0.14212220907211304,0.09749343991279602,-0.7594110369682312,0.6273685097694397,-0.1421549916267395,0.09745986759662628,-0.7595864534378052,0.6271538734436035,-0.14216841757297516,0.0974465012550354,-0.7596579790115356,0.6270663142204285,-0.14219143986701965,0.09742310643196106,-0.7597809433937073,0.626915693283081,-0.14220558106899261,0.09740851074457169,-0.7598569393157959,0.6268226504325867,-0.14223410189151764,0.0973796471953392,-0.7600089907646179,0.6266363859176636,-0.14220888912677765,0.09740515798330307,-0.7598746418952942,0.6268009543418884,-0.14212283492088318,0.09749266505241394,-0.7594146132469177,0.6273640990257263,-0.1419678032398224,0.0976499542593956,-0.7585862278938293,0.6283761858940125,-0.14173366129398346,0.09788665920495987,-0.7573351860046387,0.6298993825912476,-0.14141125977039337,0.09821102768182755,-0.7556126713752747,0.6319864988327026,-0.14081066846847534,0.09881050884723663,-0.7524034380912781,0.6358442902565002,-0.14002475142478943,0.0995856523513794,-0.7482042908668518,0.6408323645591736,-0.13906128704547882,0.10052213072776794,-0.7430552840232849,0.6468586921691895,-0.13790978491306305,0.10162156075239182,-0.7369023561477661,0.6539334654808044,-0.13655869662761688,0.10288497805595398,-0.7296838760375977,0.6620633602142334,-0.13496457040309906,0.10434053838253021,-0.7211651802062988,0.6714301109313965,-0.1331300586462021,0.10597001016139984,-0.7113630175590515,0.6819160580635071,-0.13108132779598236,0.10773515701293945,-0.7004155516624451,0.6932740211486816,-0.12882235646247864,0.10961730033159256,-0.6883454322814941,0.7053860425949097,-0.1262315809726715,0.11169786006212234,-0.674501359462738,0.7187747359275818],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999999403953552,0.9999999403953552,0.9999999403953552,0.9999999403953552,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,3.0333333015441895],"values":[0.000001917271902129869,19.450502395629883,-0.000009350070286018308,0.0000010228612836726825,19.45050048828125,-0.00001349612375634024],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[0.14937162399291992,-0.14748121798038483,-0.019200164824724197,0.9775319695472717,0.14937162399291992,-0.14748121798038483,-0.01920015923678875,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999999403953552,1,0.9999997019767761,0.9999999403953552,0.9999999403953552,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,3.0333333015441895],"values":[0.1469000279903412,6.409508228302002,0.2581009268760681,0.14690019190311432,6.409512519836426,0.25809651613235474],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[0.020259663462638855,0.012515280395746231,-0.011196221224963665,0.999653697013855,0.02025965414941311,0.01251528225839138,-0.011196221224963665,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999999403953552,1.0000003576278687,1.0000001192092896,0.9999999403953552,1.0000003576278687,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,3.0333333015441895],"values":[-1.595942507037762e-7,6.81588888168335,-0.000001188552005260135,-2.8280916808398615e-7,6.815896987915039,-0.000015620547856087796],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895],"values":[-0.08071715384721756,-0.0308280810713768,-0.013674062676727772,0.9961663484573364,-0.07995288074016571,-0.030396955087780952,-0.013094396330416203,0.9962490200996399,-0.07943124324083328,-0.029874997213482857,-0.01239853072911501,0.9963154792785645,-0.07919803261756897,-0.02931372821331024,-0.011730995029211044,0.9963587522506714,-0.07938667386770248,-0.02874116227030754,-0.011197933927178383,0.996366560459137,-0.08003021776676178,-0.02814856916666031,-0.010784545913338661,0.9963365793228149,-0.08101093769073486,-0.027527665719389915,-0.01046708319336176,0.9962780475616455,-0.08214876800775528,-0.026858460158109665,-0.010195520706474781,0.9962059259414673,-0.08334171772003174,-0.026134023442864418,-0.009944641962647438,0.9961286783218384,-0.08471541106700897,-0.025392185896635056,-0.009754592552781105,0.9960338473320007,-0.08612212538719177,-0.024668985977768898,-0.00968112051486969,0.9959321022033691,-0.08774670958518982,-0.02394605055451393,-0.009675469249486923,0.9958080053329468,-0.08917752653360367,-0.023325342684984207,-0.00974924024194479,0.9956948757171631,-0.09047510474920273,-0.022764509543776512,-0.00988140981644392,0.9955894947052002,-0.09171769767999649,-0.02225489728152752,-0.010113895870745182,0.9954850077629089,-0.09292226284742355,-0.021782830357551575,-0.01040793675929308,0.995380699634552,-0.09416244179010391,-0.021319080144166946,-0.010735081508755684,0.9952706694602966,-0.09559549391269684,-0.020870372653007507,-0.011086971499025822,0.9951397180557251,-0.09683365374803543,-0.02043692208826542,-0.01144840195775032,0.9950249195098877,-0.09774386882781982,-0.020020946860313416,-0.011809096671640873,0.9949401617050171,-0.09820408374071121,-0.01963018625974655,-0.012165278196334839,0.9948983192443848,-0.09822066128253937,-0.019289109855890274,-0.012534696608781815,0.994898796081543,-0.09794639050960541,-0.01902950368821621,-0.012943453155457973,0.9949255585670471,-0.09734838455915451,-0.018879827111959457,-0.013414103537797928,0.9949809312820435,-0.09651491791009903,-0.01884080097079277,-0.013931549154222012,0.9950556755065918,-0.09560056775808334,-0.01888536661863327,-0.014444876462221146,0.9951357841491699,-0.09447365999221802,-0.019005510956048965,-0.014929917640984058,0.995233952999115,-0.09343034774065018,-0.019196515902876854,-0.015376324765384197,0.9953219890594482,-0.09231245517730713,-0.019418630748987198,-0.01571105606853962,0.9954167604446411,-0.09113802015781403,-0.019698305055499077,-0.016027655452489853,0.9955143928527832,-0.08985651284456253,-0.020035982131958008,-0.016328739002346992,0.9956192970275879,-0.0886564776301384,-0.020464183762669563,-0.016633888706564903,0.9957131147384644,-0.08766371756792068,-0.02101987786591053,-0.016954785212874413,0.9957839846611023,-0.08680114895105362,-0.021673141047358513,-0.017280837520956993,0.9958399534225464,-0.08596490323543549,-0.022439664229750633,-0.017583129927515984,0.9958902597427368,-0.08474283665418625,-0.023334117606282234,-0.017817579209804535,0.9959702491760254,-0.08325353264808655,-0.02427602931857109,-0.018002137541770935,0.9960700273513794,-0.0817599967122078,-0.025201309472322464,-0.018153732642531395,0.9961679577827454,-0.07979488372802734,-0.026089753955602646,-0.018168123438954353,0.9963041543960571,-0.07763104885816574,-0.026944320648908615,-0.018114712089300156,0.9964533448219299,-0.07552092522382736,-0.02775389328598976,-0.018057243898510933,0.9965943098068237,-0.07347393780946732,-0.02859102562069893,-0.017907720059156418,0.996726393699646,-0.07152004539966583,-0.029454370960593224,-0.01766623929142952,0.9968476891517639,-0.06968894600868225,-0.03032355196774006,-0.017355116084218025,0.9969567656517029,-0.06800360977649689,-0.03116690367460251,-0.017000658437609673,0.9970532059669495,-0.06645599752664566,-0.03191271796822548,-0.01664724573493004,0.9971399307250977,-0.06477852165699005,-0.03264070674777031,-0.016248855739831924,0.9972333312034607,-0.06256143003702164,-0.03338862955570221,-0.015761854127049446,0.9973579049110413,-0.061045631766319275,-0.03413260355591774,-0.015279569663107395,0.9974341988563538,-0.0599093958735466,-0.03484773263335228,-0.014805009588599205,0.9974855184555054,-0.05911731719970703,-0.035528719425201416,-0.014374430291354656,0.9975150227546692,-0.05867099016904831,-0.03616175428032875,-0.01399978343397379,0.9975240230560303,-0.058567535132169724,-0.03671642392873764,-0.013655439019203186,0.997514545917511,-0.05879715457558632,-0.037180766463279724,-0.013354298658668995,0.9974879622459412,-0.05935588479042053,-0.03753863647580147,-0.013109554536640644,0.9974446892738342,-0.0602295882999897,-0.03779132664203644,-0.012923404574394226,0.9973852038383484,-0.061394911259412766,-0.03798864781856537,-0.012777101248502731,0.9973085522651672,-0.0628364086151123,-0.03814041614532471,-0.012668224051594734,0.9972143173217773,-0.06454107165336609,-0.03824958577752113,-0.012589366175234318,0.9971022605895996,-0.06649798154830933,-0.038320884108543396,-0.012508692219853401,0.9969719648361206,-0.06868474185466766,-0.038357798010110855,-0.012413360178470612,0.9968234300613403,-0.0712023675441742,-0.038367632776498795,-0.012308157980442047,0.9966477155685425,-0.07390973716974258,-0.03834894672036171,-0.012198192998766899,0.9964526295661926,-0.07667181640863419,-0.038300562649965286,-0.012088943272829056,0.9962471723556519,-0.07945180684328079,-0.03822458162903786,-0.011984637007117271,0.9960334897041321,-0.08221705257892609,-0.03812328353524208,-0.011889712885022163,0.9958140850067139,-0.08496644347906113,-0.03799997270107269,-0.01180837769061327,0.9955888986587524,-0.08787991851568222,-0.03786415234208107,-0.011743854731321335,0.9953418970108032,-0.09088267385959625,-0.03772014006972313,-0.011701740324497223,0.9950782060623169,-0.09375238418579102,-0.03752684220671654,-0.01168495137244463,0.9948194026947021,-0.0959898978471756,-0.037279561161994934,-0.011660527437925339,0.9946156144142151,-0.0978187620639801,-0.036794908344745636,-0.011793723329901695,0.9944539070129395,-0.09937109053134918,-0.036176517605781555,-0.012083582580089569,0.9943192005157471,-0.10063082724809647,-0.03555700555443764,-0.012464937753975391,0.9942101240158081,-0.1017599031329155,-0.03495723381638527,-0.012911259196698666,0.9941107630729675,-0.10276796668767929,-0.03439532592892647,-0.013405445963144302,0.9940201640129089,-0.10359277576208115,-0.033891916275024414,-0.013909257017076015,0.9939448833465576,-0.10413855314254761,-0.03346658870577812,-0.01438229065388441,0.9938955307006836,-0.10421746969223022,-0.03314019367098808,-0.014777705073356628,0.9938923716545105,-0.10383056104183197,-0.03290541097521782,-0.015076304785907269,0.9939361810684204,-0.10303296148777008,-0.03269890695810318,-0.015302157960832119,0.9940225481987,-0.10091038048267365,-0.032507527619600296,-0.015378475189208984,0.9942454099655151,-0.09774019569158554,-0.0323464497923851,-0.01532095018774271,0.9945681691169739,-0.09494855254888535,-0.032212141901254654,-0.015225691720843315,0.9948444366455078,-0.0922778770327568,-0.032066572457551956,-0.015098891220986843,0.9951022863388062,-0.08981410413980484,-0.031913045793771744,-0.014936068095266819,0.995335042476654,-0.08770554512739182,-0.03175905719399452,-0.014738591387867928,0.9955309629440308,-0.0857185423374176,-0.03159366548061371,-0.014508441090583801,0.9957126379013062,-0.08371541649103165,-0.03139917179942131,-0.014298170804977417,0.9958922863006592,-0.0818386897444725,-0.031150348484516144,-0.014054323546588421,0.9960595369338989,-0.08071724325418472,-0.030828092247247696,-0.013674075715243816,0.9961663484573364],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.9999998211860657,0.9999999403953552,0.9999998211860657,0.9999998211860657,1,1.0000001192092896],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,3.0333333015441895],"values":[0.5746009945869446,51.81690979003906,-20.522695541381836,0.5746010541915894,51.81692123413086,-20.522706985473633],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[-0.18743081390857697,9.647312992910884e-8,-0.005247788038104773,0.9822638034820557,-0.18743081390857697,9.653238919327123e-8,-0.005247784312814474,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1.0000001192092896,1.000000238418579,1,1.0000001192092896,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,3.0333333015441895],"values":[-0.5746008157730103,4.330295085906982,20.522706985473633,-0.5746002793312073,4.330299377441406,20.5226993560791],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[0.6296830773353577,-7.674562141346541e-8,0.017631011083722115,0.7766520380973816,0.6296830773353577,-7.614604413674897e-8,0.017631012946367264,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,3.0333333015441895],"values":[1,1.000000238418579,1.000000238418579,1,1.000000238418579,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,3.0333333015441895],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,3.0333333015441895],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,3.0333333015441895],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"7feac0a5-a467-4dc9-a3a9-66de6d1bd4a0","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/11upward-thrust.json b/public/anims/baked/sword_shield/11upward-thrust.json new file mode 100644 index 0000000..3c599e0 --- /dev/null +++ b/public/anims/baked/sword_shield/11upward-thrust.json @@ -0,0 +1 @@ +{"name":"sword_shield/attack3-upward-thrust","duration":2.366666555404663,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[4.461057662963867,54.97077941894531,-0.4067268669605255,4.222546100616455,54.91121292114258,-0.14029662311077118,3.90847110748291,54.700130462646484,0.3447108268737793,3.5317952632904053,54.39940643310547,1.117197871208191,3.1452603340148926,54.173805236816406,2.311044454574585,2.7606048583984375,54.20769500732422,4.219855308532715,2.3412132263183594,54.685508728027344,7.064296722412109,1.910379409790039,55.20235824584961,10.355949401855469,1.5725102424621582,55.908226013183594,14.296731948852539,1.5539703369140625,57.15312957763672,19.020263671875,1.7478188276290894,57.87654113769531,22.898717880249023,1.8509883880615234,58.20158386230469,25.9193058013916,1.7463678121566772,58.54450607299805,28.817415237426758,1.5900951623916626,58.90918731689453,31.657135009765625,1.3863800764083862,59.10362243652344,34.25249099731445,0.96675705909729,59.04899597167969,36.810150146484375,0.26077890396118164,58.95501708984375,39.61518478393555,-0.6021511554718018,58.84002685546875,42.76500701904297,-1.5046162605285645,58.45367431640625,46.45322036743164,-2.3878979682922363,57.6895637512207,50.64004898071289,-3.157569169998169,56.70542907714844,54.9196662902832,-3.8543760776519775,55.32819366455078,59.188011169433594,-4.571836471557617,53.2136116027832,63.608577728271484,-5.284748077392578,51.00886535644531,67.3662109375,-6.029651641845703,49.2014274597168,69.98078155517578,-6.503003120422363,47.93638229370117,72.28327178955078,-6.708314895629883,47.17622375488281,74.33726501464844,-6.910862445831299,46.79606628417969,75.79884338378906,-7.038641929626465,46.42900085449219,76.8277816772461,-6.949291706085205,46.2086067199707,77.54138946533203,-6.753644943237305,46.159080505371094,77.94735717773438,-6.596578121185303,46.207515716552734,78.09809112548828,-6.465696334838867,46.27943801879883,77.97137451171875,-6.323941707611084,46.45261764526367,77.80542755126953,-6.158236026763916,46.70828628540039,77.65522766113281,-5.895958423614502,46.874271392822266,77.21465301513672,-5.5609307289123535,46.946136474609375,76.52692413330078,-5.236301422119141,46.941261291503906,75.55827331542969,-4.921870231628418,47.12702178955078,74.39784240722656,-4.492717742919922,47.58833312988281,72.67979431152344,-3.8216826915740967,48.290706634521484,70.34036254882812,-2.5846548080444336,49.223819732666016,67.27227020263672,-0.9413583278656006,51.2295036315918,63.47895431518555,0.36818981170654297,53.20942306518555,59.40361785888672,1.3185172080993652,53.91231155395508,55.36394119262695,2.622360944747925,55.01020812988281,50.10295867919922,4.343253135681152,56.11522674560547,45.51811981201172,6.034652233123779,57.09516143798828,41.806358337402344,7.56116247177124,58.24388885498047,38.213348388671875,9.008220672607422,59.25511932373047,35.01545333862305,10.112528800964355,59.618289947509766,32.34164047241211,10.853943824768066,59.62055206298828,29.697248458862305,11.374748229980469,59.390174865722656,26.974700927734375,11.62176513671875,59.06758117675781,24.34099578857422,11.32032299041748,58.74473190307617,21.80240821838379,10.348495483398438,58.322776794433594,19.071435928344727,9.116992950439453,57.843353271484375,15.94409465789795,7.9343438148498535,57.178306579589844,12.433629035949707,6.942025661468506,56.33014678955078,8.870139122009277,6.194629192352295,55.448707580566406,5.672541618347168,5.828556060791016,54.64774703979492,3.1647567749023438,5.824893951416016,54.17870330810547,1.2207945585250854,5.80374002456665,54.15175247192383,-0.2187371551990509,5.643712043762207,54.37145233154297,-1.0657708644866943,5.484271049499512,54.72671890258789,-1.500118613243103,5.33997106552124,55.05950927734375,-1.776254415512085,5.191334247589111,55.29652786254883,-1.861928105354309,5.031299591064453,55.41305923461914,-1.722857117652893,4.856976509094238,55.41054153442383,-1.3941956758499146,4.670283317565918,55.258026123046875,-0.9479153156280518,4.461054801940918,54.970767974853516,-0.4067296087741852],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.26674142479896545,-0.40490594506263733,0.23504017293453217,0.8424110412597656,0.27117469906806946,-0.39542990922927856,0.23501668870449066,0.8454979062080383,0.2763303220272064,-0.3857778310775757,0.23363396525382996,0.8486649990081787,0.2845076620578766,-0.375891774892807,0.23020516335964203,0.8513321280479431,0.2969440221786499,-0.3644627332687378,0.22222808003425598,0.8541697263717651,0.31133270263671875,-0.3497312664985657,0.20971602201461792,0.8583584427833557,0.32148510217666626,-0.3312649130821228,0.19744117558002472,0.8648282289505005,0.3259880244731903,-0.3092368543148041,0.19370591640472412,0.8721137642860413,0.32748088240623474,-0.28595513105392456,0.192208394408226,0.8797965049743652,0.3255758285522461,-0.2649122178554535,0.18315833806991577,0.8889740705490112,0.3167041540145874,-0.24474823474884033,0.1760144829750061,0.8993418216705322,0.3096238076686859,-0.21749038994312286,0.17488808929920197,0.9089803099632263,0.30897924304008484,-0.18172825872898102,0.17877086997032166,0.9162683486938477,0.31277599930763245,-0.14379629492759705,0.1792297661304474,0.9216129779815674,0.31529131531715393,-0.0976615771651268,0.17356017231941223,0.927863359451294,0.3185408413410187,-0.031359005719423294,0.15998035669326782,0.9337851405143738,0.3292941749095917,0.059452466666698456,0.13624365627765656,0.932452917098999,0.3442174792289734,0.16781896352767944,0.106269471347332,0.9176371693611145,0.35575011372566223,0.2755565643310547,0.079921193420887,0.8894510269165039,0.3651280701160431,0.3672686219215393,0.05541200190782547,0.8536537289619446,0.37022995948791504,0.44289642572402954,0.030253153294324875,0.8160008192062378,0.3707233965396881,0.49843698740005493,0.008926954120397568,0.7836103439331055,0.3675140142440796,0.5278729796409607,-0.002027303446084261,0.7656888961791992,0.3692249655723572,0.5491262674331665,0.002202881034463644,0.7497522234916687,0.3809690475463867,0.5617291927337646,0.009622300043702126,0.7343230843544006,0.39268338680267334,0.5685667395591736,-0.00512272585183382,0.7228453755378723,0.4078815281391144,0.583541214466095,-0.02367733232676983,0.7018203139305115,0.4219900965690613,0.5783476233482361,-0.02402656152844429,0.697754442691803,0.42704078555107117,0.5619208812713623,-0.01658369041979313,0.7082415819168091,0.4325147867202759,0.5560126304626465,-0.013344231992959976,0.7096498608589172,0.4363759160041809,0.5541550517082214,-0.013300151564180851,0.7087392807006836,0.43362411856651306,0.5539071559906006,-0.013403855264186859,0.7106176018714905,0.42617565393447876,0.5584353804588318,-0.016260933130979538,0.7115194201469421,0.41409924626350403,0.5672856569290161,-0.02173430472612381,0.7115029692649841,0.3988310992717743,0.5778558254241943,-0.026829956099390984,0.7115452289581299,0.3829714357852936,0.5881954431533813,-0.032111238688230515,0.711567223072052,0.366967111825943,0.6002330183982849,-0.04051052778959274,0.7095170617103577,0.3492683172225952,0.6163980960845947,-0.05506278946995735,0.7035859823226929,0.32394644618034363,0.6387143731117249,-0.08198075741529465,0.6930959224700928,0.2875094711780548,0.6683669090270996,-0.12108783423900604,0.675249457359314,0.2497101128101349,0.700838565826416,-0.16149403154850006,0.6483747959136963,0.22880183160305023,0.7191224098205566,-0.19176162779331207,0.6274872422218323,0.23236727714538574,0.7019855976104736,-0.2026854157447815,0.6419816017150879,0.24767310917377472,0.6393476128578186,-0.1835019737482071,0.7044286131858826,0.24942073225975037,0.5392985343933105,-0.12559443712234497,0.7944636344909668,0.22872911393642426,0.41719549894332886,-0.05763275921344757,0.8776727318763733,0.218295618891716,0.29119250178337097,0.006092647090554237,0.9314058423042297,0.21831431984901428,0.17466476559638977,0.057598136365413666,0.9583911299705505,0.215753972530365,0.07555793970823288,0.09654984623193741,0.9687204957008362,0.21471348404884338,-0.01071955356746912,0.13103553652763367,0.9677876830101013,0.22263357043266296,-0.09333163499832153,0.1617450714111328,0.9568501114845276,0.23759965598583221,-0.16726690530776978,0.18297380208969116,0.9391958713531494,0.2544684112071991,-0.2172318398952484,0.1928129494190216,0.922431230545044,0.2651081383228302,-0.24428598582744598,0.19567306339740753,0.9120055437088013,0.26813530921936035,-0.26624178886413574,0.20258547365665436,0.9034256935119629,0.2715345621109009,-0.2950918972492218,0.2104533612728119,0.8915712237358093,0.2796042859554291,-0.33080288767814636,0.2158796340227127,0.8750925064086914,0.2883574366569519,-0.36127591133117676,0.2220187783241272,0.858508825302124,0.293383926153183,-0.3857415020465851,0.2281646430492401,0.844434916973114,0.29328587651252747,-0.402253657579422,0.23603449761867523,0.8345437049865723,0.2871566116809845,-0.41367751359939575,0.23693124949932098,0.8308282494544983,0.2753158211708069,-0.43176528811454773,0.22517570853233337,0.8289003968238831,0.2650040090084076,-0.4445401430130005,0.21977604925632477,0.8269555568695068,0.26050594449043274,-0.43951645493507385,0.22349373996257782,0.8300678133964539,0.25942862033843994,-0.43027761578559875,0.22575995326042175,0.83461993932724,0.25914567708969116,-0.42715543508529663,0.22767096757888794,0.8357916474342346,0.25864291191101074,-0.42702245712280273,0.23003052175045013,0.8353691697120667,0.2584927976131439,-0.4250335991382599,0.2311272919178009,0.8361268639564514,0.2601301670074463,-0.419660747051239,0.23152276873588562,0.83822101354599,0.2634722888469696,-0.4122166633605957,0.23269766569137573,0.8405424356460571,0.26674142479896545,-0.4049060046672821,0.23504026234149933,0.8424109816551208],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998211860657,0.9999999403953552,0.9999998807907104,0.9999999403953552,0.9999998211860657,1],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,2.366666555404663],"values":[8.305602073669434,-6.711594104766846,5.719296932220459,8.305601119995117,-6.711592197418213,5.719293594360352],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.7016453742980957,0.07470971345901489,-0.34220030903816223,0.6204926371574402,0.6931607127189636,0.06792110204696655,-0.3497437536716461,0.6265734434127808,0.6773564219474792,0.057899218052625656,-0.3600004315376282,0.6389332413673401,0.6564478278160095,0.04029882326722145,-0.3723534047603607,0.654832124710083,0.637678325176239,0.016299165785312653,-0.3819925785064697,0.6687169671058655,0.631335973739624,-0.0007858754834160209,-0.38336026668548584,0.6741284132003784,0.6448178887367249,-0.01654881052672863,-0.3739575445652008,0.6664021015167236,0.674331784248352,-0.03738522529602051,-0.3559935986995697,0.6458696722984314,0.717731773853302,-0.060145601630210876,-0.3303469121456146,0.610011875629425,0.7733031511306763,-0.08048734068870544,-0.29921069741249084,0.5531699061393738,0.8189526200294495,-0.10160351544618607,-0.26792997121810913,0.49719902873039246,0.8526182770729065,-0.12350501120090485,-0.23133668303489685,0.45196452736854553,0.8808954954147339,-0.1444213092327118,-0.18573546409606934,0.4106921851634979,0.9034984111785889,-0.15782685577869415,-0.14320731163024902,0.37185075879096985,0.9216117858886719,-0.16262249648571014,-0.11261476576328278,0.3339216411113739,0.9359431862831116,-0.15324808657169342,-0.10214328020811081,0.30015355348587036,0.9446932077407837,-0.12364032864570618,-0.1139259934425354,0.28158271312713623,0.9465345144271851,-0.08435230702161789,-0.12984910607337952,0.2830130159854889,0.9457343816757202,-0.04763470217585564,-0.13799211382865906,0.2903027832508087,0.9418137669563293,-0.008747216314077377,-0.1565895974636078,0.297304630279541,0.9345000386238098,0.028612151741981506,-0.1878981590270996,0.300974041223526,0.9269554615020752,0.06399700045585632,-0.22893308103084564,0.2902544140815735,0.9261453747749329,0.09722534567117691,-0.278033584356308,0.23558300733566284,0.9200079441070557,0.11291705816984177,-0.283324658870697,0.24609407782554626,0.9179801940917969,0.1094885915517807,-0.2794474959373474,0.25929468870162964,0.8967713117599487,0.12101688981056213,-0.3141028583049774,0.2872203588485718,0.8804071545600891,0.12075383216142654,-0.3246895372867584,0.32384946942329407,0.8751603364944458,0.11720816045999527,-0.3121391236782074,0.3506077826023102,0.8737176656723022,0.12227606028318405,-0.3112744390964508,0.3532339632511139,0.8766346573829651,0.12005556374788284,-0.31473711133003235,0.3435679078102112,0.8794499635696411,0.1158217340707779,-0.31509077548980713,0.3374475836753845,0.8818300366401672,0.11596567183732986,-0.30930420756340027,0.33653923869132996,0.8830369710922241,0.12515486776828766,-0.30169621109962463,0.3369885981082916,0.8739652037620544,0.13837507367134094,-0.2868709862232208,0.3670724928379059,0.863557755947113,0.14771710336208344,-0.262925386428833,0.40412598848342896,0.8542624115943909,0.15316161513328552,-0.2407684624195099,0.43452024459838867,0.8399225473403931,0.15856660902500153,-0.2294464111328125,0.46555468440055847,0.8175798654556274,0.16606351733207703,-0.2221219390630722,0.504626452922821,0.7841569185256958,0.1797991693019867,-0.22010771930217743,0.551654577255249,0.745027482509613,0.19551993906497955,-0.21134081482887268,0.6016984581947327,0.7174098491668701,0.2129134088754654,-0.18566131591796875,0.6368052363395691,0.7124554514884949,0.22065569460391998,-0.17513778805732727,0.6426857709884644,0.7314577102661133,0.19659820199012756,-0.18882770836353302,0.6250302195549011,0.7824257612228394,0.1751401722431183,-0.21256887912750244,0.5585251450538635,0.8554991483688354,0.13138052821159363,-0.23104363679885864,0.4443863332271576,0.9171935319900513,0.04035511240363121,-0.23360760509967804,0.3202421963214874,0.9429945349693298,-0.05983785539865494,-0.20467661321163177,0.25551554560661316,0.944085955619812,-0.11322101950645447,-0.1472475230693817,0.2723983824253082,0.9359496831893921,-0.13307492434978485,-0.08016695082187653,0.31601035594940186,0.9319003224372864,-0.15759357810020447,-0.01930570788681507,0.32611870765686035,0.9307020902633667,-0.16705788671970367,-0.01637626253068447,0.3249877989292145,0.9215956330299377,-0.16780225932598114,-0.07040827721357346,0.3428507447242737,0.9003183245658875,-0.15679578483104706,-0.12213648110628128,0.3872012495994568,0.8771293759346008,-0.14145827293395996,-0.15407885611057281,0.43231165409088135,0.8562209010124207,-0.12600354850292206,-0.17714373767375946,0.46864596009254456,0.833849310874939,-0.09725313633680344,-0.2112364023923874,0.5006161332130432,0.8038824200630188,-0.05641710013151169,-0.25052833557128906,0.5364938974380493,0.7707074284553528,-0.01606810837984085,-0.27366432547569275,0.575204074382782,0.7382970452308655,0.024585772305727005,-0.29829534888267517,0.6044277548789978,0.7115851640701294,0.05530497059226036,-0.3211016356945038,0.6224802136421204,0.6956886649131775,0.07723234593868256,-0.32984215021133423,0.6334481835365295,0.6874428987503052,0.10495185852050781,-0.3435501754283905,0.6311739683151245,0.6862102150917053,0.12165876477956772,-0.3579349219799042,0.6214476823806763,0.6934898495674133,0.11577907204627991,-0.35159721970558167,0.6180989742279053,0.7027257680892944,0.10502725094556808,-0.33765843510627747,0.6173593401908875,0.7096787095069885,0.09942633658647537,-0.33144575357437134,0.6136890053749084,0.7144442200660706,0.09683658927679062,-0.3314850926399231,0.6085307598114014,0.7164996862411499,0.09467063099145889,-0.3335786461830139,0.6053023338317871,0.715268075466156,0.09081297367811203,-0.3349277377128601,0.6066035628318787,0.7100471258163452,0.08460091054439545,-0.336924284696579,0.6125012040138245,0.7016454339027405,0.07470986992120743,-0.34220030903816223,0.6204925179481506],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999997615814209,1.0000001192092896,0.9999992847442627,0.9999998807907104,1.0000001192092896,0.9999994039535522],"interpolation":2300,"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,2.366666555404663],"values":[-0.0000010694529919419438,25.812700271606445,-0.000002994748683704529,-0.000004526700195128797,25.81270408630371,-0.0000022519291178468848],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.5641615390777588,0.011500268243253231,-0.009047282859683037,0.8255347609519958,0.5717645287513733,0.005995122250169516,-0.005371355917304754,0.820378303527832,0.5917619466781616,-0.0002237967128166929,-0.0020548116881400347,0.806110143661499,0.6181316375732422,-0.009348850697278976,0.002027590060606599,0.7860164046287537,0.6409635543823242,-0.02172962576150894,0.007481384091079235,0.7672272324562073,0.6567281484603882,-0.02709200233221054,0.013911539688706398,0.7535122036933899,0.663282573223114,-0.03285805135965347,0.008819897659122944,0.7475953102111816,0.6626043915748596,-0.036805663257837296,0.010215327143669128,0.7479948997497559,0.6515730023384094,-0.041009142994880676,0.019215306267142296,0.7572329044342041,0.6190184950828552,-0.03996115177869797,0.031091028824448586,0.7837426662445068,0.5858612060546875,-0.03706179931759834,0.03389887884259224,0.8088534474372864,0.5541667342185974,-0.03379848226904869,0.03507091850042343,0.8309795260429382,0.5123127102851868,-0.030695639550685883,0.03955477848649025,0.8573382496833801,0.46528366208076477,-0.026643836870789528,0.04134859889745712,0.883793830871582,0.4239521026611328,-0.021998653188347816,0.037755995988845825,0.9046298265457153,0.39606761932373047,-0.01654774509370327,0.029656030237674713,0.9175931811332703,0.37738069891929626,-0.011058230884373188,0.02025153860449791,0.9257706999778748,0.35662996768951416,0.08118079602718353,0.040472954511642456,0.9298315048217773,0.32632380723953247,0.13563039898872375,0.040486834943294525,0.9346004724502563,0.28540754318237305,0.14646363258361816,0.03322679549455643,0.9465658664703369,0.23258528113365173,0.1583908200263977,0.03314928337931633,0.9590190649032593,0.1542244255542755,0.16328005492687225,0.03249094635248184,0.9739090204238892,0.0006141152698546648,0.1467803418636322,0.02913382276892662,0.9887397885322571,0.000017198064597323537,0.1657562255859375,0.009427227079868317,0.9861217141151428,0.00017063433188013732,0.1676272302865982,-0.006791760213673115,0.9858270883560181,-0.00001996965511352755,0.17520809173583984,0.0025902981869876385,0.9845280647277832,0.0003097614098805934,0.18592028319835663,0.007748638279736042,0.9825342893600464,0.058734796941280365,0.19525320827960968,0.0011691416148096323,0.9789918661117554,0.11117063462734222,0.20275811851024628,0.0018149586394429207,0.972896158695221,0.10075723379850388,0.20399388670921326,0.004466852638870478,0.973763108253479,0.08544470369815826,0.20194050669670105,0.0015807982999831438,0.9756622314453125,0.0949547067284584,0.20151391625404358,-0.007187888491898775,0.9748457074165344,0.10400925576686859,0.1970464140176773,-0.007370965555310249,0.974833607673645,0.1764710694551468,0.2020113319158554,-0.005403497721999884,0.9633380770683289,0.2718014419078827,0.1852731853723526,-0.01126894075423479,0.9442832469940186,0.34778666496276855,0.1577421873807907,-0.024599948897957802,0.9238812923431396,0.4104961156845093,0.1467577964067459,-0.032252997159957886,0.8993969559669495,0.47078320384025574,0.1573605090379715,-0.02962392568588257,0.8675962686538696,0.526176393032074,0.19128073751926422,-0.008812368847429752,0.828536331653595,0.5764772891998291,0.21150939166545868,0.007849648594856262,0.7892249822616577,0.5916046500205994,0.20823881030082703,0.006844671908766031,0.7788412570953369,0.5782707333564758,0.20648741722106934,0.005215493496507406,0.7892646789550781,0.5960442423820496,0.20199322700500488,0.0005068380851298571,0.7771292328834534,0.606356143951416,0.1620786488056183,-0.005524381063878536,0.7784807085990906,0.6162434220314026,0.06985581666231155,-0.05328389257192612,0.7826398015022278,0.6018410325050354,0.04028492793440819,-0.044351737946271896,0.7963651418685913,0.5772960186004639,-0.009634573012590408,-0.03661428764462471,0.8156567215919495,0.5894455909729004,-0.014560834504663944,-0.031445570290088654,0.8070644736289978,0.5905752182006836,0.002213803119957447,-0.04162205010652542,0.8059054613113403,0.5483971834182739,-0.0071494085714221,-0.06132050231099129,0.8339359760284424,0.4971054792404175,-0.018493587151169777,-0.047114692628383636,0.8662126660346985,0.47486355900764465,-0.01701841503381729,-0.0034234956838190556,0.8798882365226746,0.4930657148361206,-0.02044452540576458,0.011591560207307339,0.8696746230125427,0.5201582908630371,-0.021200858056545258,0.008836970664560795,0.8537609577178955,0.5444123148918152,-0.018290361389517784,0.011294152587652206,0.8385422825813293,0.5668020248413086,-0.01780816726386547,0.012364520691335201,0.8235687613487244,0.5873878598213196,-0.014519520103931427,0.010299744084477425,0.8091098070144653,0.6024039387702942,-0.006282377056777477,0.003548211883753538,0.7981588244438171,0.6117511987686157,0.0012804727302864194,0.0018351598409935832,0.7910470962524414,0.6152390241622925,0.0052543506026268005,0.002505516866222024,0.7883191704750061,0.6126925945281982,0.014213066548109055,-0.0031631560996174812,0.7901872396469116,0.6080713868141174,0.022726763039827347,-0.012083389796316624,0.7934650182723999,0.5948730707168579,0.023328997194767,-0.013216137886047363,0.8033723831176758,0.574050784111023,0.02205481380224228,-0.013709093444049358,0.8184078335762024,0.5528836846351624,0.0227524284273386,-0.017655761912465096,0.8327606320381165,0.5369711518287659,0.022685851901769638,-0.020117025822401047,0.8430555462837219,0.5275875926017761,0.021274935454130173,-0.019137434661388397,0.8490185737609863,0.5250709652900696,0.020315729081630707,-0.016486067324876785,0.8506562113761902,0.5292014479637146,0.019449375569820404,-0.01471555046737194,0.848145604133606,0.5416004061698914,0.017044221982359886,-0.012736472301185131,0.840366780757904,0.5641614198684692,0.011500147171318531,-0.00904721301048994,0.8255348205566406],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,2.366666555404663],"values":[1,1.0000001192092896,1.0000001192092896,0.9999999403953552,1,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,2.366666555404663],"values":[-6.141812036730698e-7,22.74270248413086,-4.1235861658606154e-7,0.0000015943281823638245,22.742712020874023,9.77722265815828e-7],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[-0.5643881559371948,-0.1181991845369339,0.10742565244436264,0.8099102973937988,-0.5674249529838562,-0.11131111532449722,0.09257150441408157,0.8105981349945068,-0.5685515999794006,-0.1295490711927414,0.06330018490552902,0.8099130988121033,-0.5777596831321716,-0.17386174201965332,0.015361549332737923,0.7973266839981079,-0.6003289818763733,-0.2246616780757904,-0.03996201977133751,0.7665085196495056,-0.625844419002533,-0.2550758421421051,-0.057896316051483154,0.7347809672355652,-0.6554787158966064,-0.2443365752696991,-0.05075459182262421,0.7127912044525146,-0.6856375932693481,-0.2320222705602646,-0.03897261619567871,0.688874363899231,-0.7161650657653809,-0.22366099059581757,-0.029012199491262436,0.6604859828948975,-0.7414907813072205,-0.21850977838039398,-0.025183798745274544,0.6338854432106018,-0.757532000541687,-0.21695220470428467,-0.024321435019373894,0.6152117252349854,-0.7662961483001709,-0.21350225806236267,-0.026682166382670403,0.6053884029388428,-0.7687227129936218,-0.20477905869483948,-0.031823646277189255,0.6050770878791809,-0.7672721147537231,-0.1926828920841217,-0.034904349595308304,0.6106951236724854,-0.7652023434638977,-0.17365121841430664,-0.029368512332439423,0.6192319393157959,-0.7654683589935303,-0.13092069327831268,-0.003433715784922242,0.6300049424171448,-0.761580765247345,-0.056798603385686874,0.04790451377630234,0.643796443939209,-0.7520492672920227,-0.034497328102588654,0.04565165936946869,0.6566184759140015,-0.7336686253547668,-0.006868115160614252,0.082098089158535,0.6744946837425232,-0.7015963196754456,0.007093424908816814,0.1495899111032486,0.696660041809082,-0.6499918699264526,-0.04759583622217178,0.19798807799816132,0.7321516275405884,-0.5742323398590088,-0.11250875890254974,0.2401828169822693,0.7745394110679626,-0.47600695490837097,-0.1187952384352684,0.2881566286087036,0.8223569393157959,-0.49051931500434875,-0.09212249517440796,0.3029860556125641,0.8118520379066467,-0.5046668648719788,-0.06402233988046646,0.3166966140270233,0.8005721569061279,-0.497326523065567,-0.04056539013981819,0.3269572854042053,0.8025707006454468,-0.4972667098045349,-0.02431885339319706,0.33113616704940796,0.8015505075454712,-0.5545612573623657,0.0014654240803793073,0.29562491178512573,0.7778595685958862,-0.6009028553962708,0.010871615260839462,0.2568267583847046,0.7568603754043579,-0.5989003777503967,0.0006550673278979957,0.25168508291244507,0.7602450847625732,-0.5893468260765076,-0.011893849819898605,0.2578240931034088,0.7655426859855652,-0.5920770764350891,-0.02425098419189453,0.25548607110977173,0.7639263272285461,-0.5870497226715088,-0.038192227482795715,0.25752323865890503,0.7665479779243469,-0.6084875464439392,-0.044558070600032806,0.2359425127506256,0.7563653588294983,-0.6385725736618042,-0.03050844371318817,0.2004239708185196,0.7423776388168335,-0.659898579120636,-0.012687287293374538,0.1774362325668335,0.729992687702179,-0.6737370491027832,-0.0026038659270852804,0.1644938886165619,0.7204258441925049,-0.6796752214431763,-0.012095966376364231,0.16065172851085663,0.7156020402908325,-0.6721019148826599,-0.050244323909282684,0.1661451905965805,0.7198265790939331,-0.6681802868843079,-0.06859592348337173,0.16496995091438293,0.7222289443016052,-0.6578996181488037,-0.050849221646785736,0.19472338259220123,0.7257170677185059,-0.6367514133453369,-0.0690600648522377,0.25422218441963196,0.7246719598770142,-0.6715853214263916,-0.08757907897233963,0.21800284087657928,0.7026932239532471,-0.7174103856086731,-0.10713416337966919,0.18598352372646332,0.6627629995346069,-0.7587372064590454,-0.1265440434217453,0.19801616668701172,0.6075310707092285,-0.7938038110733032,-0.214628666639328,0.10623790323734283,0.5590381622314453,-0.7867915630340576,-0.2534131705760956,0.020829444751143456,0.5624116659164429,-0.7448054552078247,-0.2435792088508606,0.020439840853214264,0.6208995580673218,-0.6936820149421692,-0.18028689920902252,0.05346806347370148,0.6953007578849792,-0.7024953365325928,-0.07118623703718185,0.035656675696372986,0.7072209715843201,-0.7276657223701477,-0.04576774686574936,0.018729830160737038,0.68414705991745,-0.7440075874328613,-0.10076963901519775,-0.02028842084109783,0.6602171063423157,-0.7526811361312866,-0.1072288230061531,-0.02756456658244133,0.6490094661712646,-0.7547726035118103,-0.10032659024000168,-0.01990549825131893,0.6479634642601013,-0.7468470931053162,-0.11040126532316208,-0.015114981681108475,0.6555933356285095,-0.737326979637146,-0.11305229365825653,0.0036801116075366735,0.6659989953041077,-0.721929132938385,-0.11805018782615662,0.02804882451891899,0.681245744228363,-0.6981093883514404,-0.13419029116630554,0.0522591806948185,0.7013595700263977,-0.6765305995941162,-0.12487094104290009,0.07719363272190094,0.7216333746910095,-0.6625939607620239,-0.08699686825275421,0.10327617079019547,0.7367054224014282,-0.6369929313659668,-0.08027874678373337,0.12149807810783386,0.7569898366928101,-0.6128262281417847,-0.0887235477566719,0.1335812509059906,0.7737752795219421,-0.5937145352363586,-0.08732635527849197,0.1393369883298874,0.7876943349838257,-0.5720100402832031,-0.09285687655210495,0.1390867382287979,0.8030174970626831,-0.5529985427856445,-0.10104399174451828,0.13876836001873016,0.8153073787689209,-0.5423508882522583,-0.10684412717819214,0.13955923914909363,0.821561336517334,-0.5382190346717834,-0.1093839555978775,0.13916891813278198,0.8240069150924683,-0.5387332439422607,-0.10785724967718124,0.13722722232341766,0.8241978287696838,-0.5430548191070557,-0.10424797236919403,0.13420145213603973,0.8223222494125366,-0.5513224601745605,-0.10367758572101593,0.1266929656267166,0.8180729746818542,-0.5643879175186157,-0.11819924414157867,0.1074257418513298,0.8099104762077332],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999999403953552,0.9999996423721313,0.9999998807907104,0.9999999403953552,0.9999998211860657,0.9999997615814209],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,2.366666555404663],"values":[-9.464242225476482e-7,13.415107727050781,0.000002059790404018713,6.519750854749873e-7,13.41511058807373,-0.0000011760456573028932],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[-0.3069814443588257,-0.005662641488015652,-0.0018292132299393415,0.9516969323158264,-0.3069814443588257,-0.005662641488015652,-0.0018292211461812258,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998807907104,0.9999999403953552,0.9999999403953552,0.9999998807907104,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,2.366666555404663],"values":[-11.139496803283691,-4.0200982093811035,0.42029887437820435,-11.139497756958008,-4.020101547241211,0.4203018844127655],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.7357783317565918,-0.2685343027114868,0.3887597918510437,0.48516544699668884,0.7375155091285706,-0.2682659924030304,0.3942078649997711,0.47823062539100647,0.7394232153892517,-0.268977552652359,0.4000904858112335,0.4699276387691498,0.745749294757843,-0.26855430006980896,0.4066610634326935,0.45427238941192627,0.7595093250274658,-0.2645863890647888,0.4130902886390686,0.4271956980228424,0.784761369228363,-0.25410783290863037,0.4232121407985687,0.37479379773139954,0.8206220269203186,-0.2322797179222107,0.43262094259262085,0.29234352707862854,0.8544294834136963,-0.21266493201255798,0.42148706316947937,0.21696214377880096,0.8784648776054382,-0.19616785645484924,0.3905375301837921,0.19312705099582672,0.8889371752738953,-0.20277990400791168,0.3431698679924011,0.2256225049495697,0.882265031337738,-0.21293905377388,0.28510233759880066,0.3081916272640228,0.869717538356781,-0.1974002718925476,0.2375779151916504,0.38494324684143066,0.8483168482780457,-0.15899719297885895,0.21409529447555542,0.4574293792247772,0.8179309368133545,-0.10456033796072006,0.1964561492204666,0.5305290818214417,0.7792332172393799,-0.06103793904185295,0.1684657335281372,0.6005741357803345,0.7397472858428955,-0.039550554007291794,0.14237117767333984,0.6564603447914124,0.7012097239494324,-0.07169012725353241,0.1414514034986496,0.6950949430465698,0.6667623519897461,-0.11773206293582916,0.16346070170402527,0.7175289392471313,0.6482627987861633,-0.19460254907608032,0.2066211700439453,0.7065358757972717,0.6515448093414307,-0.2665577530860901,0.2571612298488617,0.6620456576347351,0.634988009929657,-0.31856569647789,0.30442407727241516,0.6345331072807312,0.6256908178329468,-0.3593157231807709,0.3596641719341278,0.5916460156440735,0.595089852809906,-0.38736748695373535,0.3401273190975189,0.6165452599525452,0.5415295362472534,-0.40866193175315857,0.32592830061912537,0.6584162712097168,0.46129998564720154,-0.42462342977523804,0.33086395263671875,0.705284595489502,0.4268459975719452,-0.4278091788291931,0.3268071413040161,0.7266215682029724,0.39911985397338867,-0.43868789076805115,0.3402184844017029,0.7297312617301941,0.37007832527160645,-0.4332505762577057,0.34029966592788696,0.7480188012123108,0.3576268255710602,-0.4196085035800934,0.3238764703273773,0.7688536047935486,0.34654057025909424,-0.4165726900100708,0.32034236192703247,0.7770184278488159,0.3425091803073883,-0.4158271849155426,0.31977686285972595,0.7794344425201416,0.35146814584732056,-0.41450273990631104,0.31480467319488525,0.7781745791435242,0.3668553829193115,-0.4142185151576996,0.312055379152298,0.7723093032836914,0.38939374685287476,-0.41404950618743896,0.3136195242404938,0.7606433629989624,0.4164906442165375,-0.41433316469192505,0.3170989453792572,0.7445212602615356,0.44189777970314026,-0.41655436158180237,0.32243284583091736,0.7261170148849487,0.4661618173122406,-0.41966962814331055,0.3320232331752777,0.7045077681541443,0.4924136698246002,-0.42158669233322144,0.34770920872688293,0.6774156093597412,0.5328699350357056,-0.41634002327919006,0.36906829476356506,0.6375729441642761,0.5982436537742615,-0.39783212542533875,0.3898032307624817,0.5760968923568726,0.6753430962562561,-0.371111661195755,0.40042296051979065,0.49583199620246887,0.7367510199546814,-0.35043367743492126,0.3989049196243286,0.4186514914035797,0.7651767730712891,-0.31751030683517456,0.3959309160709381,0.39614444971084595,0.7814540863037109,-0.2760096788406372,0.40850064158439636,0.38245970010757446,0.7627432346343994,-0.2570962905883789,0.38435372710227966,0.4521023631095886,0.7537742257118225,-0.20425158739089966,0.30910632014274597,0.5427328944206238,0.7434273958206177,-0.16524334251880646,0.23002757132053375,0.605885922908783,0.7305671572685242,-0.13367897272109985,0.1628570705652237,0.6495222449302673,0.7347319722175598,-0.11947931349277496,0.12078393250703812,0.6567381024360657,0.7469475269317627,-0.12881791591644287,0.10115762799978256,0.6443930864334106,0.7557034492492676,-0.15190400183200836,0.09476660192012787,0.6299657225608826,0.7578092217445374,-0.18277671933174133,0.10759713500738144,0.6170418858528137,0.7566589713096619,-0.2213182896375656,0.15424126386642456,0.5955628752708435,0.7546490430831909,-0.2564903795719147,0.23246832191944122,0.557383120059967,0.7438216209411621,-0.2837543785572052,0.321908175945282,0.5124334692955017,0.7294597625732422,-0.2938535213470459,0.39250990748405457,0.4769429862499237,0.7308619022369385,-0.28967851400375366,0.4127776324748993,0.45993688702583313,0.7466259598731995,-0.2873331904411316,0.38683781027793884,0.45863476395606995,0.7657371759414673,-0.2751061022281647,0.3793078064918518,0.4405551552772522,0.7611322402954102,-0.2668493390083313,0.38970866799354553,0.444518119096756,0.7298099398612976,-0.27098211646080017,0.40203964710235596,0.48198574781417847,0.723078191280365,-0.2623782455921173,0.39672642946243286,0.5009229183197021,0.7286966443061829,-0.25433024764060974,0.37915921211242676,0.5104465484619141,0.7222501039505005,-0.258880078792572,0.3855770230293274,0.512509822845459,0.7189648151397705,-0.26308566331863403,0.39515358209609985,0.5076704621315002,0.723820686340332,-0.26290956139564514,0.39061254262924194,0.5043650269508362,0.7285438776016235,-0.26142391562461853,0.38485321402549744,0.5027618408203125,0.7319767475128174,-0.2612232267856598,0.3832407295703888,0.4990983307361603,0.734880268573761,-0.2621099650859833,0.3846239745616913,0.4932684898376465,0.7363235354423523,-0.26455917954444885,0.3865305185317993,0.48829326033592224,0.7357780337333679,-0.26853445172309875,0.38875994086265564,0.48516568541526794],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,2.366666555404663],"values":[1.000000238418579,1,1,1.000000238418579,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,2.366666555404663],"values":[-7.747105996713799e-7,25.527103424072266,-2.2482117856270634e-7,9.30572582547029e-7,25.527103424072266,0.0000025185095182678197],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.4589588940143585,-0.14348722994327545,-0.05269257724285126,0.8752094507217407,0.45630642771720886,-0.1429496705532074,-0.05280812829732895,0.8766762018203735,0.45476680994033813,-0.1409841626882553,-0.051853202283382416,0.8778507113456726,0.4430810213088989,-0.13657242059707642,-0.0506853386759758,0.8845666646957397,0.41336703300476074,-0.1281524896621704,-0.05060824006795883,0.9000797271728516,0.3413766920566559,-0.10948046296834946,-0.049231767654418945,0.9322296977043152,0.21146434545516968,-0.06682867556810379,-0.034556351602077484,0.9744858145713806,0.09050474315881729,0.011678548529744148,-0.014767325483262539,0.9957180619239807,0.0747859850525856,0.011292965151369572,0.020940469577908516,0.9969157576560974,0.18459589779376984,0.006980382837355137,0.04305977746844292,0.9818459749221802,0.39412277936935425,-0.051894254982471466,0.06560614705085754,0.9152432084083557,0.5460495352745056,-0.13825197517871857,0.03538862615823746,0.8255083560943604,0.6287099719047546,-0.12818805873394012,0.020761089399456978,0.7667206525802612,0.6621471047401428,-0.08617664873600006,0.029380254447460175,0.7438223361968994,0.6535515189170837,-0.05612831562757492,0.037193574011325836,0.7538810968399048,0.6155285239219666,-0.04816105216741562,0.0274676363915205,0.7861620187759399,0.5360553860664368,-0.033496592193841934,0.01807273179292679,0.8433243632316589,0.4510820508003235,-0.01010135468095541,0.01770837791264057,0.892249584197998,0.3424395024776459,-0.032986655831336975,0.002796158427372575,0.9389564990997314,0.2091759443283081,-0.04813629016280174,0.008974296040832996,0.9766513109207153,0.18597641587257385,-0.04057251662015915,0.011354816146194935,0.9816505312919617,0.1404716819524765,-0.033582400530576706,-0.004666022956371307,0.9895039796829224,0.22687172889709473,-0.08742712438106537,-0.0013697396498173475,0.9699916839599609,0.36157160997390747,-0.11652624607086182,0.0018017496913671494,0.9250320792198181,0.49157699942588806,-0.1432998925447464,-0.018344789743423462,0.8587669134140015,0.5656110644340515,-0.15379522740840912,-0.03558642789721489,0.809422492980957,0.6133682131767273,-0.15111108124256134,-0.05307328328490257,0.7733874320983887,0.6442846059799194,-0.13990391790866852,-0.05833645537495613,0.7496140003204346,0.6649298667907715,-0.13443569839000702,-0.054234135895967484,0.7327032089233398,0.6778688430786133,-0.13010062277317047,-0.05365673080086708,0.721587598323822,0.6845741868019104,-0.12734848260879517,-0.05290541797876358,0.7157804369926453,0.6876094937324524,-0.12857569754123688,-0.0484488271176815,0.7129615545272827,0.6891834139823914,-0.12994688749313354,-0.04351372271776199,0.7115101218223572,0.6892252564430237,-0.13119985163211823,-0.03902255371212959,0.7115001678466797,0.6859986782073975,-0.13532279431819916,-0.035546835511922836,0.7140238285064697,0.6787453889846802,-0.13706344366073608,-0.03007144294679165,0.7208426594734192,0.6684897541999817,-0.13330617547035217,-0.020210443064570427,0.7313976287841797,0.6535028219223022,-0.12872208654880524,-0.009945939294993877,0.7458323240280151,0.6273910403251648,-0.11956978589296341,0.003022798104211688,0.7694637179374695,0.5796309113502502,-0.10175393521785736,0.025403261184692383,0.808102011680603,0.4973313808441162,-0.0899774506688118,0.051570016890764236,0.8613396883010864,0.37509551644325256,-0.0703187882900238,0.06564738601446152,0.9219810366630554,0.24117353558540344,-0.005981347057968378,0.05795395374298096,0.9687315821647644,0.02617732062935829,-0.0066964291036129,-0.02007662132382393,0.9994332790374756,0.03332335874438286,-0.07669830322265625,-0.06968356668949127,0.9940578937530518,0.18582066893577576,-0.16153545677661896,-0.07249387353658676,0.9664996862411499,0.3512970209121704,-0.15115179121494293,-0.07434407621622086,0.9209867119789124,0.49900156259536743,-0.14209598302841187,-0.06021396443247795,0.8527487516403198,0.5974225997924805,-0.13562077283859253,-0.04448265582323074,0.7891226410865784,0.6791231036186218,-0.1356414407491684,-0.030271073803305626,0.7207474708557129,0.7280614972114563,-0.13581299781799316,-0.01636628434062004,0.671724259853363,0.737730085849762,-0.11463180929422379,0.022543713450431824,0.6649102568626404,0.712626576423645,-0.1104702576994896,0.038736529648303986,0.69170743227005,0.660182535648346,-0.10117371380329132,0.05329318717122078,0.7423495650291443,0.5796302556991577,-0.08019326627254486,0.06340210139751434,0.8084417581558228,0.4831366240978241,-0.06293164193630219,0.04397153854370117,0.8721726536750793,0.3993343114852905,-0.05290038138628006,0.020102163776755333,0.9150571227073669,0.36994659900665283,-0.04480356350541115,0.026971107348799706,0.9275800585746765,0.34045180678367615,-0.0565890409052372,0.017315251752734184,0.9383978247642517,0.33770129084587097,-0.1051369458436966,-0.0205630362033844,0.9351369738578796,0.3978167772293091,-0.1539258509874344,-0.059886716306209564,0.9024755954742432,0.4408188760280609,-0.15385322272777557,-0.06332923471927643,0.8820415735244751,0.46601560711860657,-0.1551976203918457,-0.056101828813552856,0.8692501187324524,0.4783054292201996,-0.15247687697410583,-0.06118587777018547,0.8626882433891296,0.4772302508354187,-0.15102630853652954,-0.06837336719036102,0.8629991412162781,0.4706611633300781,-0.1529616266489029,-0.06808587908744812,0.8662824034690857,0.46601420640945435,-0.1540018767118454,-0.06521705538034439,0.8688273429870605,0.462281733751297,-0.15347740054130554,-0.06276330351829529,0.871091902256012,0.45864906907081604,-0.1526237428188324,-0.061579618602991104,0.8732439279556274,0.45747897028923035,-0.14735743403434753,-0.0577612929046154,0.8750213980674744,0.4589589238166809,-0.14348746836185455,-0.052692681550979614,0.8752094507217407],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998807907104,0.9999998211860657,0.9999996423721313,0.9999999403953552,1.0000001192092896,0.9999998807907104],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,2.366666555404663],"values":[-9.245657395240414e-8,23.109195709228516,-6.290925966823124e-7,-9.090530284083798e-7,23.10919952392578,-1.9749099067212228e-7],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[-0.6416029930114746,0.12185410410165787,-0.15998998284339905,0.7402029633522034,-0.6452310085296631,0.12746703624725342,-0.15600916743278503,0.7369465827941895,-0.647871732711792,0.13143101334571838,-0.15444530546665192,0.7342580556869507,-0.6501200199127197,0.13676297664642334,-0.16256466507911682,0.7295290231704712,-0.6438491940498352,0.14450635015964508,-0.1768341362476349,0.730277955532074,-0.6184741854667664,0.15995772182941437,-0.1965692937374115,0.7438170313835144,-0.5683866143226624,0.17068934440612793,-0.20463807880878448,0.7784119248390198,-0.5161966681480408,0.15909191966056824,-0.18598990142345428,0.8207548260688782,-0.5059219598770142,0.19767901301383972,-0.08100294321775436,0.8357059955596924,-0.5008480548858643,0.15779940783977509,-0.0055653853341937065,0.8510109186172485,-0.5070526003837585,0.06851852685213089,-0.042731333523988724,0.8581240773200989,-0.5129454731941223,0.06563171744346619,-0.09100328385829926,0.8510569334030151,-0.5383381247520447,0.06428077071905136,-0.09720519185066223,0.834632396697998,-0.5760987401008606,0.07828937470912933,-0.06893235445022583,0.8106968998908997,-0.5885918140411377,0.07394448667764664,-0.06106674671173096,0.8027220964431763,-0.6084828972816467,0.055000174790620804,-0.020299911499023438,0.7913984060287476,-0.5758346319198608,0.06714664399623871,-0.04570194333791733,0.8135215044021606,-0.5629804730415344,0.04709180071949959,-0.002623428124934435,0.8251233100891113,-0.540995180606842,0.05094769224524498,-0.0063477326184511185,0.8394571542739868,-0.5210296511650085,0.07920274883508682,-0.058825451880693436,0.847817599773407,-0.5463922023773193,0.08575552701950073,-0.07003200054168701,0.8301789164543152,-0.4690464735031128,0.12444314360618591,-0.1560046523809433,0.8603324294090271,-0.35096636414527893,0.1425924152135849,-0.17571091651916504,0.9086339473724365,-0.3296777904033661,0.13543783128261566,-0.17334184050559998,0.9181077480316162,-0.3983529508113861,0.12079092115163803,-0.15010720491409302,0.8967677354812622,-0.4527346193790436,0.1210290715098381,-0.13894978165626526,0.8723968267440796,-0.4973127543926239,0.11231543123722076,-0.13548263907432556,0.8495349884033203,-0.5174804925918579,0.08143392950296402,-0.12391722202301025,0.8427496552467346,-0.5277073383331299,0.061238668859004974,-0.1145494282245636,0.8394362926483154,-0.5362324118614197,0.048084359616041183,-0.11078568547964096,0.8353856801986694,-0.5430195927619934,0.04416560381650925,-0.10978888720273972,0.8313396573066711,-0.5492444038391113,0.05296313762664795,-0.11120837181806564,0.826533854007721,-0.554701566696167,0.07002314180135727,-0.11254166066646576,0.8214239478111267,-0.5614056587219238,0.08907821029424667,-0.11466184258460999,0.8147032856941223,-0.567605197429657,0.10752703994512558,-0.11879301816225052,0.8075583577156067,-0.5681610703468323,0.12048321962356567,-0.12873144447803497,0.8038066029548645,-0.5642307996749878,0.12898753583431244,-0.14534366130828857,0.8024219870567322,-0.5566132664680481,0.1402328461408615,-0.16519026458263397,0.8020153641700745,-0.5352924466133118,0.15506522357463837,-0.192411407828331,0.8077095150947571,-0.5173335075378418,0.16236171126365662,-0.2072501927614212,0.8142801523208618,-0.4941357672214508,0.1595124453306198,-0.20419999957084656,0.8298723101615906,-0.4118151068687439,0.16369658708572388,-0.19917243719100952,0.8740378022193909,-0.3102014660835266,0.13363638520240784,-0.12342588603496552,0.933103621006012,-0.18280884623527527,0.12068932503461838,-0.08807294815778732,0.9717294573783875,-0.16949090361595154,0.14729568362236023,-0.17389453947544098,0.9588208794593811,-0.26407575607299805,0.12141865491867065,-0.15394194424152374,0.9443640112876892,-0.3456111550331116,0.015957752242684364,-0.14266559481620789,0.9273321032524109,-0.3805758059024811,-0.04791947081685066,-0.16760629415512085,0.9081706404685974,-0.37159720063209534,-0.035150833427906036,-0.16124196350574493,0.9136087894439697,-0.4052317142486572,0.026942966505885124,-0.11849483847618103,0.9061017036437988,-0.4393936097621918,0.07658091932535172,-0.07196494191884995,0.8921265006065369,-0.43790578842163086,0.0649668350815773,-0.050866127014160156,0.8952264785766602,-0.43041369318962097,0.057982735335826874,-0.0369441993534565,0.9000095129013062,-0.4450913667678833,0.04653516784310341,-0.013974633067846298,0.8941660523414612,-0.4643622636795044,0.03451861813664436,0.011159466579556465,0.8849020600318909,-0.4532473385334015,0.041417934000492096,0.04229763150215149,0.8894168734550476,-0.42259451746940613,0.07711677998304367,0.04580112174153328,0.9018698334693909,-0.43457719683647156,0.11508871614933014,-0.026810459792613983,0.8928484916687012,-0.5101719498634338,0.12898549437522888,-0.13384497165679932,0.8397457599639893,-0.576666533946991,0.13558976352214813,-0.16301563382148743,0.7889848351478577,-0.6001003980636597,0.12918101251125336,-0.15719401836395264,0.7736161351203918,-0.6137647032737732,0.09766746312379837,-0.16593925654888153,0.7656488418579102,-0.6353140473365784,0.08347916603088379,-0.15931829810142517,0.7510159611701965,-0.6419195532798767,0.07733936607837677,-0.15997441112995148,0.7458995580673218,-0.6365749835968018,0.08521642535924911,-0.16508954763412476,0.7485024929046631,-0.6303249597549438,0.10332847386598587,-0.17075401544570923,0.7502378225326538,-0.6302635073661804,0.112514428794384,-0.16981540620326996,0.7491803169250488,-0.6313862800598145,0.1170048862695694,-0.16762284934520721,0.7480400204658508,-0.6337604522705078,0.12189242988824844,-0.16229601204395294,0.7464247941970825,-0.6370590329170227,0.12239320576190948,-0.16071605682373047,0.743872344493866,-0.6416031122207642,0.12185399234294891,-0.1599898636341095,0.7402029037475586],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999997615814209,1.0000001192092896,0.9999998211860657,0.9999998807907104,1.0000001192092896,1],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,2.366666555404663],"values":[0.0000013779300616079126,13.547398567199707,-2.2837222957150516e-7,3.013726654899074e-7,13.547396659851074,6.455055654441821e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[-0.3078855872154236,0.014501914381980896,0.004691828973591328,0.9513012766838074,-0.30788561701774597,0.01450191531330347,0.004691828973591328,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,2.366666555404663],"values":[1,1.0000001192092896,1.0000001192092896,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,2.366666555404663],"values":[2.8339991569519043,7.75429630279541,-6.1396002769470215,2.833998918533325,7.754303455352783,-6.139602184295654],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[-0.3320006728172302,-0.09571804106235504,-0.11373622715473175,0.9314922094345093,-0.3320007026195526,-0.09571807086467743,-0.11373620480298996,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998807907104,1,0.9999998211860657,1,0.9999999403953552,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,2.366666555404663],"values":[-1.275907663966791e-7,10.288593292236328,2.9569994453026993e-8,-1.275907663966791e-7,10.288592338562012,8.640352007205365e-7],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[-1.5148543752729893e-8,-1.6210833564400673e-8,1.5934347175061703e-8,1,-1.5614205040037632e-8,-1.1204972949485636e-8,1.709850039333105e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,2.366666555404663],"values":[1,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,2.366666555404663],"values":[7.308165663744148e-7,10.288617134094238,-1.614481419665026e-7,0.0000010819139788509347,10.288599967956543,1.8823705261183932e-8],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.1073882207274437,-0.05010690167546272,-0.05074407905340195,0.9916562438011169,0.10921657085418701,-0.0523296557366848,-0.05159120634198189,0.9912979602813721,0.11264684051275253,-0.05514543876051903,-0.053292203694581985,0.9906712770462036,0.11540893465280533,-0.05941479653120041,-0.05277888849377632,0.9901338219642639,0.11622167378664017,-0.06433512270450592,-0.04736195504665375,0.9900052547454834,0.11315061151981354,-0.06962776184082031,-0.03685061261057854,0.9904499053955078,0.10569746047258377,-0.07445182651281357,-0.027964716777205467,0.9912128448486328,0.09298079460859299,-0.07822591811418533,-0.029340343549847603,0.992156445980072,0.08071756362915039,-0.08014029264450073,-0.031992893666028976,0.9929947853088379,0.0801970586180687,-0.08193184435367584,-0.021754750981926918,0.9931678175926208,0.09244221448898315,-0.08510410040616989,-0.01375699695199728,0.9919790625572205,0.10621267557144165,-0.09021031111478806,-0.017416801303625107,0.9900897145271301,0.11062882095575333,-0.0941975861787796,-0.024081744253635406,0.989094614982605,0.11217749863862991,-0.0947163850069046,-0.023327145725488663,0.988888680934906,0.1204184889793396,-0.09655239433050156,-0.020435938611626625,0.9878053665161133,0.12928611040115356,-0.10652511566877365,-0.01873079128563404,0.9856910109519958,0.12865707278251648,-0.1219424456357956,-0.015130111947655678,0.984046995639801,0.11947978287935257,-0.1329362541437149,-0.007558829616755247,0.9838675856590271,0.10304538160562515,-0.13115429878234863,-0.003865857608616352,0.9859844446182251,0.08514732122421265,-0.11729063093662262,-0.002842104295268655,0.9894366264343262,0.0698443353176117,-0.09821836650371552,0.0002540975983720273,0.9927108883857727,0.054240789264440536,-0.07799236476421356,0.005849629640579224,0.9954601526260376,0.038063839077949524,-0.06138286739587784,0.007970214821398258,0.9973564147949219,0.03235621377825737,-0.03983147442340851,-0.00045397182111628354,0.9986822605133057,0.04084673151373863,-0.007318219635635614,0.007420629728585482,0.9991111159324646,0.035396359860897064,-0.0005915934452787042,0.02648022398352623,0.9990223050117493,0.01879848912358284,-0.01646837592124939,0.029392637312412262,0.9992554783821106,0.01664191298186779,-0.013266609981656075,0.03292597457766533,0.9992311596870422,0.026003943756222725,-0.004348788410425186,0.03582264855504036,0.9990103244781494,0.027300529181957245,-0.006613033823668957,0.03385571762919426,0.9990319013595581,0.025515800341963768,-0.009441731497645378,0.0354456789791584,0.9990012049674988,0.029708044603466988,-0.007929092273116112,0.03717109188437462,0.9988357424736023,0.03581644222140312,-0.007321818266063929,0.03820759803056717,0.9986009001731873,0.04076453670859337,-0.007358923554420471,0.03989501670002937,0.998344898223877,0.04645244777202606,-0.0059371888637542725,0.039771340787410736,0.998110830783844,0.05243699625134468,-0.0032786978408694267,0.0378076545894146,0.9979029297828674,0.05652759596705437,0.0014314226573333144,0.03331572934985161,0.9978440403938293,0.05955236405134201,0.005361103918403387,0.02564266137778759,0.9978813529014587,0.06322214007377625,0.004578703083097935,0.019889410585165024,0.9977908134460449,0.0695861205458641,-0.004106973763555288,0.01809930056333542,0.9974032640457153,0.07406783103942871,-0.026849428191781044,0.010812323540449142,0.996833086013794,0.06560071557760239,-0.06049949675798416,-0.007910707965493202,0.9959787726402283,0.04548506438732147,-0.08748844265937805,-0.024959798902273178,0.9948135018348694,0.030482087284326553,-0.09598635882139206,-0.025745408609509468,0.9945826530456543,0.0447363518178463,-0.09420090168714523,-0.026339830830693245,0.994198739528656,0.07796135544776917,-0.09219783544540405,-0.026873737573623657,0.99232017993927,0.0919375941157341,-0.0886913388967514,-0.029014119878411293,0.9913826584815979,0.09172911942005157,-0.07998056709766388,-0.022966941818594933,0.9923010468482971,0.09775032848119736,-0.06441761553287506,-0.012539696879684925,0.9930447936058044,0.10265285521745682,-0.04928538203239441,-0.006156052462756634,0.9934764504432678,0.09668637812137604,-0.03717149421572685,-0.004459953401237726,0.9946105480194092,0.08972612023353577,-0.028895726427435875,-0.0034881604369729757,0.9955410957336426,0.08934248238801956,-0.02921411767601967,-0.0008234319393523037,0.9955720901489258,0.09431915730237961,-0.03484097123146057,0.000019223529307055287,0.9949321746826172,0.09359905868768692,-0.03582661971449852,-0.006983919534832239,0.9949406385421753,0.08392776548862457,-0.03202693164348602,-0.013585437089204788,0.9958644509315491,0.0743946060538292,-0.02594010718166828,-0.016691865399479866,0.9967516660690308,0.07108352333307266,-0.02361973561346531,-0.025140002369880676,0.9968737363815308,0.07037121057510376,-0.02288941852748394,-0.040397025644779205,0.9964396953582764,0.0657336562871933,-0.024440020322799683,-0.05539358779788017,0.9959986805915833,0.07223410904407501,-0.03162417560815811,-0.05581234395503998,0.9953226447105408,0.09296725690364838,-0.030861401930451393,-0.04756695404648781,0.994053304195404,0.1047293096780777,-0.028322333469986916,-0.047785334289073944,0.9929482340812683,0.10698941349983215,-0.03371662274003029,-0.05216500535607338,0.9923181533813477,0.10810086131095886,-0.04004408046603203,-0.05313677713274956,0.9919108748435974,0.10737264156341553,-0.041615135967731476,-0.053834185004234314,0.9918876886367798,0.10615109652280807,-0.04167027771472931,-0.055358320474624634,0.9919329881668091,0.1067880243062973,-0.04221184179186821,-0.05497168377041817,0.9918631911277771,0.10728263109922409,-0.044118739664554596,-0.05244474485516548,0.9918636679649353,0.10696964710950851,-0.047010596841573715,-0.050330813974142075,0.9918741583824158,0.1073882132768631,-0.05010667443275452,-0.05074409767985344,0.9916562438011169],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,2.366666555404663],"values":[1,0.9999998211860657,0.9999998807907104,1,1,0.9999997615814209],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,2.366666555404663],"values":[4.366398811340332,7.562900543212891,-0.17679674923419952,4.366401672363281,7.562900066375732,-0.17679834365844727],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.5658078789710999,0.5273339748382568,-0.4419296383857727,0.45439913868904114,0.5667206645011902,0.5256357789039612,-0.4380774199962616,0.4589366912841797,0.5625109076499939,0.5286145210266113,-0.4321818947792053,0.46622633934020996,0.5526778697967529,0.5361678004264832,-0.4267773926258087,0.47427043318748474,0.5390728712081909,0.5475080013275146,-0.42133817076683044,0.48177769780158997,0.5242277979850769,0.5599597096443176,-0.41321641206741333,0.4907979369163513,0.5117040872573853,0.5702013373374939,-0.4014010429382324,0.5019030570983887,0.5043953657150269,0.5738520622253418,-0.3874676823616028,0.5158953070640564,0.4993456304073334,0.5759389996528625,-0.37494131922721863,0.5276052951812744,0.49420174956321716,0.5823749899864197,-0.3665519952774048,0.5312661528587341,0.49071964621543884,0.5890454649925232,-0.35978224873542786,0.5317673683166504,0.47883957624435425,0.6000111103057861,-0.35641300678253174,0.5326060056686401,0.4609313905239105,0.6131828427314758,-0.3535084128379822,0.5353325605392456,0.463155061006546,0.61082923412323,-0.3409736752510071,0.5441617965698242,0.4900197684764862,0.5921075344085693,-0.3250235915184021,0.5510435700416565,0.5140687227249146,0.575678288936615,-0.31978461146354675,0.5496050715446472,0.5175370573997498,0.5732370018959045,-0.31774771213531494,0.5500828623771667,0.5070420503616333,0.5770283341407776,-0.3101699650287628,0.5601261854171753,0.4989139139652252,0.5774235725402832,-0.30700376629829407,0.5686964392662048,0.48789703845977783,0.5847508907318115,-0.31925439834594727,0.5640032291412354,0.46628764271736145,0.6023537516593933,-0.34051284193992615,0.5511776804924011,0.4723166227340698,0.5958763360977173,-0.3617461323738098,0.5394332408905029,0.5226346850395203,0.5508105158805847,-0.38469094038009644,0.5248558521270752,0.5558763742446899,0.5139292478561401,-0.4153546690940857,0.504339873790741,0.5584964752197266,0.5044974088668823,-0.44760212302207947,0.48292484879493713,0.5592677593231201,0.4973956048488617,-0.4560309648513794,0.4815111756324768,0.5616610646247864,0.4937177896499634,-0.4531199336051941,0.48524415493011475,0.5601697564125061,0.49670976400375366,-0.45642179250717163,0.48079976439476013,0.5575159192085266,0.49875107407569885,-0.45740634202957153,0.480835497379303,0.5553380250930786,0.500281035900116,-0.4572941064834595,0.48187199234962463,0.5544078946113586,0.501014232635498,-0.4571743905544281,0.4822947382926941,0.5532804727554321,0.5018006563186646,-0.4564346969127655,0.48347094655036926,0.5508542656898499,0.506542444229126,-0.45588192343711853,0.4818153977394104,0.5520538687705994,0.5112796425819397,-0.4546016752719879,0.4766204357147217,0.5573558211326599,0.5110817551612854,-0.4543150067329407,0.47090113162994385,0.5600179433822632,0.5107241868972778,-0.4565652012825012,0.46592798829078674,0.552288830280304,0.5187386274337769,-0.46035879850387573,0.4625548720359802,0.5271072387695312,0.5434896945953369,-0.4622015357017517,0.4616782069206238,0.48182201385498047,0.5830456614494324,-0.4558265507221222,0.4691775441169739,0.43690434098243713,0.6157891750335693,-0.4367416203022003,0.48905524611473083,0.4218180477619171,0.6247028112411499,-0.40834569931030273,0.5148493647575378,0.44042232632637024,0.6097624897956848,-0.37937265634536743,0.538789689540863,0.4808533489704132,0.5753882527351379,-0.36063894629478455,0.5546602606773376,0.5369564294815063,0.5277248620986938,-0.3522898852825165,0.5559461712837219,0.6019126772880554,0.47535252571105957,-0.340067595243454,0.5441462397575378,0.6506725549697876,0.43651366233825684,-0.32774507999420166,0.5278865694999695,0.664233386516571,0.4294446110725403,-0.33575665950775146,0.5115064382553101,0.6596869230270386,0.4398217797279358,-0.358550101518631,0.4927593767642975,0.6572802662849426,0.4459378123283386,-0.37614935636520386,0.47710981965065,0.657070517539978,0.44613248109817505,-0.3878435790538788,0.4677622318267822,0.6486409306526184,0.4539555609226227,-0.40049219131469727,0.4612974524497986,0.6305906772613525,0.47236207127571106,-0.4116373360157013,0.45802199840545654,0.6121342182159424,0.4912337064743042,-0.41458603739738464,0.4605426788330078,0.5979695320129395,0.506667971611023,-0.4096445143222809,0.46680980920791626,0.5862476825714111,0.5177589654922485,-0.40797826647758484,0.4709492325782776,0.5753552317619324,0.5259878635406494,-0.4123702049255371,0.4714382588863373,0.5578610897064209,0.5383371710777283,-0.4214586317539215,0.4704855680465698,0.5328391790390015,0.5568575263023376,-0.4319188594818115,0.46844246983528137,0.514219343662262,0.5710325241088867,-0.4422150254249573,0.4625433385372162,0.5212128758430481,0.5586614012718201,-0.4562683403491974,0.4561293125152588,0.5387603640556335,0.5349498987197876,-0.46201321482658386,0.45837730169296265,0.538044273853302,0.5399383902549744,-0.4578443765640259,0.4575517773628235,0.5360424518585205,0.5516260862350464,-0.4551733434200287,0.44853609800338745,0.5485691428184509,0.5450771450996399,-0.45381370186805725,0.4427368640899658,0.5647251605987549,0.5314284563064575,-0.4496276378631592,0.4432882070541382,0.5704072117805481,0.5261846780776978,-0.4485803544521332,0.443329393863678,0.5672550201416016,0.5289813876152039,-0.45092880725860596,0.44166016578674316,0.5643130540847778,0.5319388508796692,-0.4509805142879486,0.4418239891529083,0.5649493932723999,0.5308098196983337,-0.44802311062812805,0.44536322355270386,0.566274106502533,0.5284469127655029,-0.44469720125198364,0.44980207085609436,0.5658079981803894,0.5273338556289673,-0.4419296681880951,0.45439910888671875],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999996423721313,0.9999997019767761,0.9999995827674866,0.9999997615814209,0.9999996423721313,0.9999997019767761],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,2.366666555404663],"values":[0.000006295089860941516,17.630796432495117,-0.0000019326353140058927,0.000004292594894650392,17.630796432495117,0.000013218588719610125],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.21559692919254303,-0.09361796826124191,0.11871787905693054,0.96470707654953,0.20559917390346527,-0.08945940434932709,0.12615828216075897,0.9663385152816772,0.19183838367462158,-0.08049722760915756,0.13316863775253296,0.9690120816230774,0.1710624396800995,-0.0721580907702446,0.1354137361049652,0.9732388854026794,0.1483997404575348,-0.07348840683698654,0.13194765150547028,0.9773263335227966,0.1329604983329773,-0.08992242813110352,0.1264086812734604,0.9789056777954102,0.12223244458436966,-0.12085498869419098,0.11299712210893631,0.9786137938499451,0.10178287327289581,-0.14228662848472595,0.0817720964550972,0.9811769127845764,0.0793965682387352,-0.16066621243953705,0.04456117004156113,0.9828005433082581,0.06273246556520462,-0.1968168020248413,0.015242645516991615,0.9783125519752502,0.034312374889850616,-0.20062726736068726,-0.013722360134124756,0.9789704084396362,-0.002344186184927821,-0.13838055729866028,-0.025856774300336838,0.9900388121604919,-0.028034502640366554,-0.022777199745178223,0.004108424298465252,0.9993389844894409,-0.0388934500515461,0.08278155326843262,0.03219985216856003,0.9952877759933472,-0.05611664801836014,0.07343066483736038,0.038246143609285355,0.9949855208396912,-0.08866411447525024,-0.051313482224941254,0.0998263955116272,0.9897173047065735,-0.09596332907676697,-0.187800794839859,0.24271751940250397,0.9468950033187866,-0.043018441647291183,-0.2757781445980072,0.3772731423377991,0.8830406665802002,0.023592883720993996,-0.25345078110694885,0.48401230573654175,0.8372204899787903,0.039797425270080566,-0.13708215951919556,0.5929623246192932,0.7924773693084717,0.04885391518473625,-0.027162525802850723,0.7065070867538452,0.7054950594902039,0.07319768518209457,0.08979678153991699,0.7628355622291565,0.6361293792724609,0.08670294284820557,0.22742211818695068,0.7374516725540161,0.6300212740898132,0.10061061382293701,0.30161428451538086,0.7023418545722961,0.6368848085403442,0.12412618845701218,0.2898288071155548,0.695466935634613,0.6456916928291321,0.1257999986410141,0.2687588036060333,0.6894668340682983,0.6607409119606018,0.09994617104530334,0.2692621648311615,0.6911808848381042,0.6631573438644409,0.08809364587068558,0.26891466975212097,0.6951331496238708,0.6608436703681946,0.09162484854459763,0.264125794172287,0.6933160424232483,0.6641952991485596,0.08714574575424194,0.2607864737510681,0.694736659526825,0.6646330952644348,0.08345334231853485,0.25718382000923157,0.6952863335609436,0.6659346222877502,0.09155488759279251,0.2511555850505829,0.6920070052146912,0.6705706119537354,0.11550403386354446,0.2484763264656067,0.6839402318000793,0.6761243343353271,0.16538023948669434,0.25704097747802734,0.6493920087814331,0.6963256597518921,0.2459908127784729,0.2644020617008209,0.5638591647148132,0.7427266836166382,0.32984721660614014,0.25569385290145874,0.4403364062309265,0.7949373126029968,0.35730409622192383,0.23507866263389587,0.3260192573070526,0.8430796265602112,0.30199456214904785,0.2067861706018448,0.2633829414844513,0.8925626873970032,0.19305859506130219,0.17399300634860992,0.28465351462364197,0.9227281212806702,0.061004169285297394,0.13226303458213806,0.3739883005619049,0.9159245491027832,-0.07085104286670685,0.08420580625534058,0.47664129734039307,0.8721826672554016,-0.16776219010353088,0.06962910294532776,0.5705607533454895,0.8009170293807983,-0.1955176442861557,0.11381487548351288,0.6492745280265808,0.7261278033256531,-0.14750716090202332,0.18752479553222656,0.6814002990722656,0.691931962966919,-0.05018601194024086,0.25190213322639465,0.6455060839653015,0.7192694544792175,0.05968894809484482,0.2972346544265747,0.5813460350036621,0.7550666928291321,0.14800527691841125,0.3257502317428589,0.5500078201293945,0.7546341419219971,0.20121192932128906,0.33771058917045593,0.5456971526145935,0.7400540113449097,0.2315262258052826,0.3393344581127167,0.524726152420044,0.7455938458442688,0.2523064613342285,0.3479197025299072,0.478412926197052,0.765777051448822,0.2645793557167053,0.37402334809303284,0.4279467463493347,0.7790801525115967,0.27193042635917664,0.39545753598213196,0.3764936327934265,0.7924138903617859,0.2823016941547394,0.38879629969596863,0.32281237840652466,0.8154357075691223,0.2976200580596924,0.3567621111869812,0.2754899859428406,0.8415749669075012,0.31199830770492554,0.30939143896102905,0.23573724925518036,0.8668113946914673,0.31868094205856323,0.25407108664512634,0.1942042112350464,0.8922864198684692,0.32534509897232056,0.18660573661327362,0.15658524632453918,0.9136793613433838,0.327821284532547,0.10929004848003387,0.14409677684307098,0.9272675514221191,0.30073267221450806,0.03439195454120636,0.157693013548851,0.9399521350860596,0.2529279887676239,-0.0369340255856514,0.16599398851394653,0.9524228572845459,0.23686932027339935,-0.1033444032073021,0.15158623456954956,0.9540621042251587,0.25347569584846497,-0.13670885562896729,0.1572054624557495,0.9446413516998291,0.25210314989089966,-0.11895041167736053,0.160154327750206,0.9469136595726013,0.23481673002243042,-0.0945112407207489,0.14626504480838776,0.9563133716583252,0.2253614217042923,-0.09622446447610855,0.12311102449893951,0.961663544178009,0.2260553240776062,-0.10721567273139954,0.10980125516653061,0.9619498252868652,0.23082010447978973,-0.11387059837579727,0.10886174440383911,0.9601587057113647,0.2318461686372757,-0.11384577304124832,0.11338037252426147,0.959391176700592,0.2295295149087906,-0.10513513535261154,0.11490630358457565,0.9607598185539246,0.2237282544374466,-0.09635542333126068,0.11679502576589584,0.9628189206123352,0.215597003698349,-0.09361793100833893,0.11871756613254547,0.96470707654953],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,2.366666555404663],"values":[1.0000001192092896,1,1,0.9999999403953552,1.0000001192092896,0.9999998807907104],"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,2.366666555404663],"values":[-0.0000016670730929035926,17.82781410217285,0.000009036722985911183,6.358651489790645e-7,17.827802658081055,0.000011590133908612188],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[-0.05377151444554329,-0.1183939054608345,0.29592275619506836,0.9463198184967041,-0.051953066140413284,-0.11878470331430435,0.28591498732566833,0.9494438767433167,-0.049732107669115067,-0.11924217641353607,0.27369174361228943,0.9531006813049316,-0.048415686935186386,-0.11950318515300751,0.2664473354816437,0.9551862478256226,-0.04850125312805176,-0.11948640644550323,0.26691827178001404,0.9550524950027466,-0.04874871298670769,-0.1194378212094307,0.2682804465293884,0.9546642303466797,-0.04938770458102226,-0.11931119859218597,0.2717973589897156,0.9536519050598145,-0.05060693249106407,-0.11906461417675018,0.2785065770149231,0.9516809582710266,-0.051923561841249466,-0.11879093945026398,0.2857522666454315,0.9494937062263489,-0.055145639926195145,-0.11808866262435913,0.3034844696521759,0.9438809156417847,-0.060049958527088165,-0.11692969501018524,0.3304744064807892,0.9346165657043457,-0.06035294383764267,-0.1168544813990593,0.332141637802124,0.9340152740478516,-0.050328660756349564,-0.11912139505147934,0.2769753336906433,0.9521353840827942,-0.03600451350212097,-0.12159848213195801,0.19814512133598328,0.9719341397285461,-0.02644006349146366,-0.12277679145336151,0.14550869166851044,0.9813531637191772,-0.016331912949681282,-0.12362290918827057,0.08988023549318314,0.9881154894828796,-0.004333384335041046,-0.12410549074411392,0.02384806238114834,0.9919729828834534,-0.009395170025527477,-0.12397042661905289,0.05170488730072975,0.9908933639526367,-0.04127773642539978,-0.1207878440618515,0.22716480493545532,0.965454638004303,-0.07750952243804932,-0.11186828464269638,0.4265606701374054,0.8941609263420105,-0.1012505441904068,-0.10231439769268036,0.5572150349617004,0.8177967071533203,-0.11785097420215607,-0.09335148334503174,0.6485726833343506,0.7461569905281067,-0.13066968321800232,-0.0847257524728775,0.7191182971000671,0.6772117614746094,-0.13622282445430756,-0.08039815723896027,0.7496791481971741,0.6426202058792114,-0.13583260774612427,-0.08071564137935638,0.7475324869155884,0.6451586484909058,-0.13460592925548553,-0.08170059323310852,0.7407815456390381,0.6530306339263916,-0.1349247246980667,-0.08144663274288177,0.7425355315208435,0.6510013937950134,-0.1360589861869812,-0.08053181320428848,0.7487779259681702,0.6436879634857178,-0.13666962087154388,-0.08003169298171997,0.7521376609802246,0.6396915912628174,-0.1365918219089508,-0.08009569346904755,0.7517103552818298,0.6402022242546082,-0.13679251074790955,-0.079930379986763,0.7528145909309387,0.6388811469078064,-0.1373092383146286,-0.07950194925069809,0.7556586265563965,0.6354570388793945,-0.137670636177063,-0.07919995486736298,0.7576475143432617,0.6330438256263733,-0.13862861692905426,-0.07839026302099228,0.762919008731842,0.6265713572502136,-0.14073866605758667,-0.07655585557222366,0.774531364440918,0.6119092702865601,-0.14204619824886322,-0.07538245618343353,0.7817275524139404,0.6025300621986389,-0.1421556919813156,-0.07528294622898102,0.7823296785354614,0.6017346382141113,-0.14240509271621704,-0.07505523413419724,0.7837032675743103,0.5999139547348022,-0.14151257276535034,-0.0758647695183754,0.7787907719612122,0.6063857078552246,-0.13949184119701385,-0.07764826714992523,0.7676701545715332,0.6206411123275757,-0.1368086189031601,-0.07991708070039749,0.7529033422470093,0.6387748122215271,-0.13408151268959045,-0.08211533725261688,0.7378948926925659,0.6563462018966675,-0.1329921931028366,-0.08296506106853485,0.731900691986084,0.6631374955177307,-0.13489682972431183,-0.08146891742944717,0.742382287979126,0.6511790752410889,-0.14034776389598846,-0.07690105587244034,0.7723803520202637,0.6146685481071472,-0.1441604048013687,-0.07342185080051422,0.7933621406555176,0.5868590474128723,-0.1413058638572693,-0.07605036348104477,0.7776533365249634,0.6078686714172363,-0.13502945005893707,-0.08136293292045593,0.743112325668335,0.6503316164016724,-0.13208363950252533,-0.08366190642118454,0.7269005179405212,0.6687079071998596,-0.1339935064315796,-0.08218443393707275,0.7374110817909241,0.656899094581604,-0.13604706525802612,-0.08054147660732269,0.7487121224403381,0.6437658667564392,-0.1347162425518036,-0.08161286264657974,0.7413888573646545,0.6523292660713196,-0.12991702556610107,-0.08528207242488861,0.7149765491485596,0.681657612323761,-0.12154468894004822,-0.09103921800851822,0.668900728225708,0.7276747822761536,-0.11030782014131546,-0.09769503772258759,0.6070601344108582,0.7808750867843628,-0.10083550214767456,-0.10251181572675705,0.5549311637878418,0.8193747401237488,-0.09547272324562073,-0.1049584373831749,0.5254181623458862,0.8389305472373962,-0.08922965079545975,-0.10757437348365784,0.491060733795166,0.8598402142524719,-0.07652014493942261,-0.11219621449708939,0.4211161136627197,0.8967819809913635,-0.061133936047554016,-0.1166585311293602,0.33644095063209534,0.9324489235877991,-0.052236318588256836,-0.11872483044862747,0.2874736189842224,0.9489650726318359,-0.04847560450434685,-0.11949145048856735,0.26677778363227844,0.9550924301147461,-0.051947422325611115,-0.11878591030836105,0.28588414192199707,0.9494533538818359,-0.05600018799304962,-0.11789461970329285,0.3081880211830139,0.9423295855522156,-0.057874683290719986,-0.1174573227763176,0.3185027241706848,0.938834547996521,-0.05850430577993393,-0.11730684340000153,0.3219693899154663,0.9376310706138611,-0.058459214866161346,-0.11731769889593124,0.3217211365699768,0.9377177357673645,-0.05689851939678192,-0.11768707633018494,0.3131323754787445,0.9406701922416687,-0.05556420609354973,-0.11799409985542297,0.3057885468006134,0.9431242942810059,-0.05451603978872299,-0.11822957545518875,0.30002009868621826,0.9450067281723022,-0.05377170071005821,-0.11839380860328674,0.295923113822937,0.9463197588920593],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998211860657,1.0000001192092896,0.9999997019767761,0.9999998211860657,1,0.9999998211860657],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,2.366666555404663],"values":[-9.201628472510492e-7,18.75110626220703,-0.000005832745500811143,0.000002356494178457069,18.751102447509766,0.00000784968779043993],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[0.0550377182662487,0.05196232721209526,-0.013945179991424084,0.9970337748527527,0.055037710815668106,0.051962342113256454,-0.01394517533481121,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998807907104,1,1,0.9999999403953552,1,1],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,2.366666555404663],"values":[-4.513198375701904,8.246405601501465,-0.08129886537790298,-4.5131964683532715,8.246397972106934,-0.08130292594432831],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.4565064311027527,-0.6244624853134155,0.45442914962768555,0.44174954295158386,0.459281861782074,-0.621953547000885,0.45649775862693787,0.44027695059776306,0.4595990478992462,-0.6214438080787659,0.4587477743625641,0.43832269310951233,0.45411062240600586,-0.6250835061073303,0.4606172442436218,0.43690499663352966,0.445830762386322,-0.6312334537506104,0.46445637941360474,0.4325038492679596,0.4348522126674652,-0.6396192312240601,0.47287026047706604,0.4221190810203552,0.4249207675457001,-0.6479962468147278,0.48089268803596497,0.41022616624832153,0.42844274640083313,-0.6459505558013916,0.48538535833358765,0.4044574201107025,0.4312297999858856,-0.6444581747055054,0.48847338557243347,0.400135338306427,0.4230100214481354,-0.6541257500648499,0.48660707473754883,0.39546895027160645,0.4090695083141327,-0.6693064570426941,0.4814160466194153,0.3910621702671051,0.3970389664173126,-0.6808874607086182,0.4778187870979309,0.38786786794662476,0.3898800313472748,-0.687588095664978,0.4752097427845001,0.38651251792907715,0.39653605222702026,-0.6848151683807373,0.47716885805130005,0.38222670555114746,0.4224238395690918,-0.6699351072311401,0.48295390605926514,0.37349778413772583,0.4558908939361572,-0.6478781700134277,0.4896146059036255,0.3642732501029968,0.48947593569755554,-0.6202523112297058,0.5041247606277466,0.34865275025367737,0.5165627598762512,-0.5906627178192139,0.5251855850219727,0.32933351397514343,0.5342945456504822,-0.5654429197311401,0.5402798056602478,0.3207826316356659,0.5555747151374817,-0.5393515825271606,0.5417554974555969,0.3270130753517151,0.5681421160697937,-0.5265986323356628,0.5291634202003479,0.34625786542892456,0.546536922454834,-0.5421581268310547,0.5235841274261475,0.3649953007698059,0.5317410230636597,-0.5467029213905334,0.5184931755065918,0.3866939842700958,0.5458575487136841,-0.5276455283164978,0.4855020046234131,0.43349456787109375,0.535869300365448,-0.5290356278419495,0.4464206397533417,0.4833983778953552,0.5029910802841187,-0.5506077408790588,0.44387149810791016,0.49679893255233765,0.5004816651344299,-0.5535081624984741,0.4498364329338074,0.49070775508880615,0.5074925422668457,-0.5490667819976807,0.45064210891723633,0.48774874210357666,0.5056555867195129,-0.5491053462028503,0.450466513633728,0.48977115750312805,0.5021635293960571,-0.5518843531608582,0.4480322301387787,0.4924658238887787,0.5027858018875122,-0.5517128109931946,0.44992712140083313,0.4902908205986023,0.5032787919044495,-0.5509555339813232,0.44974285364151,0.49080532789230347,0.5068343281745911,-0.5504265427589417,0.44827279448509216,0.48908188939094543,0.5105918049812317,-0.5527045726776123,0.44836854934692383,0.48247209191322327,0.512127161026001,-0.5555574893951416,0.44917577505111694,0.4767838418483734,0.5104404091835022,-0.5585960149765015,0.45014744997024536,0.47411853075027466,0.49401533603668213,-0.5716040134429932,0.4467286765575409,0.47921931743621826,0.47624412178993225,-0.585333526134491,0.43384823203086853,0.49229246377944946,0.47527068853378296,-0.5859413743019104,0.4303492307662964,0.4955703914165497,0.4680526554584503,-0.5921131372451782,0.4454881250858307,0.4815279245376587,0.4752959907054901,-0.5912531614303589,0.4720934331417084,0.449044793844223,0.4945831596851349,-0.5808514952659607,0.4993992745876312,0.41060855984687805,0.4715152680873871,-0.5975611209869385,0.5303446054458618,0.3732675313949585,0.48923400044441223,-0.5735934376716614,0.5585218667984009,0.34596824645996094,0.5588821768760681,-0.5139648914337158,0.5507381558418274,0.3466675877571106,0.5849733352661133,-0.5031235218048096,0.5307597517967224,0.35066649317741394,0.5756561756134033,-0.5241900682449341,0.5112383365631104,0.36397820711135864,0.5645285844802856,-0.5416232347488403,0.4900455176830292,0.3844568133354187,0.5582222938537598,-0.551821768283844,0.4720594882965088,0.4012985825538635,0.5508587956428528,-0.559441328048706,0.46104133129119873,0.41354668140411377,0.5511007905006409,-0.5575263500213623,0.45392847061157227,0.42355796694755554,0.5650498270988464,-0.5429418087005615,0.4476952850818634,0.43069928884506226,0.5752680897712708,-0.5324230194091797,0.44935494661331177,0.42857033014297485,0.5683627724647522,-0.5398447513580322,0.45546793937683105,0.4219956696033478,0.5545297861099243,-0.5527442097663879,0.45643147826194763,0.4226592779159546,0.5420558452606201,-0.5618897676467896,0.4572655260562897,0.42586806416511536,0.5279812812805176,-0.5694939494132996,0.4588242471218109,0.4317322075366974,0.5127229690551758,-0.5778133273124695,0.4562009871006012,0.4417324662208557,0.5076854825019836,-0.5798715949058533,0.4469583034515381,0.4541284441947937,0.4982549846172333,-0.5814831256866455,0.4459354281425476,0.4634231626987457,0.48425865173339844,-0.5859834551811218,0.45515742897987366,0.46362563967704773,0.4897533655166626,-0.5850211381912231,0.45126810669898987,0.46287044882774353,0.4950045943260193,-0.5892962217330933,0.44178128242492676,0.46100950241088867,0.48017653822898865,-0.6058018803596497,0.43953073024749756,0.45743557810783386,0.4617270231246948,-0.6220039129257202,0.4398978650569916,0.45432284474372864,0.4559035003185272,-0.6269614100456238,0.4398859739303589,0.45340028405189514,0.45735031366348267,-0.6257597804069519,0.4406694173812866,0.45284202694892883,0.45885252952575684,-0.6247560381889343,0.44244471192359924,0.45097336173057556,0.45890048146247864,-0.6244493126869202,0.44577884674072266,0.44805654883384705,0.4580296277999878,-0.6243954300880432,0.45044103264808655,0.44434452056884766,0.4565061926841736,-0.6244626045227051,0.4544290602207184,0.441749632358551],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999999403953552,0.9999999403953552,1,0.9999999403953552,0.9999998807907104,1],"interpolation":2300,"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,2.366666555404663],"values":[-0.000004963595984008862,17.9213924407959,-0.000001139137907557597,0.000001272899112336745,17.921398162841797,-0.000010839445167221129],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.4208981394767761,-0.4546758532524109,-0.07310886681079865,0.7815175652503967,0.4260668456554413,-0.4474005401134491,-0.07334012538194656,0.7828927040100098,0.42961084842681885,-0.44316911697387695,-0.07714870572090149,0.782996654510498,0.4347398281097412,-0.4416750967502594,-0.08389066904783249,0.7803119421005249,0.4410848617553711,-0.43814215064048767,-0.08582388609647751,0.7785305976867676,0.43948227167129517,-0.4377157390117645,-0.08840300887823105,0.7793877124786377,0.43144282698631287,-0.4448715150356293,-0.09056830406188965,0.7795792818069458,0.4215635359287262,-0.458818256855011,-0.0684661716222763,0.7791549563407898,0.4070776402950287,-0.4791933298110962,-0.04089948534965515,0.7765235304832458,0.38941118121147156,-0.5023704171180725,-0.036121878772974014,0.7711537480354309,0.3653333783149719,-0.5295098423957825,-0.060340698808431625,0.7632233500480652,0.35232439637184143,-0.5487764477729797,-0.0924699455499649,0.7524368762969971,0.3622359335422516,-0.554652750492096,-0.11462979763746262,0.7402739524841309,0.37931886315345764,-0.5469388365745544,-0.11929050832986832,0.7367122173309326,0.39894312620162964,-0.525970995426178,-0.11150887608528137,0.7428086400032043,0.421205073595047,-0.4984228312969208,-0.10688380151987076,0.7501578330993652,0.4363502562046051,-0.4632224440574646,-0.10354211926460266,0.7643967866897583,0.44303110241889954,-0.4199104309082031,-0.09294198453426361,0.7866133451461792,0.44126465916633606,-0.37126845121383667,-0.08075524121522903,0.8129722476005554,0.40834760665893555,-0.30144304037094116,-0.04850393161177635,0.8602509498596191,0.33367645740509033,-0.19876717031002045,-0.003917197696864605,0.921485960483551,0.23076830804347992,-0.050598058849573135,-0.043950051069259644,0.970697820186615,0.12294428795576096,0.10391586273908615,-0.16911956667900085,0.9723604917526245,-0.021948248147964478,0.19931989908218384,-0.26725664734840393,0.9425305128097534,0.01616709679365158,0.3634517192840576,-0.1935025155544281,0.9111521244049072,0.0743054747581482,0.45235389471054077,-0.21201162040233612,0.8630792498588562,0.053604606539011,0.4736800193786621,-0.21790629625320435,0.8516282439231873,0.01871046982705593,0.49329331517219543,-0.2182397097349167,0.8418331146240234,-0.01574709080159664,0.47464218735694885,-0.231425479054451,0.8490636348724365,-0.03007097728550434,0.4597783088684082,-0.23647183179855347,0.8554418683052063,-0.03718111664056778,0.46669790148735046,-0.24880604445934296,0.847883403301239,-0.048667293041944504,0.46041709184646606,-0.25007542967796326,0.8503586649894714,-0.06754312664270401,0.43985965847969055,-0.2409026175737381,0.8625122308731079,-0.06629536300897598,0.4426700472831726,-0.23526151478290558,0.8627283573150635,-0.04526270925998688,0.4621028006076813,-0.22766174376010895,0.8559103012084961,-0.009879249148070812,0.488564133644104,-0.21363236010074615,0.8459129333496094,0.06572370231151581,0.5357944965362549,-0.18320691585540771,0.8216081261634827,0.14739814400672913,0.57913738489151,-0.11395891010761261,0.7936542630195618,0.19107192754745483,0.5742282867431641,-0.025748353451490402,0.7956698536872864,0.24335996806621552,0.5335081219673157,0.050216786563396454,0.8084697723388672,0.2880791425704956,0.5049184560775757,0.10203778743743896,0.8072521686553955,0.31967708468437195,0.459177166223526,0.12014105916023254,0.8200786709785461,0.39192983508110046,0.395079106092453,0.0467103086411953,0.8295310139656067,0.3962227404117584,0.3453584313392639,-0.05718252435326576,0.8488022685050964,0.3466225266456604,0.2884729206562042,-0.1403050720691681,0.8814480900764465,0.30832380056381226,0.17617319524288177,-0.28168052434921265,0.8913784623146057,0.25009793043136597,0.03843440115451813,-0.41027697920799255,0.8761544823646545,0.20012031495571136,-0.047681860625743866,-0.4655154049396515,0.8607982993125916,0.174544095993042,-0.10086146742105484,-0.47561657428741455,0.8562419414520264,0.15968766808509827,-0.16472575068473816,-0.47143790125846863,0.851534903049469,0.16041885316371918,-0.2227703034877777,-0.45627379417419434,0.8464357256889343,0.18888893723487854,-0.2589206397533417,-0.40486234426498413,0.8563688397407532,0.22182989120483398,-0.2947613000869751,-0.33885687589645386,0.8654959797859192,0.2591375708580017,-0.33807146549224854,-0.28405994176864624,0.8589909076690674,0.3132767975330353,-0.38281360268592834,-0.22659771144390106,0.8390262126922607,0.364976167678833,-0.42665159702301025,-0.15652677416801453,0.812563955783844,0.4049631357192993,-0.4602874517440796,-0.09818501770496368,0.7839004993438721,0.44002750515937805,-0.475957453250885,-0.057854097336530685,0.7592715620994568,0.4709681570529938,-0.480215460062027,-0.018529493361711502,0.7397559285163879,0.4759977459907532,-0.48324283957481384,-0.006047654431313276,0.7347556948661804,0.4488638937473297,-0.4836694300174713,-0.01796925999224186,0.7511738538742065,0.4272099435329437,-0.47360220551490784,-0.02576426789164543,0.769758939743042,0.43059879541397095,-0.4650800824165344,-0.02744217962026596,0.773002028465271,0.43753328919410706,-0.46621817350387573,-0.039744626730680466,0.7678708434104919,0.43935632705688477,-0.4714703857898712,-0.05239275097846985,0.7628477811813354,0.4370018541812897,-0.47782883048057556,-0.055275801569223404,0.7600352764129639,0.42943301796913147,-0.4814770817756653,-0.05768555402755737,0.7618657946586609,0.42173513770103455,-0.47933363914489746,-0.0625728964805603,0.7671136856079102,0.41820964217185974,-0.47235992550849915,-0.0650084838271141,0.7731433510780334,0.4176497161388397,-0.4637933075428009,-0.06794248521327972,0.7783625721931458,0.42089810967445374,-0.4546760022640228,-0.07310891896486282,0.781517505645752],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,2.366666555404663],"values":[1,0.9999999403953552,0.9999997615814209,0.9999999403953552,0.9999999403953552,0.9999997615814209],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,2.366666555404663],"values":[0.000004037404778500786,17.203702926635742,-0.000003027756065421272,0.0000012657910701818764,17.20370101928711,0.0000012329356877671671],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[-0.12425165623426437,0.11323418468236923,-0.6639229655265808,0.7286603450775146,-0.12332136183977127,0.11394063383340836,-0.6589516401290894,0.7332067489624023,-0.12259262055158615,0.11448732763528824,-0.6550576686859131,0.7367246150970459,-0.12215489894151688,0.11481290310621262,-0.652719259262085,0.7388192415237427,-0.12176810950040817,0.11509889364242554,-0.650651752948761,0.7406598925590515,-0.12012585997581482,0.11629506945610046,-0.6418766975402832,0.7483579516410828,-0.11684931814670563,0.11859852075576782,-0.6243689656257629,0.7631802558898926,-0.1144690066576004,0.12020523101091385,-0.6116501092910767,0.7735190391540527,-0.11416573822498322,0.12040597200393677,-0.6100285649299622,0.7748120427131653,-0.11530547589063644,0.11964680254459381,-0.6161195635795593,0.7699259519577026,-0.11781840026378632,0.11792855709791183,-0.6295472383499146,0.7588688731193542,-0.12086706608533859,0.11575859785079956,-0.6458374857902527,0.7449060678482056,-0.12434481829404831,0.11316317319869995,-0.6644189357757568,0.7282032370567322,-0.12933966517448425,0.10919217765331268,-0.6911086440086365,0.7026501297950745,-0.1349010169506073,0.10439793020486832,-0.7208245992660522,0.6717996001243591,-0.13946673274040222,0.1001298576593399,-0.7452216148376465,0.6443352103233337,-0.14314109086990356,0.09644917398691177,-0.7648544311523438,0.6206496357917786,-0.14723412692546844,0.09205568581819534,-0.7867257595062256,0.5923769474029541,-0.15233218669891357,0.08608180284500122,-0.8139665126800537,0.5539343953132629,-0.15753206610679626,0.07929228991270065,-0.8417518138885498,0.5102453231811523,-0.15987995266914368,0.07594715058803558,-0.8542970418930054,0.4887198209762573,-0.14933860301971436,0.08966241031885147,-0.7979705333709717,0.5769762992858887,-0.10366819053888321,0.12685039639472961,-0.5539376139640808,0.8162811994552612,-0.02329183928668499,0.1523214429616928,-0.12445641309022903,0.9801868200302124,-0.03818071633577347,0.1502133458852768,-0.2040134072303772,0.9666212797164917,-0.04790918529033661,0.14825798571109772,-0.2559957206249237,0.9540390372276306,-0.042744092643260956,0.14935420453548431,-0.22839775681495667,0.961093544960022,-0.042223867028951645,0.1494574099779129,-0.22561711072921753,0.9617570042610168,-0.039068836718797684,0.15005408227443695,-0.20875994861125946,0.9655965566635132,-0.03740691766142845,0.15034885704517365,-0.19987896084785461,0.9674938321113586,-0.037378981709480286,0.15035369992256165,-0.19972924888134003,0.967525064945221,-0.03635773807764053,0.15052804350852966,-0.19427162408828735,0.9686474800109863,-0.034463535994291306,0.15083836019039154,-0.184151291847229,0.9706433415412903,-0.03432001173496246,0.15086108446121216,-0.1833842396736145,0.9707900881767273,-0.03648626431822777,0.1505064070224762,-0.19495917856693268,0.9685078859329224,-0.04052618518471718,0.14978446066379547,-0.21654675900936127,0.9638619422912598,-0.04603797569870949,0.14867039024829865,-0.24599750339984894,0.956693708896637,-0.05246315896511078,0.14717979729175568,-0.28033024072647095,0.9471011757850647,-0.061191461980342865,0.1448124796152115,-0.3269689381122589,0.9318670630455017,-0.07013282924890518,0.14195941388607025,-0.3747453987598419,0.9135068655014038,-0.07836894690990448,0.13892506062984467,-0.41875335574150085,0.8939819931983948,-0.08461254835128784,0.13634920120239258,-0.45211586356163025,0.8774057626724243,-0.08233026415109634,0.13731934130191803,-0.4399212896823883,0.8836483359336853,-0.0801863968372345,0.13820050656795502,-0.4284648597240448,0.8893192410469055,-0.08295653760433197,0.13705644011497498,-0.44326749444007874,0.8819567561149597,-0.07944699376821518,0.13849776983261108,-0.42451396584510803,0.8912319540977478,-0.07853270322084427,0.13886070251464844,-0.4196294844150543,0.8935666680335999,-0.08465541899204254,0.13633063435554504,-0.45234546065330505,0.8772861957550049,-0.09377995878458023,0.13210418820381165,-0.5011012554168701,0.8500890731811523,-0.10430507361888885,0.12648612260818481,-0.5573403239250183,0.8139370083808899,-0.11088670790195465,0.12252283096313477,-0.592508852481842,0.7884323596954346,-0.11837467551231384,0.11753968149423599,-0.6325197815895081,0.7563667297363281,-0.12567725777626038,0.11213240772485733,-0.6715405583381653,0.7215710878372192,-0.12921947240829468,0.10929104685783386,-0.6904674768447876,0.70328688621521,-0.1297479271888733,0.10885385423898697,-0.6932911276817322,0.7004739046096802,-0.13006874918937683,0.10858701914548874,-0.6950043439865112,0.6987560987472534,-0.1298469603061676,0.10877159982919693,-0.6938197016716003,0.6999448537826538,-0.12873505055904388,0.10968896001577377,-0.6878783106803894,0.70584636926651,-0.12726877629756927,0.11087460815906525,-0.6800444722175598,0.7134767770767212,-0.1246383860707283,0.11293766647577286,-0.6659883260726929,0.7267530560493469,-0.12361835688352585,0.11371635645627975,-0.6605373024940491,0.7317633032798767,-0.12446703016757965,0.11306934058666229,-0.6650727987289429,0.7275997996330261,-0.12517966330051422,0.11251974105834961,-0.6688814163208008,0.7240628004074097,-0.12498882412910461,0.11266747862100601,-0.6678606867790222,0.7250144481658936,-0.12443812191486359,0.11309143900871277,-0.6649189591407776,0.7277418971061707,-0.124348483979702,0.11316010355949402,-0.6644392609596252,0.7281845211982727,-0.12459685653448105,0.11296950280666351,-0.6657673716545105,0.7269576191902161,-0.12469752132892609,0.11289206147193909,-0.6663052439689636,0.7264594435691833,-0.12463618069887161,0.11293954402208328,-0.6659760475158691,0.7267643809318542,-0.12447033077478409,0.11306677013635635,-0.665090799331665,0.7275832295417786,-0.12425175309181213,0.11323416233062744,-0.6639230251312256,0.7286602854728699],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999999403953552,1,0.9999999403953552,0.9999998807907104,0.9999999403953552,0.9999998211860657],"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,2.366666555404663],"values":[0.000002990329903695965,19.45049476623535,0.0000018145365174859762,7.675980668864213e-7,19.45050048828125,-0.0000011646509392448934],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[0.14937159419059753,-0.14748121798038483,-0.019200172275304794,0.9775319695472717,0.14937162399291992,-0.14748121798038483,-0.0192001610994339,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999998211860657,1,0.9999997615814209,0.9999998211860657,0.9999999403953552,0.9999997615814209],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,2.366666555404663],"values":[0.1469004601240158,6.409506320953369,0.2581053674221039,0.14690183103084564,6.40949821472168,0.25810104608535767],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[0.020259657874703407,0.01251528225839138,-0.011196221224963665,0.999653697013855,0.020259665325284004,0.012515293434262276,-0.011196216568350792,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999999403953552,1.0000003576278687,1,1.0000001192092896,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,2.366666555404663],"values":[-1.5140346931730164e-7,6.8158769607543945,0.0000024890289296308765,-2.129653466909076e-7,6.815885543823242,-4.140468377045181e-7],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663],"values":[0.03741994500160217,0.4198648929595947,-0.06366109102964401,0.9045775532722473,0.03298933431506157,0.4162146747112274,-0.0632641464471817,0.9064627289772034,0.022944550961256027,0.4114055037498474,-0.06145206838846207,0.9090889692306519,0.009271817281842232,0.40576016902923584,-0.060026615858078,0.911959171295166,-0.005960177164524794,0.40023452043533325,-0.060084179043769836,0.914421558380127,-0.01295596081763506,0.3918400704860687,-0.055217236280441284,0.9182834625244141,-0.005452015902847052,0.38326889276504517,-0.04516277462244034,0.9225159287452698,0.010113383643329144,0.37448859214782715,-0.03611625358462334,0.9264726638793945,0.021168945357203484,0.36466550827026367,-0.03324180096387863,0.930304229259491,0.025677267462015152,0.3614140748977661,-0.03691469877958298,0.9313204884529114,0.02879495918750763,0.36416304111480713,-0.04229161888360977,0.9299287796020508,0.028768105432391167,0.3558443486690521,-0.047973670065402985,0.9328696131706238,0.030328527092933655,0.3332633078098297,-0.05752125382423401,0.9405887126922607,0.03521589934825897,0.3081773519515991,-0.07011015713214874,0.9480881690979004,0.03766755387187004,0.27617818117141724,-0.0809706598520279,0.9569485187530518,0.037116117775440216,0.22907453775405884,-0.08478505164384842,0.9689988493919373,0.04338749125599861,0.1628437340259552,-0.07540122419595718,0.98280930519104,0.06022574380040169,0.0704067125916481,-0.05643674358725548,0.994097888469696,0.07422379404306412,-0.04208296909928322,-0.03189731389284134,0.9958425760269165,0.07809823006391525,-0.16623631119728088,0.0018240176141262054,0.9829866886138916,0.08046647906303406,-0.2963782548904419,0.043736476451158524,0.9506693482398987,0.10524676740169525,-0.4127706289291382,0.08099097013473511,0.9011015295982361,0.17590413987636566,-0.49251115322113037,0.10269591212272644,0.8461347818374634,0.22285261750221252,-0.5510895848274231,0.10903474688529968,0.7967109680175781,0.17571818828582764,-0.5665380954742432,0.10032772272825241,0.7988066673278809,0.14332273602485657,-0.519422709941864,0.09856002777814865,0.8366268873214722,0.14905354380607605,-0.5175561904907227,0.11322418600320816,0.8349245190620422,0.1424843966960907,-0.5422664880752563,0.11274406313896179,0.8203255534172058,0.14676497876644135,-0.5320159792900085,0.10993324965238571,0.8266400098800659,0.1508185714483261,-0.5229052901268005,0.11529306322336197,0.8309821486473083,0.14463123679161072,-0.5297492742538452,0.11171843856573105,0.8282309174537659,0.140457883477211,-0.5296642780303955,0.11353342980146408,0.8287566304206848,0.1377490758895874,-0.5267524719238281,0.12202824652194977,0.8298591375350952,0.13429726660251617,-0.5297738313674927,0.12410339713096619,0.8281921744346619,0.1274571418762207,-0.5347156524658203,0.12223523110151291,0.8263731002807617,0.11324497312307358,-0.540318489074707,0.12263285368680954,0.8247380256652832,0.09587705135345459,-0.5519763827323914,0.12266959249973297,0.8191958665847778,0.08420918881893158,-0.5718051195144653,0.12464562803506851,0.8064807653427124,0.08659745752811432,-0.5906389951705933,0.13190068304538727,0.7913587689399719,0.10774190723896027,-0.5998801589012146,0.14477775990962982,0.7794709205627441,0.13669909536838531,-0.6023455858230591,0.1623353213071823,0.7695066332817078,0.16087111830711365,-0.5975132584571838,0.17142760753631592,0.7666231393814087,0.17136496305465698,-0.5759938359260559,0.16861996054649353,0.7813018560409546,0.15777458250522614,-0.5248590707778931,0.15966050326824188,0.8210594654083252,0.14192096889019012,-0.43272143602371216,0.13909819722175598,0.8793533444404602,0.14272058010101318,-0.31311333179473877,0.10156349837779999,0.9334215521812439,0.13330914080142975,-0.19461216032505035,0.06114666536450386,0.9698535203933716,0.110863596200943,-0.0934082418680191,0.03504966199398041,0.9888153076171875,0.09334283322095871,-0.014386790804564953,0.01801661029458046,0.9953670501708984,0.08252087235450745,0.054771389812231064,-0.0017586832400411367,0.9950816035270691,0.07342912256717682,0.12190845608711243,-0.01564086601138115,0.9896978735923767,0.05774671584367752,0.18086959421634674,-0.01968875154852867,0.9816128611564636,0.03442135825753212,0.2231096476316452,-0.021942179650068283,0.9739382863044739,0.01908617466688156,0.24845540523529053,-0.025241898372769356,0.9681262969970703,0.019326094537973404,0.26983243227005005,-0.028833214193582535,0.9622814655303955,0.02700801193714142,0.3005309998989105,-0.03279751166701317,0.9528253078460693,0.029022278264164925,0.3369395136833191,-0.03705896437168121,0.9403489232063293,0.02126619964838028,0.36768656969070435,-0.03972378373146057,0.9288575649261475,0.010082995519042015,0.38856786489486694,-0.03739195317029953,0.9206058979034424,0.008597955107688904,0.40267249941825867,-0.033928822726011276,0.914674699306488,0.01484416238963604,0.4166939854621887,-0.04329533129930496,0.9078938364982605,0.017275618389248848,0.42934945225715637,-0.053846053779125214,0.9013663530349731,0.021426890045404434,0.4374972879886627,-0.058716338127851486,0.897044837474823,0.028306907042860985,0.4363954961299896,-0.06355037540197372,0.8970613479614258,0.034779228270053864,0.43173447251319885,-0.06797987967729568,0.8987627625465393,0.03861463814973831,0.4294110834598541,-0.07012978941202164,0.899553656578064,0.03744163364171982,0.4301852583885193,-0.07030875980854034,0.8992193341255188,0.03795938566327095,0.4303678274154663,-0.06764917820692062,0.8993143439292908,0.040502533316612244,0.4270561933517456,-0.06399302929639816,0.9010480046272278,0.04061029851436615,0.4228871464729309,-0.06253103166818619,0.9031097292900085,0.03741997107863426,0.4198650121688843,-0.0636611133813858,0.9045774936676025],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.9999997615814209,0.9999997615814209,0.9999998807907104,0.9999998211860657,0.9999998211860657,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,2.366666555404663],"values":[0.5746000409126282,51.816890716552734,-20.522703170776367,0.5746009945869446,51.81689453125,-20.522695541381836],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[-0.18743082880973816,9.801385658647632e-8,-0.005247783847153187,0.9822638034820557,-0.18743081390857697,8.42065830397587e-8,-0.005247786175459623,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,2.366666555404663],"values":[1.0000001192092896,1.0000003576278687,1,1.0000001192092896,1.0000003576278687,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,2.366666555404663],"values":[-0.5746005773544312,4.330288887023926,20.522703170776367,-0.5746001601219177,4.330292224884033,20.5227108001709],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[0.6296830773353577,-7.134944723929948e-8,0.017631007358431816,0.7766520380973816,0.6296830773353577,-8.454009758906977e-8,0.01763101853430271,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,2.366666555404663],"values":[1,1.0000003576278687,1.000000238418579,1,1.0000003576278687,1.0000003576278687],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,2.366666555404663],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,2.366666555404663],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,2.366666555404663],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"93638d80-4e34-4f65-8daf-a70328666672","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/_melee_combo_manifest.json b/public/anims/baked/sword_shield/_melee_combo_manifest.json new file mode 100644 index 0000000..ef1583a --- /dev/null +++ b/public/anims/baked/sword_shield/_melee_combo_manifest.json @@ -0,0 +1,104 @@ +{ + "version": 1, + "generated": "2026-08-08T21:52:59.399Z", + "clips": [ + { + "role": "attack1", + "file": "intoout.glb", + "duration": 1.7000000476837158, + "tracks": 75, + "source": "intoout.glb" + }, + { + "role": "attack2", + "file": "st1able-sword-inward-slash.glb", + "duration": 2.2333333492279053, + "tracks": 75, + "source": "st1able-sword-inward-slash.glb" + }, + { + "role": "attack3", + "file": "11upward-thrust.glb", + "duration": 2.366666555404663, + "tracks": 75, + "source": "11upward-thrust.glb" + }, + { + "role": "finisher-air", + "file": "dropto-target.glb", + "duration": 0.8333333134651184, + "tracks": 75, + "source": "dropto-target.glb" + }, + { + "role": "attack1-combo", + "file": "one-hand-combo-hit1.json", + "duration": 1.5222222010294597, + "tracks": 16, + "source": "one-hand-sword-combo.glb" + }, + { + "role": "attack2-combo", + "file": "one-hand-combo-hit2.json", + "duration": 1.5222222010294597, + "tracks": 16, + "source": "one-hand-sword-combo.glb" + }, + { + "role": "attack3-combo", + "file": "one-hand-combo-hit3.json", + "duration": 1.5222222010294595, + "tracks": 16, + "source": "one-hand-sword-combo.glb" + }, + { + "role": "combo-full", + "file": "one-hand-sword-combo.glb", + "duration": 4.566666603088379, + "tracks": 75, + "source": "one-hand-sword-combo.glb" + }, + { + "role": "2h-combo", + "file": "two-hand-sword-combo.glb", + "duration": 3.299999952316284, + "tracks": 75, + "source": "two-hand-sword-combo.glb" + }, + { + "role": "dual-combo", + "file": "dual-weapon-combo.glb", + "duration": 3.6666667461395264, + "tracks": 75, + "source": "dual-weapon-combo.glb" + }, + { + "role": "spear", + "file": "spear1.glb", + "duration": 3.299999952316284, + "tracks": 75, + "source": "spear1.glb" + }, + { + "role": "greataxe", + "file": "greataxe.glb", + "duration": 5.4666666984558105, + "tracks": 75, + "source": "greataxe.glb" + }, + { + "role": "slash", + "file": "sword-and-shield-slash.glb", + "duration": 1.5333333015441895, + "tracks": 75, + "source": "sword-and-shield-slash.glb" + }, + { + "role": "slash-in", + "file": "insword-and-shield-slash.glb", + "duration": 1.7000000476837158, + "tracks": 75, + "source": "insword-and-shield-slash.glb" + } + ] +} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/dropto-target.json b/public/anims/baked/sword_shield/dropto-target.json new file mode 100644 index 0000000..e3367d3 --- /dev/null +++ b/public/anims/baked/sword_shield/dropto-target.json @@ -0,0 +1 @@ +{"name":"sword_shield/finisher-drop","duration":0.8333333134651184,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.969893217086792,146.82305908203125,49.041351318359375,1.2304534912109375,160.23388671875,56.950889587402344,1.1982938051223755,167.52670288085938,65.04444885253906,1.6698845624923706,169.9700469970703,72.47543334960938,2.0717074871063232,168.7047882080078,79.01133728027344,2.080565929412842,164.52320861816406,84.26924133300781,2.011371612548828,156.7959747314453,89.6353530883789,2.020461082458496,144.95692443847656,95.62425994873047,2.3195862770080566,127.65872192382812,101.22008514404297,2.6639013290405273,106.0068588256836,104.36933898925781,3.3326964378356934,81.78057098388672,105.51863098144531,3.0674023628234863,61.9445686340332,107.79178619384766,3.361286163330078,43.646156311035156,108.03490447998047,3.7843871116638184,32.89430618286133,110.11094665527344,4.3950371742248535,33.74816131591797,113.09832763671875,4.465513229370117,36.45198440551758,115.26102447509766,4.353274345397949,39.43769073486328,117.23050689697266,3.920229911804199,42.02886199951172,119.09504699707031,3.3339333534240723,43.481727600097656,120.69446563720703,2.8808884620666504,43.468536376953125,121.95608520507812,2.6010918617248535,43.27831268310547,122.57833862304688,2.4446301460266113,42.41047668457031,122.37955474853516,2.2671923637390137,41.37348556518555,120.76744079589844,2.0448684692382812,40.804996490478516,118.09364318847656,1.9745922088623047,40.946678161621094,115.98434448242188],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.3501388132572174,0.13167859613895416,0.09540057182312012,0.9224761724472046,0.374799519777298,0.15399621427059174,0.08785068988800049,0.9099960327148438,0.4066389203071594,0.15290094912052155,0.10171867907047272,0.8949410319328308,0.4135196805000305,0.15582717955112457,0.10843013972043991,0.8904843330383301,0.40501341223716736,0.16052626073360443,0.10808064788579941,0.8935961723327637,0.3914445638656616,0.16086731851100922,0.10659167170524597,0.8997394442558289,0.3853452205657959,0.16212810575962067,0.10745105147361755,0.902040958404541,0.39831462502479553,0.17055049538612366,0.11145133525133133,0.8943358659744263,0.4183754026889801,0.17227394878864288,0.12103532254695892,0.8835350275039673,0.43354532122612,0.1607465147972107,0.13365212082862854,0.8765478730201721,0.4430226981639862,0.1570156216621399,0.11842668801546097,0.8746726512908936,0.4149942696094513,0.15983887016773224,0.12292342633008957,0.8871985077857971,0.3878078758716583,0.1269872635602951,0.11694998294115067,0.9054291844367981,0.44246166944503784,0.08306428790092468,0.11588917672634125,0.8853799700737,0.5021653175354004,0.07120762020349503,0.12954628467559814,0.8520430326461792,0.4942843019962311,0.08076748996973038,0.12770198285579681,0.8560676574707031,0.504963755607605,0.08725735545158386,0.11319413781166077,0.8512254953384399,0.5254315733909607,0.09817631542682648,0.09446684271097183,0.8398566246032715,0.543268084526062,0.10832062363624573,0.0871463418006897,0.8279685378074646,0.5608317255973816,0.10988336056470871,0.0834362655878067,0.8163527250289917,0.5654029250144958,0.11423714458942413,0.08458931744098663,0.8124740719795227,0.5586223006248474,0.11309143155813217,0.08883871883153915,0.8168593049049377,0.5457392930984497,0.1063963770866394,0.09824275225400925,0.8253465294837952,0.5203078985214233,0.09449753165245056,0.11851683259010315,0.8404187560081482,0.4852089285850525,0.08265704661607742,0.14607422053813934,0.858138918876648],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,0.9999998211860657,0.9999997615814209,0.9999998211860657,0.9999998211860657,0.9999998807907104],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,0.8333333134651184],"values":[8.305593490600586,-6.711597919464111,5.719310760498047,8.305599212646484,-6.711597919464111,5.719302654266357],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.9465720653533936,0.07523014396429062,-0.016869662329554558,0.3131408095359802,0.9537714719772339,0.11765968799591064,0.05989527329802513,0.2699790298938751,0.9655562043190002,0.07499343156814575,0.07199592143297195,0.2385241985321045,0.9734031558036804,0.027980618178844452,0.0577847845852375,0.219918891787529,0.9763410687446594,0.008638118393719196,0.03896594047546387,0.21252106130123138,0.9734578132629395,-0.004232973791658878,0.031724926084280014,0.22661752998828888,0.9667823314666748,-0.010600673034787178,0.02356838993728161,0.2542913556098938,0.9593817591667175,-0.012256495654582977,0.01370381098240614,0.2815113067626953,0.9548435807228088,-0.028100501745939255,0.007017189636826515,0.295693963766098,0.9480910897254944,-0.051266226917505264,0.018655849620699883,0.31328436732292175,0.9439157247543335,-0.05347467213869095,0.016166342422366142,0.3254264295101166,0.8904935717582703,-0.0705685093998909,0.012200740166008472,0.44932445883750916,0.7878268957138062,-0.0525103323161602,-0.0379718653857708,0.6124782562255859,0.7867904901504517,0.033012911677360535,-0.07934797555208206,0.6112077236175537,0.743645429611206,0.02032805047929287,-0.05701985955238342,0.6658281087875366,0.7552207708358765,0.0045406450517475605,-0.054692476987838745,0.6531689763069153,0.7515072226524353,0.0044087618589401245,-0.04867108538746834,0.6579123139381409,0.7320188283920288,0.021605536341667175,-0.04959495738148689,0.6791332364082336,0.7176870703697205,0.03131788969039917,-0.05376635491847992,0.6935803294181824,0.7117717862129211,0.04199891537427902,-0.05960209667682648,0.6986162066459656,0.7156216502189636,0.04418046027421951,-0.06202749162912369,0.6943244338035583,0.7278984189033508,0.04768242686986923,-0.063226617872715,0.6810967326164246,0.7393932342529297,0.04631942883133888,-0.06554675847291946,0.6684727668762207,0.7566481828689575,0.03011411987245083,-0.06075939163565636,0.6502960920333862,0.7812835574150085,-0.004078571684658527,-0.04563850164413452,0.6224921941757202],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,1,0.9999990463256836,0.9999997615814209,0.9999998807907104,0.9999990463256836],"interpolation":2300,"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.000003866677161568077,25.81270408630371,6.17315038198285e-7,0.0000026321997665945673,25.812705993652344,-1.4257570057907287e-7],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.7756488919258118,-0.06606370210647583,-0.1707487255334854,0.6040275692939758,0.8125104308128357,-0.07700487226247787,-0.23064786195755005,0.5298098921775818,0.8101488947868347,-0.07666069269180298,-0.23601621389389038,0.5311104655265808,0.7664327621459961,-0.1014222577214241,-0.24734832346439362,0.5840489268302917,0.7109436392784119,-0.12643015384674072,-0.23901744186878204,0.6491881608963013,0.6759833097457886,-0.12610755860805511,-0.2086038440465927,0.6954336762428284,0.6333144307136536,-0.11969373375177383,-0.1705581545829773,0.7453161478042603,0.5471242070198059,-0.12069059908390045,-0.12413116544485092,0.8189507722854614,0.43380504846572876,-0.06836815923452377,-0.040023043751716614,0.897517204284668,0.29038140177726746,-0.034541819244623184,0.019258534535765648,0.956093430519104,0.12845171988010406,-0.031847380101680756,0.04229149967432022,0.9903016090393066,0.30348092317581177,-0.054975949227809906,0.02502109669148922,0.9509211182594299,0.8061736822128296,0.02123172953724861,-0.058411139994859695,0.5884057879447937,0.9083864688873291,0.07561258226633072,0.06633080542087555,0.4058535397052765,0.9192162752151489,0.06956937909126282,0.06214752420783043,0.3825431764125824,0.8984125256538391,0.06491277366876602,0.03297259658575058,0.4330751895904541,0.8701105713844299,0.05585561692714691,0.004085960332304239,0.4896642565727234,0.8429712057113647,0.06451263278722763,0.007452805060893297,0.5340244770050049,0.8217438459396362,0.07642986625432968,0.017076311632990837,0.5644501447677612,0.812595784664154,0.08367615193128586,0.025572454556822777,0.5762225985527039,0.8067057728767395,0.08871039003133774,0.03110381029546261,0.5834285616874695,0.8115217685699463,0.09014607965946198,0.03771639242768288,0.5760933756828308,0.820502758026123,0.08460258692502975,0.04263116419315338,0.5637377500534058,0.8256552219390869,0.06618355959653854,0.033639367669820786,0.5592688322067261,0.8242210745811462,0.037807997316122055,0.004528123419731855,0.5649864673614502],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1.0000001192092896,1.0000003576278687,1.0000004768371582,0.9999999403953552,0.9999999403953552,1.0000001192092896],"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.000003496988028928172,22.742706298828125,-0.00000566375501875882,4.837652909372991e-7,22.742713928222656,0.000010771146662591491],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[-0.16719770431518555,0.06008213013410568,-0.14231571555137634,0.9737460017204285,-0.0011762036010622978,0.016531754285097122,-0.08960623294115067,0.9958393573760986,-0.002153948415070772,-0.03404947742819786,-0.008899852633476257,0.9993782043457031,-0.027773428708314896,-0.04084954038262367,0.002120584947988391,0.9987769722938538,-0.05607714131474495,-0.02793353796005249,-0.01841077394783497,0.9978657960891724,-0.050068069249391556,-0.037331514060497284,-0.0019716520328074694,0.9980459213256836,-0.0399714820086956,-0.042067933827638626,0.006489351857453585,0.9982938170433044,-0.0719858780503273,-0.05040101334452629,0.02364090457558632,0.9958508610725403,-0.2181016057729721,-0.062491197139024734,0.05311109125614166,0.9724740386009216,-0.39800360798835754,-0.08184646815061569,0.09830048680305481,0.9084224104881287,-0.5626012086868286,-0.08299025148153305,0.10460823774337769,0.8158735632896423,-0.6705241203308105,-0.06006447225809097,0.07815738022327423,0.7353101968765259,-0.8842740654945374,-0.09621558338403702,0.17137616872787476,0.4235943555831909,-0.9720777273178101,-0.06958117336034775,0.13143351674079895,0.1815175563097,-0.9510142207145691,-0.06791125982999802,0.12927941977977753,0.2724829316139221,-0.9258889555931091,-0.06564782559871674,0.13514912128448486,0.3466334044933319,-0.8935803771018982,-0.07281947880983353,0.15388141572475433,0.4153696298599243,-0.8550741076469421,-0.0722559317946434,0.15103551745414734,0.4907296895980835,-0.8243622183799744,-0.07224184274673462,0.14642253518104553,0.5420041084289551,-0.8198189735412598,-0.075726218521595,0.14866143465042114,0.547779381275177,-0.823263943195343,-0.07931041717529297,0.149008646607399,0.5419803857803345,-0.8407015204429626,-0.082914799451828,0.1508188545703888,0.5134196877479553,-0.8652350902557373,-0.08608321100473404,0.15554271638393402,0.4687904119491577,-0.8825771808624268,-0.09050697833299637,0.1704522669315338,0.4287330210208893,-0.8836220502853394,-0.10153363645076752,0.19263043999671936,0.4144834280014038],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,1,0.9999997019767761,0.9999998807907104,0.9999998211860657,1],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.000006832266990386415,13.415105819702148,-0.000010418363672215492,-0.000001121405148296617,13.415112495422363,-0.000010760899385786615],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[-0.30698150396347046,-0.005662651266902685,-0.001829224405810237,0.9516968727111816,-0.30698150396347046,-0.005662641022354364,-0.0018292332533746958,0.9516968727111816],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999999403953552,0.9999999403953552,1.0000001192092896,0.9999998807907104,0.9999999403953552,1],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,0.8333333134651184],"values":[-11.139503479003906,-4.020095348358154,0.42030954360961914,-11.13949966430664,-4.020092964172363,0.4203092157840729],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.8634766936302185,-0.2974995970726013,0.2929317057132721,0.2830071747303009,0.8524913191795349,-0.3416932225227356,0.3131115734577179,0.2417963743209839,0.8629930019378662,-0.35760536789894104,0.29651758074760437,0.1985922008752823,0.8792035579681396,-0.31583672761917114,0.3026185631752014,0.1888655126094818,0.8705286383628845,-0.2825912833213806,0.34293729066848755,0.2114619016647339,0.8485826849937439,-0.2693076431751251,0.38196492195129395,0.2479589283466339,0.8378229141235352,-0.2544773817062378,0.39479222893714905,0.27826809883117676,0.8304567337036133,-0.23150663077831268,0.3935994803905487,0.319101482629776,0.7941185832023621,-0.23110753297805786,0.3773004710674286,0.41666463017463684,0.7179218530654907,-0.23861755430698395,0.3359428942203522,0.5610635876655579,0.619088888168335,-0.20000359416007996,0.3202425241470337,0.6886017322540283,0.6236498355865479,-0.18990521132946014,0.37382563948631287,0.6597358584403992,0.3580950200557709,-0.22306261956691742,0.37358543276786804,0.8261022567749023,0.108625628054142,-0.1489270180463791,0.3782804608345032,0.9071522355079651,0.07153436541557312,-0.11734351515769958,0.382561057806015,0.913652241230011,0.16426049172878265,-0.1474345177412033,0.40286871790885925,0.8882445693016052,0.2813335359096527,-0.1748274713754654,0.38539955019950867,0.8612514734268188,0.3622581958770752,-0.17479968070983887,0.3813541531562805,0.8323359489440918,0.4123038351535797,-0.1794605404138565,0.3626503050327301,0.8162623643875122,0.4347432255744934,-0.18435631692409515,0.335058331489563,0.8153201937675476,0.43946895003318787,-0.18753838539123535,0.3353557586669922,0.8119316101074219,0.42710524797439575,-0.19196036458015442,0.34421977400779724,0.8137844204902649,0.38888171315193176,-0.19333243370056152,0.3494901657104492,0.8302109837532043,0.3351629376411438,-0.19253472983837128,0.35720255970954895,0.8502955436706543,0.30634966492652893,-0.20784802734851837,0.35967981815338135,0.8564925789833069],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1,0.9999998807907104,1,1.000000238418579,1,1.0000001192092896],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,0.8333333134651184],"values":[-3.1121609822548635e-7,25.527099609375,-0.000009436867003387306,0.000019032613636227325,25.527103424072266,0.00000750637445889879],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.7159972786903381,-0.030589763075113297,0.042030781507492065,0.6961649060249329,0.8185994625091553,-0.005623232573270798,0.10455922037363052,0.564739465713501,0.8484180569648743,-0.01917082630097866,0.09224201738834381,0.5208749771118164,0.8115397691726685,-0.005458800122141838,0.13368360698223114,0.5687724947929382,0.7269912362098694,0.025201287120580673,0.1631823033094406,0.6664984822273254,0.6573479771614075,0.032644905149936676,0.12656866014003754,0.742164671421051,0.5862537026405334,0.016337336972355843,0.062173761427402496,0.8075729608535767,0.5216480493545532,-0.035401005297899246,-0.0023121112026274204,0.852422833442688,0.5458401441574097,-0.04059693217277527,-0.017295537516474724,0.8367265462875366,0.5797265768051147,-0.056289803236722946,-0.03704242780804634,0.8120200037956238,0.5380041599273682,-0.06700113415718079,-0.059106938540935516,0.8381937742233276,0.40142425894737244,-0.08621034026145935,-0.14212043583393097,0.9006820321083069,0.7894815802574158,-0.12720826268196106,-0.1549912989139557,0.5800988078117371,0.9042028784751892,-0.13807427883148193,-0.13633836805820465,0.3804793059825897,0.9003239870071411,-0.16767388582229614,-0.1247367337346077,0.38176292181015015,0.8849000930786133,-0.1850995123386383,-0.12992830574512482,0.40719613432884216,0.8580138683319092,-0.17201635241508484,-0.12801139056682587,0.46672868728637695,0.8277459144592285,-0.1552351862192154,-0.12764281034469604,0.5238760113716125,0.8098859786987305,-0.1321791410446167,-0.12207081913948059,0.5583118200302124,0.8096163272857666,-0.11020366102457047,-0.11573497205972672,0.5647849440574646,0.8128021359443665,-0.10698012262582779,-0.11710639297962189,0.5605301260948181,0.8254008293151855,-0.11330606043338776,-0.12624192237854004,0.5384591221809387,0.8411442637443542,-0.11921215802431107,-0.13516677916049957,0.5098967552185059,0.8488383889198303,-0.12745355069637299,-0.15079066157341003,0.49039897322654724,0.8437707424163818,-0.13502396643161774,-0.17768914997577667,0.48810452222824097],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,0.9999998807907104,0.9999998807907104,0.9999999403953552,0.9999998807907104,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.000005585933649854269,23.10919952392578,0.0000033096464449045015,0.0000057559009292162955,23.10920524597168,-0.000004850079221796477],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[-0.06704330444335938,0.013571499846875668,0.08579311519861221,0.9939620494842529,-0.0051477449014782906,-0.01189109031111002,0.1287262886762619,0.9915955066680908,0.021052192896604538,-0.00911229569464922,0.1200413778424263,0.992503821849823,0.0164767038077116,-0.02900000289082527,0.15057173371315002,0.9880362749099731,0.0005741111235693097,-0.040624938905239105,0.16929970681667328,0.9847268462181091,0.00039773734170012176,-0.03978264331817627,0.16602230072021484,0.9853191375732422,0.0198960117995739,-0.026971399784088135,0.14021360874176025,0.9895538687705994,-0.0761086642742157,-0.006039646919816732,0.0961245447397232,0.992436945438385,-0.2869485020637512,-0.007002798840403557,0.08915719389915466,0.9537623524665833,-0.5256718993186951,0.06834039837121964,-0.028196660801768303,0.8474689722061157,-0.6849926710128784,0.06338150799274445,-0.031005922704935074,0.7251251339912415,-0.513924777507782,-0.03931306302547455,0.11244730651378632,0.8495242595672607,-0.6382543444633484,-0.0559215173125267,0.12483640760183334,0.7575751543045044,-0.6379287838935852,-0.09824264794588089,0.1777883917093277,0.7428233623504639,-0.6611040830612183,-0.09086243063211441,0.1615733504295349,0.7270347476005554,-0.7095721364021301,-0.08138573914766312,0.14029961824417114,0.685711145401001,-0.7579531669616699,-0.05124010518193245,0.09393143653869629,0.6434736847877502,-0.7843661904335022,-0.03481123223900795,0.06699207425117493,0.6156865954399109,-0.8045403957366943,-0.019188618287444115,0.04406316950917244,0.5919501185417175,-0.8233858346939087,-0.004984527826309204,0.025621864944696426,0.5668813586235046,-0.8327142596244812,-0.004803599324077368,0.024825584143400192,0.553125262260437,-0.8358190059661865,-0.01401300635188818,0.03787127509713173,0.5475180745124817,-0.8224768042564392,-0.028772324323654175,0.06174488738179207,0.5647048950195312,-0.7832393050193787,-0.053759172558784485,0.10215331614017487,0.6109098792076111,-0.7382352352142334,-0.07510096579790115,0.13841797411441803,0.6559032797813416],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1,1,1.0000001192092896,0.9999998211860657,1,0.9999998211860657],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.0000091818565124413,13.54739761352539,-4.876661705566221e-7,0.0000025633269160607597,13.54740047454834,-0.000014678472325613257],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[-0.30788561701774597,0.014501934871077538,0.004691822454333305,0.9513012766838074,-0.30788561701774597,0.014501916244626045,0.00469182850793004,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1,1.0000001192092896,1,0.9999998211860657,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,0.8333333134651184],"values":[2.8339967727661133,7.754312515258789,-6.139607906341553,2.833998918533325,7.754303455352783,-6.139604091644287],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[-0.3320007026195526,-0.09571804106235504,-0.11373620480298996,0.9314922094345093,-0.3320007026195526,-0.09571801871061325,-0.11373620480298996,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,0.9999999403953552,0.9999999403953552,1,1,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,0.8333333134651184],"values":[-8.382174065957315e-9,10.288625717163086,-0.000003315742105769459,4.982572363587678e-7,10.288614273071289,0.000007900177479314152],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[-1.4682881577243734e-8,-1.2070812793751884e-8,1.30821717903018e-8,1,-2.22571543417871e-8,-1.4464602848818231e-8,1.4391844160854816e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1,1,1,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,0.8333333134651184],"values":[5.812734684695897e-7,10.288604736328125,-0.0000011264859267612337,-1.0872612676848803e-7,10.288602828979492,0.000020773983123945072],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[-0.04484790191054344,0.01455832738429308,-0.011899040080606937,0.9988169074058533,-0.07189979404211044,0.003397247986868024,-0.004320121835917234,0.9973967671394348,-0.09302830696105957,0.000658692792057991,-0.0033857240341603756,0.9956575036048889,-0.09661125391721725,-0.001833876478485763,-0.003717543324455619,0.9953135848045349,-0.08852583169937134,-0.0001064336029230617,-0.007443697191774845,0.9960460662841797,-0.07694247364997864,0.003836933523416519,-0.011945176869630814,0.9969565868377686,-0.06808961182832718,0.004854157101362944,-0.015799900516867638,0.9975423216819763,-0.06154964864253998,0.0010149221634492278,-0.017835386097431183,0.997944176197052,-0.05130168795585632,-0.004859861917793751,-0.014337822794914246,0.9985684752464294,-0.04486747831106186,-0.00846634991466999,-0.006844485178589821,0.9989336133003235,-0.03532826527953148,-0.011519651859998703,0.015322310850024223,0.99919193983078,0.01890983246266842,-0.01007999200373888,0.002360388170927763,0.9997676014900208,0.10463447123765945,-0.003219038713723421,0.003539389232173562,0.9944992661476135,0.15566430985927582,0.00389886274933815,0.005278837401419878,0.9877882599830627,0.16258107125759125,0.0037375125102698803,0.004829087294638157,0.9866762757301331,0.1521482914686203,0.003489047521725297,0.005880109965801239,0.988334059715271,0.1361948400735855,0.002807829761877656,0.005754434037953615,0.9906613826751709,0.12387309968471527,0.0011708105448633432,0.00542316073551774,0.9922825694084167,0.1166948676109314,-0.0009480585576966405,0.005625242833048105,0.9931514263153076,0.10761378705501556,-0.0011281882179901004,0.006705720908939838,0.9941695332527161,0.10314873605966568,-0.001532597467303276,0.007382403127849102,0.9946373701095581,0.10374532639980316,-0.0015375720104202628,0.007468768395483494,0.9945746660232544,0.10315234959125519,0.0008166011539287865,0.007953779771924019,0.9946334362030029,0.10032480210065842,0.0024480826687067747,0.004625218454748392,0.994940996170044,0.09387406706809998,0.0005405303672887385,-0.0017229985678568482,0.9955824613571167],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998211860657,0.9999998211860657,0.9999998211860657,1,0.9999998807907104,0.9999998807907104],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,0.8333333134651184],"values":[4.366401195526123,7.562928676605225,-0.17679919302463531,4.366400241851807,7.5629191398620605,-0.1767934113740921],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.3937264680862427,0.5999674201011658,-0.34274542331695557,0.6062542200088501,0.3587997555732727,0.5492322444915771,-0.4236016869544983,0.6246345639228821,0.29998528957366943,0.48919567465782166,-0.5260812044143677,0.6276423931121826,0.2729983329772949,0.45810699462890625,-0.5871813297271729,0.6089565753936768,0.2861783802509308,0.45652681589126587,-0.6128246784210205,0.5780408978462219,0.3076202869415283,0.46844321489334106,-0.6223946809768677,0.5464025139808655,0.3235194981098175,0.4740103781223297,-0.6260918378829956,0.5278812050819397,0.33012786507606506,0.46508464217185974,-0.6301887631416321,0.526853084564209,0.3303472101688385,0.46098026633262634,-0.6199244856834412,0.5422745943069458,0.33231326937675476,0.45656663179397583,-0.5847786664962769,0.5823649168014526,0.34891626238822937,0.4487725496292114,-0.5322240591049194,0.6273740530014038,0.4204522967338562,0.47849151492118835,-0.4467361271381378,0.6282455921173096,0.55124831199646,0.5380372405052185,-0.31138166785240173,0.5564915537834167,0.6739793419837952,0.5719875693321228,-0.15330670773983002,0.4416777193546295,0.7378384470939636,0.5737244486808777,-0.013909926638007164,0.35530441999435425,0.7538449168205261,0.5687256455421448,0.07534193992614746,0.3203009366989136,0.7291018962860107,0.5798938870429993,0.08832164853811264,0.3526086211204529,0.704544186592102,0.5880534052848816,0.09758410602807999,0.3850819766521454,0.6955704689025879,0.5983722805976868,0.07330022007226944,0.390844464302063,0.6989609599113464,0.5961281061172485,0.032681647688150406,0.3937218487262726,0.703495442867279,0.5858845710754395,0.018844498321413994,0.40184375643730164,0.7049376368522644,0.5813853740692139,0.024070674553513527,0.40555474162101746,0.7054616212844849,0.578138530254364,0.026943208649754524,0.409089058637619,0.7111787796020508,0.590813398361206,-0.008068878203630447,0.380918949842453,0.7246547937393188,0.5787874460220337,-0.03844176232814789,0.37202534079551697],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999997019767761,0.9999997615814209,1,0.9999997615814209,0.9999997615814209,1],"interpolation":2300,"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.000002400962557658204,17.630796432495117,-0.000012656228136620484,-0.000004085407908860361,17.63079071044922,-3.530361709636054e-7],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.0464908629655838,-0.3216697573661804,-0.07051311433315277,0.9430774450302124,0.037503890693187714,-0.3427481949329376,-0.08745107054710388,0.9345958828926086,0.009005655534565449,-0.33777838945388794,-0.1083526685833931,0.9349247813224792,0.00409508915618062,-0.3516279458999634,-0.06663321703672409,0.933756411075592,0.01839851215481758,-0.36915963888168335,-0.018556108698248863,0.928998589515686,0.04097123071551323,-0.3853139877319336,0.028396811336278915,0.9214380979537964,0.06561583280563354,-0.3965657949447632,0.06620286405086517,0.913261890411377,0.08530735224485397,-0.40080156922340393,0.08765042573213577,0.907963752746582,0.09893990308046341,-0.4079516530036926,0.0794677883386612,0.9041411876678467,0.11372512578964233,-0.43475472927093506,0.030271628871560097,0.8928261995315552,0.11175145953893661,-0.42246466875076294,-0.01644430309534073,0.8993135094642639,0.05984937772154808,-0.2510054409503937,-0.006503918673843145,0.9661118388175964,-0.08424881845712662,0.1525316834449768,0.05377119779586792,0.9832318425178528,-0.08136236667633057,0.11202507466077805,0.17838749289512634,0.9741706252098083,-0.002540253335610032,-0.3938409090042114,0.2351410686969757,0.8885896801948547,0.02640244923532009,-0.375563383102417,0.2865971624851227,0.8809751272201538,0.07790530472993851,-0.36803266406059265,0.2849899232387543,0.8816254734992981,0.09406773746013641,-0.3908030688762665,0.2868550717830658,0.8695622086524963,0.11490807682275772,-0.4498303234577179,0.3199877142906189,0.825867235660553,0.11316531151533127,-0.4565846621990204,0.32757437229156494,0.819401741027832,0.1378101110458374,-0.4261658191680908,0.33043190836906433,0.8307862877845764,0.15086038410663605,-0.3824255168437958,0.32461225986480713,0.8518326282501221,0.15558753907680511,-0.3757179081439972,0.3085399270057678,0.8599021434783936,0.10272098332643509,-0.3843572437763214,0.3497937321662903,0.8481522798538208,0.05803217366337776,-0.3827858865261078,0.3655913472175598,0.8464338183403015],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1.000000238418579,1.0000001192092896,1,1.0000001192092896,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.000004138993062952068,17.827804565429688,-0.00000930370242713252,0.0000014144458191367448,17.82781219482422,-0.00000986003487923881],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[-0.41688790917396545,-0.5143411755561829,0.6249132752418518,0.4136919677257538,-0.4034094512462616,-0.4788265526294708,0.6327463388442993,0.455651193857193,-0.3716807961463928,-0.4288580119609833,0.6262204647064209,0.5345860123634338,-0.3703857362270355,-0.36278945207595825,0.6242344975471497,0.5844053030014038,-0.36808252334594727,-0.30265679955482483,0.6301813125610352,0.6130135655403137,-0.36604493856430054,-0.246629998087883,0.6394952535629272,0.6294683814048767,-0.3625148832798004,-0.20206530392169952,0.6509641408920288,0.6356085538864136,-0.3543328046798706,-0.1765187531709671,0.6634495854377747,0.6349204778671265,-0.3395764231681824,-0.16649025678634644,0.6805105209350586,0.6275940537452698,-0.3172832429409027,-0.16079272329807281,0.7102329134941101,0.6074917316436768,-0.2829514145851135,-0.21074947714805603,0.7274947166442871,0.5884510278701782,-0.2438376247882843,-0.40761855244636536,0.6888078451156616,0.5476623773574829,-0.22431877255439758,-0.6381391286849976,0.6263543963432312,0.387478768825531,-0.13515342772006989,-0.5393803715705872,0.589543342590332,0.5858678817749023,-0.06327512115240097,-0.09705229103565216,0.5908118486404419,0.7984475493431091,-0.12577594816684723,-0.11663348972797394,0.5511723160743713,0.8165697455406189,-0.11952439695596695,-0.07363380491733551,0.5375689268112183,0.8314515352249146,-0.08635640144348145,-0.01448681391775608,0.5226573348045349,0.8480342030525208,-0.04413309693336487,0.08546016365289688,0.5075497031211853,0.8562371730804443,-0.014588526450097561,0.13399451971054077,0.5089132785797119,0.8501998782157898,-0.04315139353275299,0.14033107459545135,0.5118609666824341,0.8464298248291016,-0.08326880633831024,0.09914293885231018,0.5205118060112,0.8439813256263733,-0.07577793300151825,0.06809201091527939,0.5245654582977295,0.8452528119087219,-0.015210880897939205,0.04167512431740761,0.4738387167453766,0.8794934153556824,0.05710415914654732,0.02421807125210762,0.40344300866127014,0.9128999710083008],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,1.0000001192092896,1,0.9999997019767761,0.9999999403953552,0.9999998211860657],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.00000676924355502706,18.751115798950195,-0.0000018840044049284188,-9.14820930120186e-7,18.7510929107666,-0.00001892935688374564],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[0.055037714540958405,0.05196232721209526,-0.013945185579359531,0.9970337748527527,0.05503770336508751,0.05196233466267586,-0.013945181854069233,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999999403953552,1.0000001192092896,0.9999999403953552,0.9999999403953552,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,0.8333333134651184],"values":[-4.513198375701904,8.246417999267578,-0.08129971474409103,-4.513197898864746,8.2464017868042,-0.08130718767642975],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.30186834931373596,-0.5601342916488647,0.3529011309146881,0.6859926581382751,0.2134941965341568,-0.5633177161216736,0.4183841943740845,0.6797412037849426,0.13231469690799713,-0.5596494674682617,0.5085848569869995,0.640801727771759,0.09746202081441879,-0.568082332611084,0.5591006278991699,0.595978319644928,0.09832211583852768,-0.5822518467903137,0.5554715394973755,0.5854629874229431,0.11934108287096024,-0.599916934967041,0.5318812131881714,0.5856276750564575,0.13839936256408691,-0.6155353784561157,0.5114154815673828,0.5834517478942871,0.14888037741184235,-0.6289578080177307,0.494549036026001,0.5810920596122742,0.17589201033115387,-0.6481693387031555,0.4673537611961365,0.5749077796936035,0.22329282760620117,-0.6697202920913696,0.43331894278526306,0.5602229833602905,0.2834595739841461,-0.6965898871421814,0.38805970549583435,0.5327503085136414,0.3559246361255646,-0.7555448412895203,0.2797650694847107,0.4734988212585449,0.5230419039726257,-0.7647621035575867,0.17405317723751068,0.3335740566253662,0.6473875641822815,-0.7225138545036316,0.056913405656814575,0.23584723472595215,0.5859764218330383,-0.776898980140686,0.05506563559174538,0.22366808354854584,0.5803841948509216,-0.7751326560974121,-0.011124706827104092,0.24939875304698944,0.60909503698349,-0.7518115639686584,-0.045034442096948624,0.24850453436374664,0.6671483516693115,-0.7147568464279175,-0.02564229629933834,0.20827430486679077,0.737463116645813,-0.6438097357749939,-0.043876007199287415,0.19932909309864044,0.7860665917396545,-0.576431393623352,-0.04048467427492142,0.21951575577259064,0.8002833724021912,-0.5535414814949036,-0.018267754465341568,0.2297925502061844,0.790391743183136,-0.5706649422645569,-0.005742426961660385,0.2226869910955429,0.7808823585510254,-0.5849003195762634,0.005172445438802242,0.21928878128528595,0.7684401273727417,-0.6000805497169495,0.017714401707053185,0.22156113386154175,0.7496272921562195,-0.6199690699577332,0.03863457590341568,0.22848346829414368],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,1,1,1.0000001192092896,0.9999999403953552,1.000000238418579],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.0000027791950287792133,17.92138671875,-0.000014757548342458904,-0.000010072714758280199,17.921398162841797,0.0000062627741499454714],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[-0.11034124344587326,0.619553804397583,0.10247845947742462,0.7703740000724792,-0.14658188819885254,0.6342373490333557,0.165963813662529,0.7407515048980713,-0.1858019381761551,0.6116510629653931,0.12507610023021698,0.7587598562240601,-0.16919901967048645,0.6310411095619202,0.0786675363779068,0.752974271774292,-0.11465656757354736,0.6573544144630432,0.07187162339687347,0.7413322329521179,-0.04165269061923027,0.6681731939315796,0.06607531756162643,0.7398943901062012,0.01648491621017456,0.6299355030059814,0.06612555682659149,0.7736517190933228,0.0896003469824791,0.5842151641845703,0.07353623956441879,0.8032788038253784,0.16749486327171326,0.5127302408218384,0.08894871175289154,0.8373417854309082,0.2757124602794647,0.4745490550994873,0.03454616293311119,0.8352199792861938,0.2149428427219391,0.4171946942806244,-0.02952745370566845,0.882539689540863,0.19692714512348175,0.29485470056533813,-0.029661336913704872,0.9345591068267822,0.29109445214271545,0.41137126088142395,-0.027438292279839516,0.8632988333702087,0.04255738481879234,0.22532124817371368,-0.14048953354358673,0.9631624817848206,0.03795531019568443,0.210946723818779,-0.3025485575199127,0.9287223815917969,0.1814584583044052,0.2388419359922409,-0.3084445297718048,0.9027122259140015,0.12450310587882996,0.20231656730175018,-0.3659951686859131,0.8997858166694641,-0.06447307765483856,0.09603478759527206,-0.4061577022075653,0.9064527153968811,-0.09431172162294388,0.07888856530189514,-0.2773727774620056,0.9528621435165405,-0.053518764674663544,0.10918551683425903,-0.14957493543624878,0.9812449216842651,-0.032865505665540695,0.07741183042526245,-0.12998035550117493,0.9879435300827026,-0.02128973789513111,0.06895872950553894,-0.16515344381332397,0.9836238026618958,-0.04639670252799988,0.04089942201972008,-0.18631848692893982,0.9805406928062439,-0.04887176677584648,0.004136309027671814,-0.17836277186870575,0.98274165391922,-0.047066010534763336,-0.03252721205353737,-0.1836874634027481,0.9813183546066284],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1,0.9999997615814209,0.9999997615814209,1,1,0.9999997019767761],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.00001232112572324695,17.203702926635742,-0.000016300356946885586,0.000007527480647695484,17.203699111938477,0.000013626219697471242],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[-0.01943308301270008,0.08943702280521393,-0.7255172729492188,0.6820909380912781,0.0405813567340374,0.03627721220254898,-0.7597315907478333,0.647954523563385,0.061556972563266754,0.0333622545003891,-0.7387727499008179,0.6703078150749207,0.01753946766257286,0.02660907804965973,-0.7239797711372375,0.6890846490859985,-0.05481617525219917,0.023823222145438194,-0.7304062247276306,0.680392861366272,-0.10472873598337173,0.020430726930499077,-0.7448334693908691,0.6586635112762451,-0.1201663389801979,0.027647042647004128,-0.7494572997093201,0.650468647480011,-0.14059990644454956,0.01840396411716938,-0.7675870060920715,0.6250625848770142,-0.15707333385944366,0.0020720548927783966,-0.7915276885032654,0.5905993580818176,-0.2080778330564499,-0.06569881737232208,-0.7735127806663513,0.5950338244438171,-0.15997545421123505,-0.06812763959169388,-0.6954728364944458,0.6971972584724426,-0.1404593586921692,-0.044995736330747604,-0.7104539275169373,0.6881147027015686,-0.18309499323368073,-0.10369446873664856,-0.7895119786262512,0.5765367150306702,-0.03218580782413483,0.01449292991310358,-0.7163224816322327,0.696876049041748,-0.009759427979588509,0.009194307029247284,-0.6257620453834534,0.7798988223075867,-0.07338018715381622,-0.05220530182123184,-0.5844727754592896,0.8064003586769104,-0.07208626717329025,0.008021892979741096,-0.42751652002334595,0.9010931849479675,-0.03485005348920822,0.17554350197315216,-0.24164479970932007,0.953717827796936,-0.025080252438783646,0.2141890525817871,-0.26957499980926514,0.9385219216346741,-0.0249510295689106,0.1605723351240158,-0.36122509837150574,0.9182104468345642,-0.06383752822875977,0.16709139943122864,-0.3692725598812103,0.9119446277618408,-0.11606642603874207,0.1709015667438507,-0.3692604601383209,0.9060727953910828,-0.14720582962036133,0.19602414965629578,-0.34842649102211,0.9047120809555054,-0.20785534381866455,0.24970890581607819,-0.3918587267398834,0.8607487082481384,-0.2159591019153595,0.3286232352256775,-0.4438655972480774,0.8052029609680176],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999999403953552,1,0.9999998807907104,0.9999998211860657,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.0000025733013444551034,19.45049476623535,0.0000030366945793502964,0.0000035299920000397833,19.450498580932617,0.0000011285982282061013],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[0.14937159419059753,-0.14748123288154602,-0.019200149923563004,0.9775319695472717,0.14937159419059753,-0.14748123288154602,-0.019200170412659645,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999997019767761,1,0.9999997019767761,0.9999998211860657,1.0000001192092896,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.14690150320529938,6.409544467926025,0.25809958577156067,0.14690034091472626,6.409520626068115,0.25810733437538147],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[0.020259669050574303,0.012515283189713955,-0.01119622215628624,0.999653697013855,0.020259631797671318,0.01251528225839138,-0.011196221224963665,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1.0000001192092896,1.0000003576278687,1.0000001192092896,1.0000001192092896,1.0000003576278687,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,0.8333333134651184],"values":[6.255471589611261e-7,6.8158860206604,-0.000006965291504457127,3.5241896512161475e-7,6.815887451171875,-0.000005605923888651887],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184],"values":[0.16294710338115692,-0.027413260191679,0.0711902529001236,0.9836812019348145,0.11278726905584335,-0.02377414144575596,0.04480439051985741,0.9923237562179565,0.09780038893222809,-0.018910951912403107,0.005217359866946936,0.9950127005577087,0.10580049455165863,-0.025780444964766502,-0.012784351594746113,0.9939709305763245,0.11467727273702621,-0.015755783766508102,-0.009111267514526844,0.9932360649108887,0.12325989454984665,-0.0033215126022696495,0.00009912728273775429,0.9923688769340515,0.12972597777843475,-0.01253263745456934,-0.001751973177306354,0.991469144821167,0.12837916612625122,-0.03235253691673279,-0.013307369314134121,0.9911079406738281,0.13661542534828186,-0.04128703847527504,-0.021913550794124603,0.9895208477973938,0.1456758677959442,-0.03866163268685341,-0.01651863381266594,0.9884386658668518,0.1526751071214676,-0.04517337679862976,0.004934569355100393,0.9872311353683472,0.05664331093430519,-0.05817247927188873,0.02519402839243412,0.9963798522949219,0.008615748025476933,-0.058981020003557205,0.02809099480509758,0.9978265762329102,0.12884798645973206,-0.05490715429186821,0.03441896662116051,0.9895447492599487,0.257555216550827,-0.05424924194812775,0.04947233945131302,0.9634702205657959,0.29051050543785095,-0.05060148984193802,0.05899970978498459,0.9537096619606018,0.22509853541851044,-0.043002840131521225,0.05138382315635681,0.9720293879508972,0.1527235209941864,-0.03739945963025093,0.03245614096522331,0.9870275855064392,0.12808996438980103,-0.03584730625152588,0.020022567361593246,0.9909122586250305,0.13448067009449005,-0.03212308883666992,0.021788276731967926,0.9901557564735413,0.16329717636108398,-0.03196598216891289,0.021979428827762604,0.9858139753341675,0.17740195989608765,-0.03322282433509827,0.01681285724043846,0.9834338426589966,0.1761893630027771,-0.02646753191947937,0.01070104818791151,0.9839422106742859,0.21254883706569672,-0.010667218826711178,-0.004853134974837303,0.9770801663398743,0.2602241039276123,0.008314044214785099,-0.0281818900257349,0.9651010632514954],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.9999998807907104,1,1.0000001192092896,0.9999997615814209,1,1],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,0.8333333134651184],"values":[0.5745995044708252,51.81690216064453,-20.522687911987305,0.5746003985404968,51.816871643066406,-20.522687911987305],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[-0.18743081390857697,9.955458324384381e-8,-0.005247785709798336,0.9822638034820557,-0.18743079900741577,9.570277370585245e-8,-0.005247786175459623,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1.0000001192092896,1.0000003576278687,1,1,1.0000001192092896,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,0.8333333134651184],"values":[-0.5745978355407715,4.330318450927734,20.5226993560791,-0.5745999813079834,4.3302812576293945,20.522708892822266],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[0.6296830773353577,-7.674562141346541e-8,0.017631011083722115,0.7766520380973816,0.6296831369400024,-7.49469037941708e-8,0.017631014809012413,0.7766519784927368],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,0.8333333134651184],"values":[1,1.000000238418579,1.0000003576278687,0.9999999403953552,1.0000001192092896,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,0.8333333134651184],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,0.8333333134651184],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,0.8333333134651184],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"6599923c-3e5d-427b-b990-6d1b5b78a34d","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/dual-weapon-combo.json b/public/anims/baked/sword_shield/dual-weapon-combo.json new file mode 100644 index 0000000..4cc0e52 --- /dev/null +++ b/public/anims/baked/sword_shield/dual-weapon-combo.json @@ -0,0 +1 @@ +{"name":"sword_shield/dual-weapon-combo","duration":3.6666667461395264,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.17144334316253662,54.268211364746094,0.578481912612915,-0.1449815034866333,54.385498046875,0.5755385160446167,-0.060877203941345215,54.68056869506836,0.5411756038665771,0.052440524101257324,55.04840850830078,0.47573068737983704,0.17771995067596436,55.46126174926758,0.4066869020462036,0.3153207302093506,55.910743713378906,0.354702353477478,0.427351713180542,56.39553451538086,0.3720715343952179,0.4643329977989197,56.847225189208984,0.48084670305252075,0.49203604459762573,57.25016784667969,0.6929071545600891,0.49066752195358276,57.612030029296875,1.0974369049072266,0.4396827816963196,58.019500732421875,1.543843388557434,0.35540586709976196,58.36406707763672,2.1534016132354736,0.25832921266555786,58.539817810058594,3.0125114917755127,0.18107250332832336,58.60313415527344,4.079808712005615,0.1415373682975769,58.4831428527832,5.408459186553955,0.1800777018070221,58.21334457397461,6.819031715393066,0.2936742603778839,57.53281021118164,8.44139575958252,0.4253687858581543,56.28296661376953,10.586682319641113,0.5800287127494812,54.88536834716797,12.816325187683105,0.7411982417106628,53.6715087890625,14.723517417907715,0.9317086338996887,52.93781280517578,15.910979270935059,1.124279499053955,52.628414154052734,18.145139694213867,1.1697232723236084,52.46533203125,20.57376480102539,1.197004795074463,52.51593017578125,23.420562744140625,1.4318745136260986,52.92436599731445,26.761545181274414,1.7303454875946045,53.65333557128906,30.5808162689209,2.0555191040039062,55.09132766723633,34.5070686340332,2.311933994293213,56.96464157104492,38.18440246582031,2.5369272232055664,58.78528594970703,41.404144287109375,2.7495596408843994,60.18567657470703,44.259822845458984,2.931915044784546,60.49244689941406,46.63405990600586,2.97709321975708,60.3663330078125,48.3135986328125,2.8612821102142334,59.93194580078125,50.37099075317383,2.581542491912842,59.282928466796875,53.5982551574707,2.268552303314209,58.44194412231445,57.52996063232422,1.938546895980835,57.2677116394043,61.62807083129883,1.52982497215271,55.398109436035156,65.6033935546875,1.3120322227478027,53.82424545288086,68.61095428466797,1.2099227905273438,52.86533737182617,70.81425476074219,1.054854393005371,52.32780456542969,72.86054992675781,0.9471440315246582,52.20720672607422,74.50094604492188,0.9442110061645508,52.344791412353516,75.97029113769531,0.8308162689208984,52.52230453491211,77.15320587158203,0.685206413269043,52.7760009765625,78.10515594482422,0.5235791206359863,53.06431579589844,79.06876373291016,0.41240739822387695,53.30571365356445,80.083984375,0.2787351608276367,53.55866622924805,80.68197631835938,0.33093690872192383,53.808223724365234,81.00565338134766,0.6759662628173828,53.97687911987305,81.17457580566406,1.4228644371032715,54.00285339355469,81.20620727539062,2.4624643325805664,53.99399185180664,81.61420440673828,3.355957508087158,53.46778869628906,82.77861785888672,3.841594696044922,52.60792541503906,84.50411987304688,4.201792240142822,51.50383758544922,86.28997802734375,4.157252788543701,51.263885498046875,88.76679992675781,3.6180243492126465,51.499446868896484,91.762939453125,2.9769468307495117,51.84949493408203,94.7448959350586,2.608884811401367,51.740230560302734,97.37500762939453,1.9680767059326172,52.015750885009766,100.69068908691406,1.3873786926269531,52.76091384887695,103.83869171142578,0.7439746856689453,53.94658279418945,107.05317687988281,0.22107410430908203,55.095394134521484,109.96819305419922,-0.23000860214233398,56.29338455200195,112.5986557006836,-0.5675897598266602,57.153995513916016,114.97285461425781,-0.8005189895629883,57.66171646118164,117.35354614257812,-0.925755500793457,57.8344612121582,119.94925689697266,-0.9714498519897461,57.78327941894531,122.8421401977539,-0.890406608581543,57.61390686035156,126.04837799072266,-0.7138528823852539,57.21630859375,129.78622436523438,-0.514439582824707,56.510501861572266,133.7130889892578,-0.3768644332885742,55.48240280151367,137.44749450683594,-0.3771505355834961,54.52144241333008,140.82220458984375,-0.3594083786010742,53.40287399291992,144.1759490966797,-0.3541259765625,52.30673599243164,147.07374572753906,-0.4211597442626953,51.40015411376953,149.55918884277344,-0.4548358917236328,50.716217041015625,151.8497772216797,-0.4864358901977539,50.114986419677734,153.55172729492188,-0.5079116821289062,49.48938751220703,154.96487426757812,-0.5311317443847656,48.84380340576172,156.08177185058594,-0.5478267669677734,48.21994400024414,156.861083984375,-0.5575227737426758,47.61433410644531,157.5231170654297,-0.5676612854003906,47.03342056274414,158.0308380126953,-0.5805540084838867,46.50259780883789,158.35923767089844,-0.6109371185302734,46.086448669433594,158.49874877929688,-0.6669855117797852,45.807247161865234,158.437255859375,-0.726959228515625,45.68656539916992,158.2842254638672,-0.772191047668457,45.71531677246094,158.0985565185547,-0.8082132339477539,45.962196350097656,157.65667724609375,-0.7431488037109375,46.40657043457031,156.9010467529297,-0.6050300598144531,46.96055603027344,155.7204132080078,-0.44390392303466797,47.577178955078125,154.14181518554688,-0.25898075103759766,48.243019104003906,152.25665283203125,-0.08082389831542969,49.01347351074219,150.0819549560547,0.03841876983642578,50.0111083984375,147.8170166015625,0.09417533874511719,51.2633056640625,145.4994659423828,0.13527774810791016,52.343387603759766,143.35691833496094,0.13465404510498047,53.029296875,141.53890991210938,0.11493396759033203,53.49691390991211,140.03977966308594,0.08842754364013672,54.05509948730469,138.7326202392578,0.05838775634765625,54.497901916503906,137.7838592529297,0.025514602661132812,54.75179672241211,137.18014526367188,-0.0026292800903320312,54.80604553222656,136.72760009765625,-0.030457496643066406,54.65595626831055,136.3701171875,-0.05891990661621094,54.490333557128906,136.17416381835938,-0.08358383178710938,54.332374572753906,136.10980224609375,-0.09880256652832031,54.24470520019531,136.12957763671875,-0.12401390075683594,54.23908996582031,136.18714904785156,-0.14538097381591797,54.24999237060547,136.26593017578125,-0.1625518798828125,54.26298141479492,136.35140991210938,-0.17154693603515625,54.26853561401367,136.43270874023438],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.16952496767044067,-0.4902651011943817,0.26279985904693604,0.8135340809822083,0.16729429364204407,-0.4937344193458557,0.26204490661621094,0.8121400475502014,0.16065609455108643,-0.5042499303817749,0.25985851883888245,0.8077098727226257,0.1526729315519333,-0.5164788961410522,0.2568281292915344,0.8024835586547852,0.14516206085681915,-0.5268136262893677,0.25316593050956726,0.798312246799469,0.13896170258522034,-0.5319255590438843,0.24784508347511292,0.7976952791213989,0.13594184815883636,-0.5301533341407776,0.24215294420719147,0.8011362552642822,0.13935653865337372,-0.5208374857902527,0.24021080136299133,0.8072217106819153,0.1455216407775879,-0.5067519545555115,0.23865601420402527,0.815517783164978,0.15303343534469604,-0.4935415983200073,0.240976482629776,0.8215399384498596,0.15932250022888184,-0.48548009991645813,0.24669966101646423,0.8234468698501587,0.16511762142181396,-0.48305854201316833,0.2554868757724762,0.8210463523864746,0.17054329812526703,-0.48317500948905945,0.26457279920578003,0.8169811367988586,0.17368705570697784,-0.490013986825943,0.2738739252090454,0.8091428875923157,0.17506511509418488,-0.5007419586181641,0.28153249621391296,0.7995932102203369,0.17608891427516937,-0.506069004535675,0.2831343114376068,0.7954381704330444,0.17881321907043457,-0.5034223794937134,0.2802726924419403,0.7975205183029175,0.18300287425518036,-0.49235039949417114,0.2732376158237457,0.8058798313140869,0.18833960592746735,-0.47305628657341003,0.2612805664539337,0.8200478553771973,0.1943165510892868,-0.4491075575351715,0.24579352140426636,0.8367371559143066,0.20224781334400177,-0.41622671484947205,0.225001722574234,0.8574528694152832,0.21350166201591492,-0.37418070435523987,0.2019922137260437,0.879548192024231,0.2267606407403946,-0.33182045817375183,0.18246006965637207,0.8973199725151062,0.23512648046016693,-0.30716797709465027,0.16804452240467072,0.9067108035087585,0.24310849606990814,-0.2878456711769104,0.1550082564353943,0.9132445454597473,0.2623348534107208,-0.23713672161102295,0.1342136561870575,0.9257069230079651,0.288247287273407,-0.1656980663537979,0.10855590552091599,0.9368422031402588,0.31906184554100037,-0.07011136412620544,0.07677571475505829,0.9420135021209717,0.3501230478286743,0.0480603352189064,0.039788585156202316,0.9346234202384949,0.3744209110736847,0.18092221021652222,-0.0015162854688242078,0.909436047077179,0.3826979994773865,0.3153201937675476,-0.04733875393867493,0.8671069741249084,0.3786258399486542,0.43260228633880615,-0.08991315215826035,0.8132732510566711,0.3690677285194397,0.5188133716583252,-0.12361294031143188,0.7611448764801025,0.3618317246437073,0.5708404779434204,-0.14291812479496002,0.7230445146560669,0.35962656140327454,0.61670982837677,-0.1559390425682068,0.6826571226119995,0.356277734041214,0.656015932559967,-0.16786205768585205,0.6438413262367249,0.3524332046508789,0.6895811557769775,-0.1776997596025467,0.6071997284889221,0.34764742851257324,0.7173553109169006,-0.18771423399448395,0.5738519430160522,0.3435242176055908,0.7401295900344849,-0.19706246256828308,0.5434755682945251,0.3421197235584259,0.7585685849189758,-0.2045537531375885,0.5154469609260559,0.34474462270736694,0.7727617025375366,-0.20989663898944855,0.48983052372932434,0.35922104120254517,0.7698416709899902,-0.20930376648902893,0.4842478632926941,0.36655694246292114,0.7620762586593628,-0.20544900000095367,0.49261191487312317,0.3716830611228943,0.752284824848175,-0.1980898678302765,0.5066356658935547,0.37294015288352966,0.7402984499931335,-0.18588246405124664,0.5275619626045227,0.37555044889450073,0.7179532647132874,-0.16915090382099152,0.5611532330513,0.3757089674472809,0.685773491859436,-0.1433822214603424,0.6066292524337769,0.37283018231391907,0.6376942992210388,-0.10347457975149155,0.6660605669021606,0.37372368574142456,0.5502358078956604,-0.05039370432496071,0.7450044751167297,0.36860302090644836,0.4413096010684967,0.009820651262998581,0.8180961012840271,0.3618144989013672,0.297739714384079,0.062443602830171585,0.881216287612915,0.3478023409843445,0.14355134963989258,0.1100359857082367,0.9199557900428772,0.3299284279346466,0.020190032199025154,0.1452961415052414,0.9325388073921204,0.31739136576652527,-0.052768874913454056,0.1643046736717224,0.9324602484703064,0.32957005500793457,-0.07303410023450851,0.16218321025371552,0.9272250533103943,0.3656495213508606,-0.06924698501825333,0.14811594784259796,0.9162788391113281,0.39172282814979553,-0.06824801862239838,0.13467377424240112,0.9076113700866699,0.40492522716522217,-0.1210278570652008,0.12968942523002625,0.896977424621582,0.39833909273147583,-0.20185862481594086,0.13934025168418884,0.8838344812393188,0.38223162293434143,-0.26197853684425354,0.14713095128536224,0.8738528490066528,0.36385712027549744,-0.30189985036849976,0.15825900435447693,0.8668440580368042,0.3497585654258728,-0.32370662689208984,0.16935083270072937,0.8626721501350403,0.33154699206352234,-0.3260543644428253,0.18163341283798218,0.8664724230766296,0.3082710802555084,-0.3199157118797302,0.19511738419532776,0.874386727809906,0.28663206100463867,-0.3128783106803894,0.20699171721935272,0.8815348744392395,0.26482585072517395,-0.30185407400131226,0.2171948254108429,0.8897066116333008,0.25107258558273315,-0.28544721007347107,0.22184383869171143,0.8979241847991943,0.24152766168117523,-0.27024078369140625,0.21374814212322235,0.9071637988090515,0.23191921412944794,-0.2571633756160736,0.19966652989387512,0.9166317582130432,0.22535936534404755,-0.245762899518013,0.1859196126461029,0.9242551922798157,0.2258772850036621,-0.2359609156847,0.17732734978199005,0.9283624887466431,0.23339468240737915,-0.22632011771202087,0.17439274489879608,0.9294586777687073,0.24521060287952423,-0.2164335548877716,0.17309339344501495,0.9290140271186829,0.26070016622543335,-0.20638978481292725,0.17295268177986145,0.9271063208580017,0.2789745032787323,-0.19622957706451416,0.1739865094423294,0.9237943291664124,0.2990395128726959,-0.18673747777938843,0.17627088725566864,0.919039249420166,0.32032060623168945,-0.17790868878364563,0.1791485697031021,0.9130438566207886,0.3417871296405792,-0.1698092222213745,0.18295317888259888,0.9060212969779968,0.3627473711967468,-0.16268739104270935,0.18729743361473083,0.8982576727867126,0.38241225481033325,-0.1569872498512268,0.192040354013443,0.8900766372680664,0.3999779224395752,-0.1528017222881317,0.19728393852710724,0.881900429725647,0.41474321484565735,-0.15032123029232025,0.20297393202781677,0.8741814494132996,0.4260593056678772,-0.14990253746509552,0.20897206664085388,0.8673715591430664,0.4327542781829834,-0.1534634232521057,0.21705710887908936,0.861428439617157,0.4354301691055298,-0.1617916375398636,0.22758054733276367,0.8558219075202942,0.4360719621181488,-0.17312085628509521,0.23841261863708496,0.8503116369247437,0.4352785050868988,-0.18809090554714203,0.24770399928092957,0.8448652029037476,0.4214895963668823,-0.20958027243614197,0.2587199807167053,0.8434966206550598,0.39762043952941895,-0.23336850106716156,0.26433607935905457,0.8470912575721741,0.36561253666877747,-0.26037928462028503,0.26642748713493347,0.852963387966156,0.329291433095932,-0.2911808490753174,0.2684173583984375,0.8571656942367554,0.2958288788795471,-0.3203146755695343,0.26742425560951233,0.859283447265625,0.26820340752601624,-0.3501966595649719,0.26521363854408264,0.8573744893074036,0.24462100863456726,-0.3768042027950287,0.2635733187198639,0.8536441326141357,0.22717206180095673,-0.39845919609069824,0.2625732421875,0.8489277958869934,0.21630537509918213,-0.41220182180404663,0.26023703813552856,0.845918595790863,0.20765112340450287,-0.423798531293869,0.25866439938545227,0.8428336977958679,0.2002304196357727,-0.43454745411872864,0.25799062848091125,0.8393551707267761,0.19401004910469055,-0.4445665180683136,0.2579476535320282,0.8355739116668701,0.18881011009216309,-0.45384281873703003,0.2584266662597656,0.8316207528114319,0.1843152940273285,-0.46234971284866333,0.259382426738739,0.8276360630989075,0.18038725852966309,-0.47018128633499146,0.26056674122810364,0.8237081170082092,0.1769058257341385,-0.47739505767822266,0.2618057429790497,0.8199122548103333,0.17372801899909973,-0.48402538895606995,0.2629421651363373,0.8163329362869263,0.17078115046024323,-0.49014851450920105,0.26373445987701416,0.8130390048027039,0.16919128596782684,-0.49229443073272705,0.2630660831928253,0.8122910857200623,0.16916586458683014,-0.49163389205932617,0.2626989781856537,0.8128150701522827,0.16923345625400543,-0.49116045236587524,0.262597918510437,0.8131197690963745,0.1693648099899292,-0.49072322249412537,0.262679785490036,0.8133299946784973,0.1695019006729126,-0.4902729094028473,0.26276254653930664,0.8135462403297424],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998211860657,0.9999998211860657,0.9999997615814209,0.9999998211860657,0.9999998211860657,0.9999997615814209],"interpolation":2300,"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,3.6666667461395264],"values":[8.30560302734375,-6.7115888595581055,5.719296932220459,8.305571556091309,-6.711597442626953,5.719291687011719],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.7706883549690247,0.14633506536483765,-0.3653581142425537,0.5011376142501831,0.7753880620002747,0.14834797382354736,-0.3620929419994354,0.4956357777118683,0.7857535481452942,0.15311390161514282,-0.3602939546108246,0.478890061378479,0.798026442527771,0.15821963548660278,-0.3589983880519867,0.4574281573295593,0.8103997111320496,0.1619284451007843,-0.3574514389038086,0.4350401759147644,0.8214957118034363,0.16279146075248718,-0.35558003187179565,0.4149778485298157,0.8273946046829224,0.15991652011871338,-0.35197681188583374,0.4073784351348877,0.8294131755828857,0.15234805643558502,-0.34602734446525574,0.41125279664993286,0.8247916102409363,0.15144982933998108,-0.34071826934814453,0.42507967352867126,0.8213315010070801,0.15288060903549194,-0.33755943179130554,0.43369996547698975,0.8256896734237671,0.14758425951004028,-0.33642756938934326,0.42810264229774475,0.8266628980636597,0.1449468433856964,-0.3378513753414154,0.42599916458129883,0.8289351463317871,0.13978411257266998,-0.34317919611930847,0.4189927875995636,0.8316115140914917,0.1367499828338623,-0.3537910580635071,0.40565210580825806,0.8341535329818726,0.13636617362499237,-0.3689286708831787,0.3866313397884369,0.8323760628700256,0.13965462148189545,-0.38482171297073364,0.37357574701309204,0.8258305191993713,0.14865164458751678,-0.4010849893093109,0.36747437715530396,0.8023800253868103,0.18080832064151764,-0.41928163170814514,0.38431426882743835,0.7768880724906921,0.22170691192150116,-0.441520631313324,0.3903209865093231,0.7522780299186707,0.23987965285778046,-0.45733389258384705,0.40912240743637085,0.725865364074707,0.24459590017795563,-0.4676392674446106,0.44114142656326294,0.6977480053901672,0.2392631322145462,-0.46996912360191345,0.4847986400127411,0.6769425868988037,0.2309739887714386,-0.4703044295310974,0.5169268250465393,0.6666738986968994,0.2280307412147522,-0.47283220291137695,0.5291288495063782,0.6662641763687134,0.22172018885612488,-0.47474581003189087,0.5306113362312317,0.6730226278305054,0.19681128859519958,-0.4516514837741852,0.5516489148139954,0.6929958462715149,0.14722579717636108,-0.38804590702056885,0.5894926190376282,0.7121506929397583,0.07825348526239395,-0.29993683099746704,0.6298853754997253,0.7238160371780396,0.005022205878049135,-0.2044115960597992,0.6589999794960022,0.7289153337478638,-0.05651519447565079,-0.12300572544336319,0.6710872650146484,0.7233005166053772,-0.09251701831817627,-0.08034782856702805,0.6795743107795715,0.7135729193687439,-0.13300403952598572,-0.05887436866760254,0.6853156089782715,0.7015149593353271,-0.16008083522319794,-0.04691646248102188,0.6928563117980957,0.6913371086120605,-0.16663004457950592,-0.04675383120775223,0.7014995217323303,0.6815091371536255,-0.16930076479911804,-0.0483059398829937,0.7103161811828613,0.6681126952171326,-0.16474318504333496,-0.05296417325735092,0.7236573100090027,0.6434264779090881,-0.15689486265182495,-0.05991325154900551,0.7468578815460205,0.6211079359054565,-0.1506948173046112,-0.06503569334745407,0.7663461565971375,0.6049149632453918,-0.14237362146377563,-0.06920843571424484,0.7803959250450134,0.5927227139472961,-0.1362377107143402,-0.07225772738456726,0.7905048131942749,0.5870087146759033,-0.13757149875164032,-0.07253095507621765,0.7945024371147156,0.5859108567237854,-0.1319798231124878,-0.0749417170882225,0.7960361242294312,0.5895154476165771,-0.1215079128742218,-0.0783480852842331,0.7947131395339966,0.5973997712135315,-0.11655493080615997,-0.07991679012775421,0.7893933057785034,0.6124837398529053,-0.10850168764591217,-0.08391647785902023,0.7784915566444397,0.6334888339042664,-0.08674772083759308,-0.09578021615743637,0.7628845572471619,0.6660727858543396,-0.052134353667497635,-0.1160917803645134,0.7349501848220825,0.7169082760810852,-0.011716201901435852,-0.13748081028461456,0.683377206325531,0.7813892364501953,0.03137033060193062,-0.17206403613090515,0.5990331768989563,0.8513307571411133,0.04663299024105072,-0.18992166221141815,0.48681730031967163,0.9113763570785522,0.044193968176841736,-0.20326320827007294,0.35513952374458313,0.9540700912475586,0.025873223319649696,-0.20739313960075378,0.2146366983652115,0.9737092852592468,-0.004690997302532196,-0.205924391746521,0.09728003293275833,0.976844310760498,-0.01871972531080246,-0.21145816147327423,0.02665014937520027,0.9729623794555664,-0.008769365958869457,-0.23078621923923492,0.0022536839824169874,-0.9666081666946411,0.0014872703468427062,0.2562367618083954,0.0030506078619509935,0.9606337547302246,0.01234686654061079,-0.2774990499019623,0.00497013283893466,0.955618679523468,0.028723925352096558,-0.29064252972602844,0.03866332024335861,0.9527420401573181,0.0032591356430202723,-0.29885101318359375,0.054406821727752686,0.9484671950340271,-0.010848240926861763,-0.3066757321357727,0.07900857925415039,0.9335236549377441,0.005648839753121138,-0.3203471899032593,0.16087056696414948,0.9039769172668457,0.022141648456454277,-0.33290159702301025,0.26741719245910645,0.8684264421463013,0.0367521233856678,-0.3322482109069824,0.3661910891532898,0.8352830410003662,0.04921623319387436,-0.3174019753932953,0.44624656438827515,0.8065910935401917,0.06103324890136719,-0.29869139194488525,0.5064278244972229,0.7845402359962463,0.06808757036924362,-0.2806157171726227,0.5487399101257324,0.7709992527961731,0.062420155853033066,-0.26270291209220886,0.5767590999603271,0.7696449756622314,0.06216783449053764,-0.24870829284191132,0.5847442746162415,0.782548189163208,0.0707751139998436,-0.2457962930202484,0.5676208734512329,0.8005056381225586,0.0811532735824585,-0.24870538711547852,0.5392128825187683,0.7931393384933472,0.09681440144777298,-0.25037217140197754,0.5466906428337097,0.7791224122047424,0.0825745090842247,-0.22823549807071686,0.5779777765274048,0.7375131249427795,0.06767329573631287,-0.20812956988811493,0.6388871073722839,0.715510904788971,0.059929631650447845,-0.2019033581018448,0.6660988330841064,0.6738690137863159,0.03942713141441345,-0.19102555513381958,0.7126396894454956,0.6323340535163879,0.022227294743061066,-0.18742240965366364,0.7513538002967834,0.5952474474906921,0.0078107030130922794,-0.18788932263851166,0.7812280058860779,0.5605797171592712,-0.005682779476046562,-0.18956968188285828,0.806090235710144,0.5276049375534058,-0.018150417134165764,-0.19189588725566864,0.8273327946662903,0.4969766139984131,-0.02993418462574482,-0.19436919689178467,0.8451856970787048,0.46867701411247253,-0.04133393242955208,-0.19666185975074768,0.8602078557014465,0.4434877932071686,-0.0510915145277977,-0.2000100016593933,0.872183620929718,0.4224604070186615,-0.05780688673257828,-0.20567362010478973,0.8808427453041077,0.40801316499710083,-0.06172138825058937,-0.2143978476524353,0.8852961659431458,0.39981451630592346,-0.06270179897546768,-0.22692598402500153,0.8858450651168823,0.39570939540863037,-0.0623495951294899,-0.24196478724479675,0.8837305307388306,0.3941360414028168,-0.05916190892457962,-0.2603227198123932,0.8794252276420593,0.4087526202201843,-0.05268143117427826,-0.2834427058696747,0.8659135103225708,0.43455785512924194,-0.04285523295402527,-0.3093196153640747,0.8447747230529785,0.4683055281639099,-0.02837224490940571,-0.33785784244537354,0.8159271478652954,0.5070620179176331,-0.009333360008895397,-0.36664119362831116,0.779984176158905,0.544963538646698,0.01156214065849781,-0.39071959257125854,0.7417677044868469,0.582161545753479,0.03522682934999466,-0.4114779233932495,0.7003805637359619,0.6240910291671753,0.05598323047161102,-0.4248342514038086,0.6533698439598083,0.6568933129310608,0.0787065401673317,-0.42747044563293457,0.6160888075828552,0.6800306439399719,0.09800022095441818,-0.41893303394317627,0.59367436170578,0.6955001354217529,0.12058355659246445,-0.4059491455554962,0.5804690718650818,0.7109991312026978,0.13442765176296234,-0.39006391167640686,0.5694378018379211,0.7319923639297485,0.13449326157569885,-0.3740997612476349,0.5533064603805542,0.7462112307548523,0.1272692233324051,-0.37150388956069946,0.5375463962554932,0.7581669688224792,0.12332814931869507,-0.3683020770549774,0.5237618684768677,0.7664229869842529,0.12864652276039124,-0.3650156855583191,0.5126493573188782,0.7693859338760376,0.135104700922966,-0.3651109039783478,0.5064444541931152,0.7707802057266235,0.14040128886699677,-0.3658333122730255,0.502345860004425,0.7713618874549866,0.14503169059753418,-0.36663538217544556,0.4995448887348175,0.7713889479637146,0.14777521789073944,-0.3663169741630554,0.49893224239349365,0.7711189985275269,0.1477338820695877,-0.36617806553840637,0.49946343898773193,0.7710021138191223,0.14735998213291168,-0.36600780487060547,0.49987897276878357,0.7708807587623596,0.14679859578609467,-0.36579546332359314,0.5003864765167236,0.7706332206726074,0.14627501368522644,-0.3655521273612976,0.5010983347892761],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998807907104,1,0.9999990463256836,0.9999998807907104,1,0.9999992847442627],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.0000044858306864625774,25.812705993652344,-1.2010218597424682e-8,0.00000757565385356429,25.812713623046875,-0.000014529970030707773],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.3588152527809143,0.07593890279531479,0.12724877893924713,0.9215707778930664,0.35507726669311523,0.0705949142575264,0.1247548907995224,0.9237817525863647,0.343668133020401,0.06095844879746437,0.12090085446834564,0.9292789101600647,0.3268248438835144,0.04961395263671875,0.11737173795700073,0.9364549517631531,0.30375829339027405,0.04990312084555626,0.11906393617391586,0.9439620971679688,0.27713608741760254,0.05082293599843979,0.1205577701330185,0.9518815279006958,0.26196977496147156,0.023702820762991905,0.10891956090927124,0.9586169719696045,0.24580910801887512,0.026315903291106224,0.10642743110656738,0.9630984663963318,0.24287277460098267,0.03981483727693558,0.11143829673528671,0.962813138961792,0.23513564467430115,0.042392000555992126,0.11087539792060852,0.9646868705749512,0.19784653186798096,0.03739401698112488,0.10221649706363678,0.9741715788841248,0.19262640178203583,0.022056670859456062,0.09580191224813461,0.9763352870941162,0.18941575288772583,-0.023885158821940422,0.07786130160093307,0.9785135984420776,0.19561216235160828,-0.10437365621328354,0.05027936026453972,0.9738141298294067,0.19531762599945068,-0.15872052311897278,0.033342067152261734,0.9672368764877319,0.19828563928604126,-0.18639183044433594,0.025539442896842957,0.9619192481040955,0.1948130875825882,-0.201470747590065,0.032863814383745193,0.9593629837036133,0.253913938999176,-0.20606136322021484,0.05619785562157631,0.9433494806289673,0.3128281831741333,-0.24010135233402252,0.10105102509260178,0.9133884906768799,0.3636322617530823,-0.2768896222114563,0.10230764001607895,0.8835365772247314,0.41105374693870544,-0.2848219871520996,0.11283132433891296,0.8585920929908752,0.47970980405807495,-0.26273736357688904,0.11052349209785461,0.8298386335372925,0.526908278465271,-0.23072846233844757,0.11191387474536896,0.8103131055831909,0.5704835057258606,-0.21432653069496155,0.10352592915296555,0.7860630750656128,0.6051676273345947,-0.19670580327510834,0.09487703442573547,0.7655569314956665,0.6281593441963196,-0.1777505725622177,0.0837368369102478,0.7528670430183411,0.6281226873397827,-0.15927855670452118,0.042369093745946884,0.7604585289955139,0.600386917591095,-0.1375180035829544,-0.01395224966108799,0.7876735925674438,0.5500741600990295,-0.1104455441236496,-0.06330989301204681,0.8253557085990906,0.4913143515586853,-0.07447589188814163,-0.08451712131500244,0.863666832447052,0.4676814377307892,0.11015935242176056,0.01978144235908985,0.8767825961112976,0.4563747048377991,0.16624651849269867,0.06109100580215454,0.8719817399978638,0.4720294177532196,0.17121055722236633,0.07906561344861984,0.8611758351325989,0.5080236792564392,0.1587100774049759,0.09099966287612915,0.8416900634765625,0.5392057299613953,0.14670616388320923,0.10221339762210846,0.822974443435669,0.5721258521080017,0.13147787749767303,0.10484278947114944,0.802741289138794,0.61246657371521,0.12310561537742615,0.10350950807332993,0.7739609479904175,0.6372435688972473,0.12467379122972488,0.1083231121301651,0.7527570724487305,0.6407152414321899,0.14042358100414276,0.12519274652004242,0.744373619556427,0.6315777897834778,0.1519681066274643,0.13566863536834717,0.7480703592300415,0.6131463646888733,0.15744316577911377,0.13824644684791565,0.761676549911499,0.5933778882026672,0.16335175931453705,0.14059846103191376,0.7755327224731445,0.5837544202804565,0.16673952341079712,0.13020819425582886,0.7838842868804932,0.5786793828010559,0.1699276715517044,0.10461930930614471,0.7907651662826538,0.5751070380210876,0.172679141163826,0.07488775253295898,0.7961316704750061,0.572199285030365,0.1817481964826584,0.05817638710141182,0.7976033687591553,0.5671695470809937,0.17230769991874695,0.043410152196884155,0.8042041659355164,0.5590800642967224,0.1554969847202301,0.027778178453445435,0.8139278888702393,0.548742949962616,0.12565234303474426,0.01792733557522297,0.826299786567688,0.5319089889526367,0.08819884806871414,0.0047702607698738575,0.8421823382377625,0.4938652217388153,0.02805059216916561,-0.006465332582592964,0.8690618872642517,0.43364599347114563,-0.04054708406329155,0.005433205980807543,0.9001542329788208,0.3654748797416687,-0.07737600058317184,0.026089005172252655,0.9272326827049255,0.3243992030620575,-0.08954516053199768,0.03583443537354469,0.9409903287887573,0.2876622974872589,-0.08595649152994156,0.04942544922232628,0.9525854587554932,0.22641536593437195,-0.11433560401201248,0.05329211801290512,0.9658278226852417,0.19389089941978455,-0.14429104328155518,0.04921937733888626,0.9691047072410583,0.24091234803199768,-0.1590709239244461,0.03734976425766945,0.9566936492919922,0.2413376122713089,-0.16621951758861542,0.024702439084649086,0.9557808637619019,0.265518456697464,-0.12536939978599548,0.03389459848403931,0.9553186297416687,0.4266797602176666,-0.13221929967403412,-0.018285663798451424,0.8944988250732422,0.5643002986907959,-0.1297539919614792,-0.08645328134298325,0.8107126355171204,0.6464980840682983,-0.09231112152338028,-0.12403310090303421,0.7470841407775879,0.6821831464767456,-0.05124141648411751,-0.1274918019771576,0.718154788017273,0.6814339756965637,-0.008847367018461227,-0.10733458399772644,0.7239121794700623,0.640582799911499,0.022207554429769516,-0.08429419994354248,0.7629253268241882,0.556035578250885,0.03213447332382202,-0.06825362145900726,0.8277277946472168,0.43821296095848083,0.025123421102762222,-0.04338919371366501,0.8974717855453491,0.2828899919986725,0.0245120320469141,0.012216486968100071,0.9587613344192505,0.1275753527879715,0.051006801426410675,0.06848694384098053,0.9881459474563599,0.07940999418497086,0.09019413590431213,0.10140076279640198,0.9875611662864685,0.13277457654476166,0.08447415381669998,0.09007977694272995,0.9834229350090027,0.21563707292079926,0.12243098020553589,0.08053605258464813,0.9654145836830139,0.23683257400989532,0.13514173030853271,0.08526112884283066,0.9583202004432678,0.32551655173301697,0.15201117098331451,0.07465827465057373,0.9302460551261902,0.4098142385482788,0.14188332855701447,0.061181917786598206,0.898987352848053,0.4664117097854614,0.14323516190052032,0.0588013157248497,0.870911180973053,0.5079208612442017,0.1421661674976349,0.0565621443092823,0.8477063179016113,0.5395098924636841,0.13411104679107666,0.04965750873088837,0.829745352268219,0.561284065246582,0.1373094618320465,0.04786914214491844,0.8147483468055725,0.5801311731338501,0.14002038538455963,0.04417901113629341,0.8011806607246399,0.5962860584259033,0.13251961767673492,0.033804573118686676,0.7910364866256714,0.6087260842323303,0.13011205196380615,0.030645526945590973,0.7820385694503784,0.617664098739624,0.1273314505815506,0.028844838961958885,0.7755293846130371,0.6225740313529968,0.1328284591436386,0.03467913717031479,0.77042555809021,0.6248491406440735,0.13263964653015137,0.03511610999703407,0.7685942649841309,0.6258034706115723,0.13322214782238007,0.03611157462000847,0.7676703929901123,0.6235120892524719,0.13391365110874176,0.03708156943321228,0.7693665623664856,0.6152182817459106,0.13658230006694794,0.03994329646229744,0.7754071950912476,0.5989311337471008,0.1406826227903366,0.04422879219055176,0.7871046662330627,0.5728607773780823,0.14432360231876373,0.048353977501392365,0.8053962588310242,0.5338351130485535,0.1471150666475296,0.05253046378493309,0.8310341835021973,0.4766705334186554,0.1475561410188675,0.057434458285570145,0.8647043704986572,0.4020119905471802,0.12805014848709106,0.059752851724624634,0.9046652317047119,0.36176958680152893,0.11532935500144958,0.07805235683917999,0.9218078851699829,0.3476216495037079,0.11536527425050735,0.10217788815498352,0.9248836636543274,0.3603735566139221,0.09624241292476654,0.11165483295917511,0.9210871458053589,0.3736782670021057,0.09352197498083115,0.12328512966632843,0.9145594239234924,0.3709174394607544,0.08630476891994476,0.12468313425779343,0.9162018895149231,0.36218464374542236,0.099615678191185,0.13428965210914612,0.9169870615005493,0.35170233249664307,0.10246220976114273,0.13861143589019775,0.9201053380966187,0.3409355580806732,0.08718498051166534,0.1341952383518219,0.9263656735420227,0.33768704533576965,0.08126306533813477,0.13189172744750977,0.9284225106239319,0.34021174907684326,0.0773359015583992,0.12938745319843292,0.9281885623931885,0.34649160504341125,0.07460632175207138,0.12696275115013123,0.926422119140625,0.35149791836738586,0.07410208135843277,0.1262131780385971,0.9246774315834045,0.3531992733478546,0.07493574172258377,0.12664540112018585,0.9239025115966797,0.354879230260849,0.07539667189121246,0.12689214944839478,0.9231871366500854,0.35668057203292847,0.07565237581729889,0.127069890499115,0.9224472641944885,0.35871556401252747,0.07589215040206909,0.12726064026355743,0.9216117858886719],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1,0.9999998807907104,1,1,1,1],"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.000004623776931111934,22.742708206176758,-0.000002597890897959587,-0.000005728546057071071,22.742698669433594,0.000011626354535110295],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.4310854971408844,-0.0769377052783966,0.12125634402036667,0.8908101916313171,-0.4307025671005249,-0.07852070778608322,0.12371387332677841,0.890519380569458,-0.4283575713634491,-0.08145172148942947,0.12815099954605103,0.8907596468925476,-0.42342036962509155,-0.08451231569051743,0.1326219141483307,0.8921794891357422,-0.4170994460582733,-0.0976242870092392,0.13044260442256927,0.8941377401351929,-0.4070744812488556,-0.106342613697052,0.12708835303783417,0.8982372283935547,-0.38840794563293457,-0.07474013417959213,0.13593171536922455,0.9083368182182312,-0.364536315202713,-0.08145759999752045,0.1349136382341385,0.9177560806274414,-0.33933404088020325,-0.10370677709579468,0.14704078435897827,0.9232964515686035,-0.30995339155197144,-0.14058201014995575,0.19796493649482727,0.9192255139350891,-0.2859048843383789,-0.14608246088027954,0.22942601144313812,0.9188482165336609,-0.3224186301231384,-0.14554692804813385,0.218863382935524,0.9093740582466125,-0.36219531297683716,-0.13606058061122894,0.2073918581008911,0.8984935879707336,-0.4016278088092804,-0.10959295928478241,0.21650366485118866,0.8830689191818237,-0.41905921697616577,-0.10219935327768326,0.21287919580936432,0.8767138123512268,-0.416394978761673,-0.08961763978004456,0.19214053452014923,0.8841187357902527,-0.38874557614326477,-0.06171368062496185,0.14707675576210022,0.9074341654777527,-0.3647781014442444,-0.03119353950023651,0.10603199899196625,0.9245113134384155,-0.33423590660095215,-0.02350154146552086,0.06705787777900696,0.9398070573806763,-0.30661314725875854,-0.024259518831968307,0.02382734976708889,0.9512266516685486,-0.3386857509613037,-0.0015626373933628201,-0.021628614515066147,0.9406496286392212,-0.4120013117790222,0.034709203988313675,-0.03875890001654625,0.9096965789794922,-0.4535076320171356,0.05190562456846237,-0.03468020260334015,0.8890635371208191,-0.4968552589416504,0.07196754962205887,-0.019207660108804703,0.864630937576294,-0.5421440005302429,0.08310566842556,-0.0006022814777679741,0.8361656665802002,-0.5840927362442017,0.09187687933444977,0.011571704410016537,0.8063872456550598,-0.6119174957275391,0.07384006679058075,0.03430983051657677,0.7867194414138794,-0.6177104115486145,0.05176859721541405,0.05406012758612633,0.7828354835510254,-0.603200376033783,0.046765971928834915,0.06389062851667404,0.7936499714851379,-0.5776669383049011,0.07139010727405548,0.06384618580341339,0.8106343746185303,-0.5570111870765686,-0.005285517778247595,-0.045487772673368454,0.8292415142059326,-0.5269454717636108,0.02446919120848179,-0.1033574640750885,0.8432360291481018,-0.5039587616920471,0.0559699647128582,-0.13532613217830658,0.8512225151062012,-0.5109817981719971,0.060935087502002716,-0.1700012981891632,0.840407133102417,-0.525904655456543,0.05733214691281319,-0.1988445371389389,0.8249837160110474,-0.5468089580535889,0.059789445251226425,-0.20793412625789642,0.8088192343711853,-0.5669991970062256,0.060712169855833054,-0.19843582808971405,0.7971506714820862,-0.5740978121757507,0.0563720166683197,-0.1883290857076645,0.7948371171951294,-0.5657099485397339,0.06005934625864029,-0.1739576756954193,0.8038058876991272,-0.5496532320976257,0.07444582134485245,-0.1530705839395523,0.8178682923316956,-0.532074511051178,0.09318900853395462,-0.13225704431533813,0.8310960531234741,-0.5255240201950073,0.08744408935308456,-0.11599506437778473,0.8382858633995056,-0.5284201502799988,0.07713575661182404,-0.09793733060359955,0.8397800326347351,-0.5365608930587769,0.0615851916372776,-0.0752926915884018,0.8382366895675659,-0.5470684170722961,0.039793502539396286,-0.04900500550866127,0.8347042202949524,-0.5599325299263,-0.006094388663768768,-0.021889610216021538,0.828226625919342,-0.5768427848815918,-0.01975392922759056,0.025181952863931656,0.8162279725074768,-0.5998562574386597,-0.037625547498464584,0.08596804738044739,0.794585645198822,-0.6377795338630676,-0.05293823033571243,0.1376962661743164,0.7559593319892883,-0.6938849091529846,-0.052242130041122437,0.15068896114826202,0.702201783657074,-0.7308106422424316,-0.05961982160806656,0.16097162663936615,0.6606432199478149,-0.7354216575622559,-0.07031095027923584,0.1783076673746109,0.6499366760253906,-0.7223246693611145,-0.07417342811822891,0.18182553350925446,0.6630873680114746,-0.6937990784645081,-0.05127866566181183,0.211497962474823,0.6864997744560242,-0.6590455770492554,-0.0216450747102499,0.21503868699073792,0.7203810214996338,-0.6080802083015442,-0.016513310372829437,0.22145779430866241,0.7621825337409973,-0.5219771265983582,-0.02482464164495468,0.23357342183589935,0.8199799656867981,-0.405790239572525,-0.04011097177863121,0.22009459137916565,0.8861624002456665,-0.2777116298675537,0.023142648860812187,0.11672212928533554,0.9532662630081177,-0.21950043737888336,0.09782674163579941,0.012848044745624065,0.9706103205680847,-0.31302332878112793,0.09872595965862274,-0.00037108946708031,0.9446001648902893,-0.4297264814376831,0.09233809262514114,-0.030429081991314888,0.897709846496582,-0.5191795825958252,0.08817076683044434,-0.038512058556079865,0.8492321968078613,-0.5645589232444763,0.08116939663887024,-0.0350973904132843,0.820641815662384,-0.578877866268158,0.07026197761297226,-0.011047166772186756,0.8123064041137695,-0.575456976890564,0.05038386955857277,0.024999229237437248,0.815895676612854,-0.5439013242721558,0.03323391452431679,0.032590944319963455,0.837857186794281,-0.48598507046699524,0.015666190534830093,0.021520722657442093,0.8735616207122803,-0.4273028075695038,-0.002216809196397662,0.01812947355210781,0.9039240479469299,-0.4263998568058014,-0.03608454018831253,0.06452138721942902,0.9015088081359863,-0.4545169174671173,-0.0819617211818695,0.15428701043128967,0.8734369874000549,-0.5089930295944214,-0.09678449481725693,0.16047658026218414,0.8401226997375488,-0.4090494215488434,-0.10767414420843124,0.11486738175153732,0.8988271951675415,-0.3060795068740845,-0.09957026690244675,0.1085624098777771,0.9405398964881897,-0.31398317217826843,-0.09916451573371887,0.1001409962773323,0.9389104247093201,-0.3539297580718994,-0.074928417801857,0.09713627398014069,0.9271914958953857,-0.38851621747016907,-0.06419693678617477,0.08854323625564575,0.9149284362792969,-0.41327181458473206,-0.052680034190416336,0.08198627084493637,0.9053781032562256,-0.4301286041736603,-0.035320039838552475,0.0807022899389267,0.898459255695343,-0.4413183331489563,-0.03193138539791107,0.07640789449214935,0.8935213088989258,-0.4510050415992737,-0.029575219377875328,0.07372777163982391,0.8889791965484619,-0.4577910602092743,-0.016976548358798027,0.07718625664710999,0.8855401873588562,-0.46280643343925476,-0.010915739461779594,0.0768110379576683,0.8830578327178955,-0.4668208062648773,-0.0067440215498209,0.07700139284133911,0.8809674382209778,-0.47067347168922424,-0.014266785234212875,0.07311174273490906,0.8791573643684387,-0.47327134013175964,-0.015183810144662857,0.07363275438547134,0.8777026534080505,-0.47585999965667725,-0.015384902246296406,0.07381828874349594,0.8762827515602112,-0.47909945249557495,-0.017264900729060173,0.07556223124265671,0.8743317723274231,-0.4773215651512146,-0.016923731192946434,0.07650134712457657,0.8752287030220032,-0.4687032401561737,-0.01658632978796959,0.07867018133401871,0.8796892762184143,-0.45355144143104553,-0.02010967768728733,0.08471924811601639,0.8869664072990417,-0.4284977316856384,-0.026575608178973198,0.09337888658046722,0.8983116745948792,-0.39217618107795715,-0.03793710470199585,0.104632668197155,0.91313236951828,-0.3496982157230377,-0.03089483082294464,0.11830854415893555,0.9288485646247864,-0.3626878261566162,-0.013550164178013802,0.1071789488196373,0.9256277084350586,-0.38168755173683167,-0.016489874571561813,0.08810314536094666,0.9199351072311401,-0.37930935621261597,-0.07202745229005814,0.11955676972866058,0.9146817326545715,-0.375518798828125,-0.12400161474943161,0.1373756229877472,0.9081503748893738,-0.3826363682746887,-0.13275346159934998,0.14314128458499908,0.9030373692512512,-0.4039720296859741,-0.10735900700092316,0.12072239816188812,0.9003925323486328,-0.4201056957244873,-0.07899997383356094,0.09991414844989777,0.8984917402267456,-0.41876089572906494,-0.06860209256410599,0.1069580689072609,0.8991624116897583,-0.4163983464241028,-0.07240153849124908,0.11292911320924759,0.8992316126823425,-0.4178142249584198,-0.07540114969015121,0.11787407845258713,0.8976924419403076,-0.4218900501728058,-0.07772465795278549,0.12190580368041992,0.8950455784797668,-0.4250079095363617,-0.07846486568450928,0.12331809848546982,0.8933107852935791,-0.42625775933265686,-0.07810591906309128,0.12283442169427872,0.8928132653236389,-0.4277282953262329,-0.07773003727197647,0.12233276665210724,0.8922114372253418,-0.42931169271469116,-0.0773397833108902,0.12181378901004791,0.8915556073188782,-0.43091103434562683,-0.07692881673574448,0.12126079201698303,0.8908947706222534],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999999403953552,0.9999998211860657,0.9999998211860657,1,0.9999998807907104,0.9999996423721313],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,3.6666667461395264],"values":[5.114140435580339e-7,13.415114402770996,-0.0000030992887332104146,-6.605157523154048e-7,13.415117263793945,-0.00002021501677518245],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[-0.30698150396347046,-0.005662641953676939,-0.0018292005406692624,0.9516968727111816,-0.3069814443588257,-0.005662634968757629,-0.0018291944870725274,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1,0.9999999403953552,1,0.9999999403953552,0.9999998807907104,1],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,3.6666667461395264],"values":[-11.139496803283691,-4.02009916305542,0.4202994108200073,-11.139521598815918,-4.020109176635742,0.42030173540115356],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.7031707763671875,-0.3038426339626312,0.28104129433631897,0.5781404376029968,0.7046774625778198,-0.3029307723045349,0.28038981556892395,0.5770998597145081,0.708472728729248,-0.30010947585105896,0.2780167758464813,0.5750716924667358,0.7143372297286987,-0.29595017433166504,0.27413561940193176,0.5718264579772949,0.7223542332649231,-0.29210373759269714,0.26956573128700256,0.5658746957778931,0.7335176467895508,-0.2899099588394165,0.265609472990036,0.5543968081474304,0.7470477223396301,-0.2901468575000763,0.26244115829467773,0.537456214427948,0.7596537470817566,-0.2924252450466156,0.25800859928131104,0.520427942276001,0.7727110981941223,-0.29639551043510437,0.25337105989456177,0.5008696913719177,0.7830589413642883,-0.3029308319091797,0.2472306787967682,0.4836616814136505,0.7909673452377319,-0.31053757667541504,0.2396060824394226,0.46960195899009705,0.7957409620285034,-0.3204289972782135,0.23062437772750854,0.4592755436897278,0.7989498376846313,-0.33301812410354614,0.22194315493106842,0.44890904426574707,0.8012626767158508,-0.3463054299354553,0.21392491459846497,0.4385052025318146,0.8024640083312988,-0.3599262833595276,0.206120103597641,0.4289744794368744,0.8062606453895569,-0.3733416497707367,0.20100516080856323,0.41250061988830566,0.8093857169151306,-0.38938528299331665,0.19682444632053375,0.3931080996990204,0.8116273880004883,-0.40930014848709106,0.1943204551935196,0.36874642968177795,0.8187558054924011,-0.4263487160205841,0.19789642095565796,0.3297010362148285,0.8287558555603027,-0.4348081052303314,0.2022462636232376,0.28844764828681946,0.8473190665245056,-0.4278545379638672,0.20957057178020477,0.23467226326465607,0.861330509185791,-0.4137498736381531,0.1991383582353592,0.2174045294523239,0.863343358039856,-0.4173114001750946,0.18337975442409515,0.2164747565984726,0.8645323514938354,-0.41308140754699707,0.16207708418369293,0.23596327006816864,0.8780972957611084,-0.3977286219596863,0.16796906292438507,0.20626090466976166,0.8867244124412537,-0.3488648533821106,0.14638374745845795,0.2656782567501068,0.8891612887382507,-0.2867315411567688,0.1483922302722931,0.32427918910980225,0.873729944229126,-0.20718251168727875,0.19175511598587036,0.3961077630519867,0.8421809673309326,-0.13797396421432495,0.2542833089828491,0.45501038432121277,0.8032156825065613,-0.09209982305765152,0.3119494915008545,0.49904879927635193,0.7568247318267822,-0.07530026882886887,0.36216679215431213,0.5388705134391785,0.7060916423797607,-0.09733720868825912,0.4100573658943176,0.5690458416938782,0.6586040258407593,-0.1443917155265808,0.45991939306259155,0.5778113603591919,0.6193428039550781,-0.19502553343772888,0.503728449344635,0.5697695016860962,0.566837728023529,-0.2695935368537903,0.5430074334144592,0.5578146576881409,0.5383904576301575,-0.30712223052978516,0.5789980888366699,0.5296913981437683,0.5434481501579285,-0.30623939633369446,0.621678352355957,0.4737061858177185,0.5348187685012817,-0.3206595182418823,0.6298666000366211,0.46304911375045776,0.5296956300735474,-0.3290332555770874,0.6268907189369202,0.4670840799808502,0.5310204029083252,-0.3305342197418213,0.628140926361084,0.46282121539115906,0.534841001033783,-0.3273927569389343,0.63020920753479,0.4578160047531128,0.5319589376449585,-0.32435545325279236,0.6197217702865601,0.4772401750087738,0.5314218997955322,-0.32050248980522156,0.6065093874931335,0.4970064163208008,0.5341207981109619,-0.315131813287735,0.5915171504020691,0.5152809023857117,0.544671356678009,-0.29995670914649963,0.5769200325012207,0.529643714427948,0.5539292693138123,-0.293279767036438,0.5565443634986877,0.5453509092330933,0.5549260377883911,-0.2939201891422272,0.5279970169067383,0.571740448474884,0.5497668981552124,-0.29877057671546936,0.49352073669433594,0.6040942668914795,0.5323287844657898,-0.2895961403846741,0.44482365250587463,0.6594635248184204,0.5242642760276794,-0.2834520637989044,0.40750694274902344,0.691910445690155,0.5350227355957031,-0.24599912762641907,0.3652239739894867,0.7210038304328918,0.5469046235084534,-0.17972896993160248,0.3045119643211365,0.7588579058647156,0.5567539930343628,-0.1261591613292694,0.24597159028053284,0.7833306193351746,0.5585811138153076,-0.10724411904811859,0.19179190695285797,0.7998135685920715,0.5534108877182007,-0.10208778083324432,0.17552447319030762,0.8077782392501831,0.5394703149795532,-0.08793842047452927,0.1926914006471634,0.8149285912513733,0.5421541333198547,-0.0779900848865509,0.20070625841617584,0.812221348285675,0.5606449246406555,-0.05379367247223854,0.15382081270217896,0.8118637204170227,0.5911003947257996,-0.03175266832113266,0.10171246528625488,0.7995290756225586,0.6274329423904419,-0.02613290585577488,0.0733766257762909,0.7747650742530823,0.666587233543396,-0.041838210076093674,0.05255455523729324,0.7423941493034363,0.6997799873352051,-0.06896410137414932,0.0359157919883728,0.7101141214370728,0.7323371767997742,-0.10865907371044159,0.028234751895070076,0.6716236472129822,0.7592942118644714,-0.1520843207836151,0.02815084531903267,0.6320998668670654,0.780319333076477,-0.19286110997200012,0.03691098466515541,0.5937541127204895,0.7950385212898254,-0.22981473803520203,0.05820349603891373,0.5583111643791199,0.8045569658279419,-0.26090219616889954,0.08931058645248413,0.5259673595428467,0.8169212937355042,-0.279645711183548,0.1307767629623413,0.4871707260608673,0.828944206237793,-0.2982845604419708,0.18334271013736725,0.4361918270587921,0.8342710733413696,-0.3197190463542938,0.2425798922777176,0.37805625796318054,0.8251543045043945,-0.3346192538738251,0.2936182916164398,0.3477623760700226,0.8089008331298828,-0.3444565236568451,0.3398782014846802,0.33393415808677673,0.790195107460022,-0.3538210690021515,0.38366565108299255,0.3212522268295288,0.7711169123649597,-0.3559998869895935,0.4200204610824585,0.3197273015975952,0.7550809979438782,-0.34784215688705444,0.44865545630455017,0.32797369360923767,0.7448719143867493,-0.33524593710899353,0.4732726216316223,0.3298318386077881,0.7387151122093201,-0.32250484824180603,0.488445907831192,0.33423227071762085,0.7372854351997375,-0.3094298541545868,0.5019174218177795,0.32976099848747253,0.7386363744735718,-0.2961020767688751,0.5127492547035217,0.32222381234169006,0.7409847378730774,-0.2855161428451538,0.5201607942581177,0.31441205739974976,0.7458175420761108,-0.2738391160964966,0.5263038873672485,0.3029399514198303,0.7498098611831665,-0.2699775695800781,0.5299230813980103,0.28996363282203674,0.7528014183044434,-0.26875579357147217,0.5318545699119568,0.2796265184879303,0.7531934976577759,-0.2728288471698761,0.5310141444206238,0.2762027382850647,0.749154269695282,-0.2837280333042145,0.5252150893211365,0.2870808243751526,0.7414209246635437,-0.2973027229309082,0.5144199728965759,0.31189462542533875,0.7317924499511719,-0.30937260389328003,0.5021234154701233,0.34152665734291077,0.7167552709579468,-0.33049747347831726,0.4890631139278412,0.37128230929374695,0.7030985951423645,-0.3511406481266022,0.4778611660003662,0.3924300968647003,0.6889746785163879,-0.37229791283607483,0.4658098816871643,0.4119821786880493,0.6733909845352173,-0.39306625723838806,0.4516342282295227,0.4336704909801483,0.6623759269714355,-0.40518832206726074,0.4389982521533966,0.4520632326602936,0.6557183861732483,-0.4049578905105591,0.4249081611633301,0.4748637080192566,0.65348881483078,-0.3962309658527374,0.41150209307670593,0.4966079294681549,0.6575534343719482,-0.3789476752281189,0.40083879232406616,0.5131768584251404,0.6669545769691467,-0.3589617908000946,0.3927111029624939,0.5216283202171326,0.6723295450210571,-0.34053125977516174,0.3794821798801422,0.5366608500480652,0.6762000918388367,-0.32294997572898865,0.36493751406669617,0.5525192022323608,0.6819809675216675,-0.3065009117126465,0.3525509536266327,0.5627318024635315,0.6873751878738403,-0.295783668756485,0.341684490442276,0.5685763955116272,0.6913880109786987,-0.289293110370636,0.3311755955219269,0.5732494592666626,0.6936097145080566,-0.2856043577194214,0.3202349841594696,0.5786064267158508,0.6937059164047241,-0.28454235196113586,0.3089205324649811,0.5851290225982666,0.6936375498771667,-0.28541362285614014,0.2990780770778656,0.5898799896240234,0.6936774253845215,-0.2876293361186981,0.29090720415115356,0.5928356051445007,0.6959279775619507,-0.29074814915657043,0.28585121035575867,0.5911336541175842,0.698794424533844,-0.2946333587169647,0.282946914434433,0.5872126221656799,0.7011066675186157,-0.2979479730129242,0.28111666440963745,0.5836521983146667,0.7025954127311707,-0.301071435213089,0.28046372532844543,0.5805650353431702,0.7032122015953064,-0.30381739139556885,0.2810540199279785,0.5780971050262451],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1.000000238418579,1,1.000000238418579,1,1.0000001192092896,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,3.6666667461395264],"values":[-0.0000011777744930441258,25.527101516723633,-5.927968800278904e-7,-0.000007552665010734927,25.5271053314209,0.0000072443131102772895],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.5326458811759949,-0.18511788547039032,-0.019296834245324135,0.8256194591522217,0.5288496017456055,-0.18687890470027924,-0.020933760330080986,0.827620804309845,0.5191652774810791,-0.19238102436065674,-0.02579660899937153,0.8323410153388977,0.507342517375946,-0.18765297532081604,-0.023245487362146378,0.8407434821128845,0.49376675486564636,-0.17980720102787018,-0.01896677166223526,0.8505904674530029,0.4785197675228119,-0.17263446748256683,-0.015825610607862473,0.8607936501502991,0.46085789799690247,-0.16986750066280365,-0.01635894365608692,0.8709118366241455,0.44304537773132324,-0.16073787212371826,-0.013314971700310707,0.881871223449707,0.42438948154449463,-0.15190428495407104,-0.01129422802478075,0.8925755620002747,0.40300098061561584,-0.13890501856803894,-0.008137688972055912,0.9045603275299072,0.37767359614372253,-0.12621672451496124,-0.006869764998555183,0.917270302772522,0.3490900695323944,-0.11419876664876938,-0.007957560941576958,0.930070698261261,0.31880807876586914,-0.10244204103946686,-0.01033194549381733,0.94221031665802,0.27983376383781433,-0.08888836205005646,-0.010855672881007195,0.9558629989624023,0.2411234825849533,-0.0797610729932785,-0.012584595941007137,0.9671294093132019,0.2069857269525528,-0.06883824616670609,-0.014927934855222702,0.9758049845695496,0.18963086605072021,-0.05750614032149315,-0.015395824797451496,0.9800490736961365,0.19061142206192017,-0.0467468686401844,-0.01154452096670866,0.9804839491844177,0.18527838587760925,-0.029785066843032837,-0.007803161162883043,0.9822036027908325,0.18635326623916626,-0.015724841505289078,-0.005825613159686327,0.9823396801948547,0.17322134971618652,0.004706803243607283,-0.00607815757393837,0.9848529696464539,0.23303139209747314,0.03517281636595726,0.01815064623951912,0.9716634750366211,0.2801372706890106,0.04876398295164108,0.03694304823875427,0.9580085873603821,0.29753512144088745,0.058380287140607834,0.0460805743932724,0.9518094062805176,0.23282508552074432,-0.016796553507447243,0.006562733091413975,0.9723514318466187,0.36429572105407715,-0.07575780153274536,0.020078038796782494,0.9279796481132507,0.48038050532341003,-0.07960504293441772,0.0476270355284214,0.872140645980835,0.5557304620742798,-0.06971894204616547,0.07312849909067154,0.825200080871582,0.5893430113792419,-0.06870175898075104,0.08230570703744888,0.8007376194000244,0.5949140787124634,-0.07171570509672165,0.08177546411752701,0.7963961362838745,0.5957488417625427,-0.0663289949297905,0.08828580379486084,0.7955434322357178,0.5793229937553406,-0.05922631919384003,0.09153520315885544,0.807773768901825,0.5352780818939209,-0.05845101922750473,0.07975079119205475,0.8388686776161194,0.4535984396934509,-0.06062411144375801,0.052066389471292496,0.8876160979270935,0.38450342416763306,-0.0825205072760582,0.02108490653336048,0.919185996055603,0.3495056927204132,-0.11177921295166016,-0.004983922932296991,0.9302292466163635,0.2652464509010315,-0.11809572577476501,-0.027976825833320618,0.956511914730072,0.3576532304286957,-0.08853232115507126,-0.00830936525017023,0.9296112656593323,0.4453516900539398,-0.08449408411979675,0.030064871534705162,0.8908528089523315,0.4994085133075714,-0.08874621987342834,0.04472137242555618,0.8606481552124023,0.5287399291992188,-0.08442336320877075,0.056924570351839066,0.8426543474197388,0.552375853061676,-0.0805133730173111,0.06700727343559265,0.826987624168396,0.5699899196624756,-0.07908794283866882,0.07322920858860016,0.8145514726638794,0.5810636878013611,-0.07461568713188171,0.08028207719326019,0.8064442276954651,0.5852217078208923,-0.0592554435133934,0.09539826959371567,0.8030588626861572,0.5897513628005981,-0.07407790422439575,0.08565683662891388,0.7996053695678711,0.5956840515136719,-0.07698862999677658,0.08508467674255371,0.7949804067611694,0.5996912121772766,-0.0836254358291626,0.07966771721839905,0.7918524742126465,0.5982723832130432,-0.09664586931467056,0.06650251895189285,0.7926583290100098,0.554223895072937,-0.12081548571586609,0.0349239818751812,0.8228121399879456,0.5075126886367798,-0.10227234661579132,0.03893723338842392,0.8546667098999023,0.4652222990989685,-0.10213126987218857,0.02784336917102337,0.8788414001464844,0.4637071192264557,-0.10891710221767426,0.022655539214611053,0.8789764046669006,0.49075648188591003,-0.1107206642627716,0.027518728747963905,0.8637949824333191,0.5238813161849976,-0.1185920462012291,0.028288645669817924,0.8430208563804626,0.5535773038864136,-0.13382567465305328,0.02110896073281765,0.8217039108276367,0.5746790766716003,-0.14282391965389252,0.016374295577406883,0.8056533336639404,0.5756039023399353,-0.1326395869255066,0.024805881083011627,0.8065181374549866,0.5680467486381531,-0.133645698428154,0.023309951648116112,0.8117378950119019,0.5544887185096741,-0.1366945207118988,0.019075147807598114,0.8206661939620972,0.5355600118637085,-0.13839387893676758,0.015564226545393467,0.8329347968101501,0.5149139165878296,-0.13685393333435059,0.01435941830277443,0.846125602722168,0.4963877201080322,-0.13991868495941162,0.009738908149302006,0.8566955327987671,0.48784342408180237,-0.13835325837135315,0.00962437316775322,0.8618437051773071,0.48181843757629395,-0.13847395777702332,0.008780973963439465,0.8652161359786987,0.4919036328792572,-0.16788659989833832,-0.011300918646156788,0.8542348742485046,0.5021737217903137,-0.17781907320022583,-0.017735978588461876,0.8461012840270996,0.5020816326141357,-0.18443435430526733,-0.022758599370718002,0.8446183204650879,0.485935777425766,-0.1871498078107834,-0.026272544637322426,0.8533177375793457,0.45080795884132385,-0.1956167072057724,-0.03497682139277458,0.8702200651168823,0.4487745463848114,-0.18425801396369934,-0.027731407433748245,0.8740030527114868,0.45637068152427673,-0.16244179010391235,-0.01309976726770401,0.8747381567955017,0.45782193541526794,-0.15141811966896057,-0.006333789322525263,0.8760316967964172,0.46865636110305786,-0.14038622379302979,0.001347523764707148,0.8721531629562378,0.48430413007736206,-0.10868903994560242,0.02401459962129593,0.8677900433540344,0.4783432185649872,-0.08048027753829956,0.040738314390182495,0.873528003692627,0.47044050693511963,-0.059763628989458084,0.05157465487718582,0.8788937330245972,0.4501391053199768,-0.04934650659561157,0.05205259844660759,0.8900731801986694,0.42453670501708984,-0.03815162181854248,0.051432520151138306,0.9031432867050171,0.39876553416252136,-0.037157632410526276,0.04415203630924225,0.9152354598045349,0.36498332023620605,-0.02695031464099884,0.039486177265644073,0.9297858476638794,0.329318106174469,-0.028025144711136818,0.030693212524056435,0.9433038234710693,0.2966311573982239,-0.027126599103212357,0.024032237008213997,0.9543042182922363,0.27426233887672424,-0.030051251873373985,0.01857312023639679,0.9610058069229126,0.2752702534198761,-0.0399574413895607,0.015973186120390892,0.960403323173523,0.2998895049095154,-0.05064712092280388,0.017745479941368103,0.9524632692337036,0.32920894026756287,-0.06207078695297241,0.01935512199997902,0.9420159459114075,0.36409616470336914,-0.07750784605741501,0.01960451528429985,0.9279236197471619,0.39273667335510254,-0.08990627527236938,0.019731996580958366,0.9150330424308777,0.42253556847572327,-0.10985232144594193,0.015521892346441746,0.8995305299758911,0.44974005222320557,-0.1371123194694519,0.005399038549512625,0.8825559616088867,0.46561184525489807,-0.1654672622680664,-0.00866536982357502,0.8693394660949707,0.47740593552589417,-0.18605536222457886,-0.020123422145843506,0.8585231900215149,0.48524826765060425,-0.19872134923934937,-0.028105106204748154,0.8510312438011169,0.48357900977134705,-0.20516033470630646,-0.03280521184206009,0.8502849340438843,0.4788612723350525,-0.20336967706680298,-0.03212353214621544,0.8534054160118103,0.4960027039051056,-0.20215512812137604,-0.03170660138130188,0.843865692615509,0.5175178050994873,-0.19374409317970276,-0.02571943588554859,0.8330528736114502,0.5264936089515686,-0.18899433314800262,-0.02218763530254364,0.8286093473434448,0.5273976922035217,-0.18820495903491974,-0.021646302193403244,0.828228235244751,0.5284970998764038,-0.18671728670597076,-0.020561598241329193,0.8278917670249939,0.5323388576507568,-0.1811574250459671,-0.016064800322055817,0.8267643451690674,0.5398608446121216,-0.18085190653800964,-0.015319153666496277,0.8219538331031799,0.5450225472450256,-0.18099844455718994,-0.015120051801204681,0.8185116648674011,0.5477386116981506,-0.18172264099121094,-0.015563939698040485,0.8165274858474731,0.5472669005393982,-0.18154555559158325,-0.015472947619855404,0.8168848156929016,0.5439469218254089,-0.1874263733625412,-0.02038552612066269,0.8176659345626831,0.5399613380432129,-0.1857234686613083,-0.019220486283302307,0.8207186460494995,0.5358619093894958,-0.18499591946601868,-0.01884281076490879,0.8235735893249512,0.5325629115104675,-0.18518976867198944,-0.019157234579324722,0.8256600499153137],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999999403953552,0.9999997019767761,0.9999998807907104,0.9999999403953552,0.9999998807907104,0.9999997615814209],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,3.6666667461395264],"values":[4.6430894684590385e-8,23.109195709228516,-0.0000026827760848391335,-0.000013422348274616525,23.109203338623047,5.761684178651194e-7],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.6273226737976074,0.1159149557352066,-0.1324109137058258,0.7586154341697693,-0.6247525811195374,0.11491311341524124,-0.1306483894586563,0.7611900568008423,-0.6176376342773438,0.11181042343378067,-0.12527652084827423,0.768328070640564,-0.607374906539917,0.09930851310491562,-0.12760168313980103,0.7777861952781677,-0.5968104600906372,0.0875677838921547,-0.13172659277915955,0.7866367101669312,-0.5880814790725708,0.08335849642753601,-0.13456441462039948,0.7931607961654663,-0.5821893215179443,0.09228683263063431,-0.1335267722606659,0.7966864705085754,-0.5782349705696106,0.10637086629867554,-0.13634102046489716,0.7973335385322571,-0.5754581093788147,0.12750700116157532,-0.13828033208847046,0.7959073781967163,-0.5697294473648071,0.14574317634105682,-0.1437947303056717,0.7959210872650146,-0.5603878498077393,0.15974055230617523,-0.14827515184879303,0.7990387678146362,-0.5476772785186768,0.16981768608093262,-0.15293848514556885,0.804873526096344,-0.533444881439209,0.17860913276672363,-0.15935267508029938,0.8112595677375793,-0.5133205056190491,0.17772075533866882,-0.17190784215927124,0.821806013584137,-0.4928548336029053,0.17663782835006714,-0.17977958917617798,0.8328100442886353,-0.47717559337615967,0.17538852989673615,-0.18391525745391846,0.8412594795227051,-0.4723845422267914,0.17846739292144775,-0.19092607498168945,0.8417538404464722,-0.4798450469970703,0.18723466992378235,-0.20302441716194153,0.8327502608299255,-0.4877903163433075,0.19514420628547668,-0.21536146104335785,0.8231639266014099,-0.5040642619132996,0.19819536805152893,-0.21269026398658752,0.8132654428482056,-0.5157793164253235,0.20421114563941956,-0.20138303935527802,0.8072883486747742,-0.48479411005973816,0.23380854725837708,-0.17766934633255005,0.8238579630851746,-0.35951024293899536,0.2444562315940857,-0.17239977419376373,0.8838958740234375,-0.1995779275894165,0.20383521914482117,-0.07875601947307587,0.9552053809165955,-0.15398043394088745,0.04204128682613373,-0.01224766205996275,0.9871031045913696,-0.12784257531166077,-0.06038603186607361,0.03138161823153496,0.9894569516181946,-0.1462581306695938,-0.016343502327799797,0.06581771373748779,0.9869191646575928,-0.19012844562530518,-0.0033159502781927586,0.13346806168556213,0.9726389050483704,-0.2624651789665222,0.01937359757721424,0.1483057290315628,0.9532796740531921,-0.3513002395629883,0.05849330872297287,0.12474700808525085,0.9260696172714233,-0.4311637282371521,0.09682390838861465,0.10703117400407791,0.8906555771827698,-0.47744834423065186,0.12411608546972275,0.09120775014162064,0.8650546073913574,-0.4787389636039734,0.1166573166847229,0.08860211819410324,0.865649938583374,-0.4147997796535492,0.055323127657175064,0.08872759342193604,0.9038849472999573,-0.2620420753955841,0.03788939118385315,0.012909158132970333,0.9642260074615479,-0.2685229778289795,0.048114560544490814,-0.0007085077231749892,0.962070643901825,-0.2561633288860321,0.06641650944948196,-0.014478784054517746,0.9642404317855835,-0.4047093689441681,0.07735317200422287,-0.02991095371544361,0.9106767773628235,-0.46647465229034424,0.10026516765356064,-0.056444644927978516,0.8770189881324768,-0.509041965007782,0.1338607519865036,-0.0657440572977066,0.8477236032485962,-0.5359156727790833,0.15671415627002716,-0.0737418383359909,0.8263154029846191,-0.555719256401062,0.16529041528701782,-0.07530709356069565,0.8112854361534119,-0.5676015019416809,0.1628590077161789,-0.07366101443767548,0.8036662936210632,-0.5807903409004211,0.1563418209552765,-0.06715402007102966,0.7960717678070068,-0.612971305847168,0.142584428191185,-0.050177767872810364,0.7755115032196045,-0.6326272487640381,0.14303775131702423,-0.04876800999045372,0.7595687508583069,-0.6370717883110046,0.1108008399605751,-0.040531836450099945,0.761721670627594,-0.6350433826446533,0.05855492502450943,-0.023517295718193054,0.7698948979377747,-0.6185984015464783,-0.024244854226708412,0.001089668832719326,0.785332441329956,-0.5541340708732605,-0.056487541645765305,-0.07120602577924728,0.8274505138397217,-0.47886013984680176,0.1026606634259224,-0.04170161113142967,0.8708701133728027,-0.41452446579933167,0.029072342440485954,-0.01725807413458824,0.9094099402427673,-0.39569032192230225,-0.029673289507627487,-0.00384422205388546,0.9178964495658875,-0.40352675318717957,-0.047984831035137177,-0.011941861361265182,0.9136307239532471,-0.4385874271392822,-0.034900788217782974,-0.017946574836969376,0.8978312611579895,-0.48581600189208984,-0.018269876018166542,-0.01310872845351696,0.8737718462944031,-0.5301948189735413,-0.0008934112265706062,-0.010817867703735828,0.8478063941001892,-0.548434853553772,-0.0007354044355452061,-0.0244939923286438,0.8358341455459595,-0.5615848302841187,-0.017794130370020866,-0.03492815047502518,0.8264901041984558,-0.5736399292945862,-0.03888676315546036,-0.045744623988866806,0.8169042468070984,-0.587833821773529,-0.05160744488239288,-0.059063225984573364,0.8051705360412598,-0.6016717553138733,-0.053666651248931885,-0.07458018511533737,0.793441116809845,-0.6185021996498108,-0.04186496511101723,-0.08226276189088821,0.7803429961204529,-0.637693464756012,-0.035040970891714096,-0.09301748871803284,0.7638500928878784,-0.6562584042549133,-0.033200062811374664,-0.10700847953557968,0.7461714744567871,-0.6877299547195435,-0.011029956862330437,-0.07373565435409546,0.7221280932426453,-0.706612229347229,-0.004731111694127321,-0.06347793340682983,0.7047321796417236,-0.7201711535453796,-0.012419559061527252,-0.05245053768157959,0.6916995644569397,-0.7280856966972351,-0.029467584565281868,-0.0573592483997345,0.6824461817741394,-0.7283986806869507,-0.036567676812410355,-0.07806851714849472,0.6797084212303162,-0.7197385430335999,-0.03981800004839897,-0.10038192570209503,0.6857947707176208,-0.7058460712432861,-0.03984348475933075,-0.11778280884027481,0.697367250919342,-0.6964192390441895,-0.03692634776234627,-0.11908245086669922,0.70672208070755,-0.6917356252670288,-0.028753748163580894,-0.10087546706199646,0.7144922614097595,-0.67958664894104,-0.009270912036299706,-0.08497024327516556,0.7285987734794617,-0.6565908193588257,0.0321536622941494,-0.07793344557285309,0.7495205998420715,-0.6434231400489807,0.05353235453367233,-0.06892397254705429,0.7605198621749878,-0.635341465473175,0.0864759162068367,-0.07109659910202026,0.7640735507011414,-0.6288334131240845,0.11949780583381653,-0.08686622977256775,0.7633761763572693,-0.630189061164856,0.14243900775909424,-0.100387804210186,0.7566341161727905,-0.6348373889923096,0.15349577367305756,-0.11732561886310577,0.7481011152267456,-0.623919665813446,0.17346002161502838,-0.15423479676246643,0.746222198009491,-0.6120002865791321,0.18708683550357819,-0.18687114119529724,0.7453411221504211,-0.6091228127479553,0.19068288803100586,-0.20767958462238312,0.7412682771682739,-0.6142879128456116,0.19414353370666504,-0.21816770732402802,0.7330495119094849,-0.6261376142501831,0.19396798312664032,-0.216664656996727,0.7234532237052917,-0.6408600211143494,0.18527503311634064,-0.20486107468605042,0.7162426710128784,-0.6562966704368591,0.16796283423900604,-0.19029098749160767,0.7105298042297363,-0.6628726124763489,0.14831233024597168,-0.18511426448822021,0.7101662755012512,-0.6677860617637634,0.13039687275886536,-0.18081384897232056,0.7101864814758301,-0.6721215844154358,0.11092400550842285,-0.1782025396823883,0.7100650072097778,-0.6670056581497192,0.10419675707817078,-0.18614128232002258,0.713861346244812,-0.6543983221054077,0.10517384111881256,-0.1975371241569519,0.7222744822502136,-0.6435256004333496,0.09912519156932831,-0.1996890753507614,0.7322385907173157,-0.6313042640686035,0.09314104914665222,-0.19879037141799927,0.7438158988952637,-0.6192957162857056,0.09456546604633331,-0.20284225046634674,0.7525856494903564,-0.6219585537910461,0.10180239379405975,-0.18218891322612762,0.7547258138656616,-0.6271346807479858,0.10569828003644943,-0.16055834293365479,0.7548185586929321,-0.6291764378547668,0.10646072030067444,-0.14213311672210693,0.7567042708396912,-0.6291772127151489,0.10140860080718994,-0.13077539205551147,0.7594406604766846,-0.6285964250564575,0.09916391223669052,-0.12390657514333725,0.7613673806190491,-0.6283999085426331,0.0940442830324173,-0.12348047643899918,0.7622478604316711,-0.6305614709854126,0.09412343800067902,-0.1210174709558487,0.7608467936515808,-0.6315901875495911,0.0946909636259079,-0.12060974538326263,0.7599873542785645,-0.6314799189567566,0.09557972848415375,-0.12161663174629211,0.7598072290420532,-0.6312195658683777,0.09882625192403793,-0.12426236271858215,0.7591798901557922,-0.6315584182739258,0.10969679802656174,-0.1219959631562233,0.7577714323997498,-0.6299852728843689,0.1122976690530777,-0.12637870013713837,0.7579817175865173,-0.628202497959137,0.11441341787576675,-0.12995144724845886,0.7585405707359314,-0.6273348331451416,0.11597868800163269,-0.1325307935476303,0.7585747241973877],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999997615814209,1,1.0000001192092896,0.9999998807907104,1,1.000000238418579],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.000007256121989485109,13.547398567199707,-0.0000021196165107539855,0.0000013577767958850018,13.54739761352539,2.681321689124161e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[-0.30788561701774597,0.01450191531330347,0.00469182850793004,0.9513012766838074,-0.30788561701774597,0.01450191531330347,0.004691828042268753,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998807907104,1.0000001192092896,1.000000238418579,0.9999998807907104,0.9999998807907104,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,3.6666667461395264],"values":[2.8339977264404297,7.754301071166992,-6.139598846435547,2.8339948654174805,7.75430154800415,-6.139604568481445],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[-0.33200064301490784,-0.09571805596351624,-0.11373622715473175,0.9314922094345093,-0.3320007026195526,-0.09571807086467743,-0.11373621970415115,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998807907104,1,0.9999999403953552,0.9999998807907104,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,3.6666667461395264],"values":[1.2572900232044049e-7,10.288591384887695,1.8230697662602324e-7,0.000006101094641053351,10.288595199584961,-0.000004500383511185646],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[-1.3518728358974386e-8,-7.632479537278414e-9,1.2558302842080593e-8,1,-1.3984389646282125e-8,-1.6698322724550962e-8,1.2907548807561398e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,3.6666667461395264],"values":[-8.930408768037523e-8,10.288590431213379,-6.966433261368365e-7,-0.00001421451543137664,10.288593292236328,-0.0000026980217171512777],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.00582654494792223,-0.0056729442439973354,-0.006692326627671719,0.9999445676803589,0.005354986991733313,-0.005371239967644215,-0.006319897715002298,0.9999513030052185,0.003880217205733061,-0.004436585586518049,-0.0053342548198997974,0.999968409538269,0.0029128119349479675,-0.003658259054645896,-0.0039800722151994705,0.999981164932251,0.0036920802667737007,-0.0037088370881974697,-0.002336619421839714,0.999983549118042,0.005433168727904558,-0.0047730072401463985,-0.0005553637747652829,0.9999736547470093,0.007401156239211559,-0.007098951376974583,0.001133686164394021,0.9999467730522156,0.00814124383032322,-0.010058733634650707,0.0030198730528354645,0.9999116659164429,0.009327133186161518,-0.013844050467014313,0.004929053597152233,0.9998485445976257,0.009279705584049225,-0.01863233931362629,0.0066782403737306595,0.9997611045837402,0.008445503190159798,-0.023787586018443108,0.007769657764583826,0.9996511340141296,0.0068725710734725,-0.02847905270755291,0.008419235236942768,0.999535322189331,0.005161238368600607,-0.03334593027830124,0.008474805392324924,0.9993945956230164,0.0033482657745480537,-0.036882877349853516,0.0082844914868474,0.9992796778678894,0.000914740317966789,-0.03871375322341919,0.00826943013817072,0.9992157220840454,-0.001626896089874208,-0.03843684494495392,0.00831187330186367,0.9992251396179199,-0.00395449623465538,-0.03729479759931564,0.008636708371341228,0.9992591738700867,-0.0058777970261871815,-0.03442100062966347,0.008797580376267433,0.9993513822555542,-0.00677662855014205,-0.03139468654990196,0.008696669712662697,0.9994462728500366,-0.007650363724678755,-0.030309703201055527,0.008333228528499603,0.9994765520095825,-0.008445105515420437,-0.032240841537714005,0.007415834814310074,0.9994169473648071,-0.010522427968680859,-0.03763351961970329,0.005645881872624159,0.9992202520370483,-0.01418662816286087,-0.04269062355160713,0.0020444386173039675,0.9989855289459229,-0.01959371194243431,-0.04262305796146393,-0.002398562151938677,0.9988962411880493,-0.023002732545137405,-0.03951212391257286,-0.006653683725744486,0.9989321231842041,-0.023943506181240082,-0.03502114489674568,-0.011660921387374401,0.999031662940979,-0.023940129205584526,-0.029559269547462463,-0.016443267464637756,0.9991409778594971,-0.023708883672952652,-0.023972338065505028,-0.01947823539376259,0.9992416501045227,-0.022305646911263466,-0.017470624297857285,-0.02079065702855587,0.9993823170661926,-0.016139378771185875,-0.009395348839461803,-0.017860373482108116,0.9996660947799683,-0.0043062991462647915,-0.0009749341406859457,-0.006288936827331781,0.9999704957008362,0.004321872256696224,0.007408616598695517,0.010120186023414135,0.9999119639396667,0.01187967974692583,0.013887779787182808,0.020969990640878677,0.9996130466461182,0.01830553449690342,0.01975373923778534,0.025576293468475342,0.9993100166320801,0.023380490019917488,0.025035042315721512,0.027340302243828773,0.9990391135215759,0.026815444231033325,0.029810259118676186,0.029541976749897003,0.9987590312957764,0.028546124696731567,0.03420298546552658,0.030037041753530502,0.9985554814338684,0.028380097821354866,0.038373444229364395,0.03229174762964249,0.9983382821083069,0.027189861983060837,0.04134799912571907,0.03523947671055794,0.9981529116630554,0.025360755622386932,0.04390726238489151,0.03714573755860329,0.9980226755142212,0.02335912548005581,0.0466398224234581,0.03421376273036003,0.9980523586273193,0.021251130849123,0.049531858414411545,0.028455859050154686,0.9981409311294556,0.01894867606461048,0.052101343870162964,0.023619677871465683,0.9981826543807983,0.016632821410894394,0.0545850433409214,0.01962403580546379,0.998177707195282,0.014400097541511059,0.05713828653097153,0.017505532130599022,0.9981089234352112,0.012463551014661789,0.058842990547418594,0.016041478142142296,0.9980605244636536,0.010566676035523415,0.058998577296733856,0.014285275712609291,0.9980999231338501,0.008721492253243923,0.05789332836866379,0.01219396572560072,0.9982101917266846,0.007002321071922779,0.05804506689310074,0.0095520643517375,0.9982436895370483,0.0055633666925132275,0.05382443964481354,0.005981331691145897,0.9985170364379883,0.004168176092207432,0.0464189276099205,0.004660364240407944,0.9989024996757507,0.0029901175294071436,0.033194057643413544,0.0021541693713515997,0.9994421005249023,0.0019230640027672052,0.012829496525228024,0.0006008510245010257,0.9999157190322876,0.0009717592038214207,-0.00968384649604559,0.0012705877888947725,0.9999518394470215,0.0005288405227474868,-0.028821349143981934,0.011000332422554493,0.999523937702179,0.0006556990556418896,-0.047337472438812256,0.01833091489970684,0.998710572719574,0.0007273710216395557,-0.06193980574607849,0.02133139595389366,0.9978516101837158,0.0007312368834391236,-0.0705343633890152,0.025170113891363144,0.9971914887428284,0.00014427558926399797,-0.0671514943242073,0.02031821757555008,0.9975358843803406,-0.00023028894793242216,-0.06463100016117096,0.018098600208759308,0.9977450966835022,-0.000615142984315753,-0.0600459985435009,0.014089028351008892,0.9980959892272949,-0.0009800908155739307,-0.05422049015760422,0.009252570569515228,0.9984856843948364,-0.0012667188420891762,-0.04874863848090172,0.0042196353897452354,0.9988014101982117,-0.0014427299611270428,-0.04418756440281868,0.00018173348507843912,0.9990221858024597,-0.0014165377942845225,-0.040509726852178574,-0.0018348176963627338,0.9991764426231384,-0.0012387734604999423,-0.037658944725990295,-0.0025518040638417006,0.9992866516113281,-0.0009075985290110111,-0.0355023629963398,-0.0017073893686756492,0.9993677735328674,-0.000029458240533131175,-0.033246953040361404,0.00043612925219349563,0.9994470477104187,0.0023192325606942177,-0.02927394211292267,0.0025142075028270483,0.9995655417442322,0.004935420583933592,-0.02379211038351059,0.00267102406360209,0.999701201915741,0.005325271748006344,-0.01872190460562706,0.0022751037031412125,0.9998080134391785,0.0049952189438045025,-0.01459228340536356,0.0006939750746823847,0.999880850315094,0.004780002404004335,-0.00885021686553955,-0.001082948874682188,0.9999488592147827,0.00453018257394433,-0.003846016712486744,-0.0034610165748745203,0.9999763369560242,0.004110944923013449,0.002699595410376787,-0.006123281084001064,0.9999691247940063,0.00364678050391376,0.010920546017587185,-0.008540285751223564,0.9998972415924072,0.0032836259342730045,0.019643006846308708,-0.009538532234728336,0.9997561573982239,0.00314230564981699,0.021835224702954292,-0.009260623715817928,0.9997137784957886,0.0010180213721469045,0.021727873012423515,-0.008393715135753155,0.9997282028198242,-0.005386712029576302,0.020779039710760117,-0.007234733086079359,0.9997434020042419,-0.009328467771410942,0.019466286525130272,-0.005991249345242977,0.9997490048408508,-0.011374752968549728,0.018155450001358986,-0.005161514971405268,0.9997571706771851,-0.012468656525015831,0.01726369746029377,-0.005373906344175339,0.9997588396072388,-0.012834476307034492,0.016301503404974937,-0.005839229095727205,0.9997677206993103,-0.012632138095796108,0.015215128660202026,-0.00645650876685977,0.9997836351394653,-0.01209114771336317,0.013926873914897442,-0.007471760734915733,0.9998019933700562,-0.011392099782824516,0.01258535124361515,-0.010440848767757416,0.9998013973236084,-0.010634769685566425,0.01072817761451006,-0.011995972134172916,0.999813973903656,-0.00968712754547596,0.00915661919862032,-0.012444600462913513,0.9998337626457214,-0.008608434349298477,0.007305363658815622,-0.012657084502279758,0.999856173992157,-0.007258873898535967,0.005489549599587917,-0.012682867236435413,0.9998781681060791,-0.005491940770298243,0.004132455680519342,-0.012524273246526718,0.9998979568481445,-0.0027545474004000425,0.0027779589872807264,-0.012295151129364967,0.9999167323112488,0.00045450672041624784,0.0013208836317062378,-0.012145712040364742,0.9999252557754517,0.0024167243391275406,-0.00026057157083414495,-0.012173833325505257,0.9999229311943054,0.0035504631232470274,-0.0019724732264876366,-0.012127355672419071,0.9999182224273682,0.004195299930870533,-0.0036420775577425957,-0.012085137888789177,0.9999115467071533,0.004537683445960283,-0.005204112268984318,-0.011670933105051517,0.9999080300331116,0.004771483596414328,-0.006309168878942728,-0.010847222991287708,0.9999098777770996,0.005092605482786894,-0.006326906383037567,-0.009796101599931717,0.999919056892395,0.005471887998282909,-0.006223618518561125,-0.008586028590798378,0.9999288320541382,0.005759319756180048,-0.006048316601663828,-0.006915615405887365,0.9999411702156067,0.005975879728794098,-0.00588435772806406,-0.006001746281981468,0.9999468326568604,0.006131946574896574,-0.005759859457612038,-0.005866538733243942,0.9999474287033081,0.006234530359506607,-0.005647051613777876,-0.005873532500118017,0.9999473690986633,0.006278494838625193,-0.005626903846859932,-0.005961926653981209,0.9999467134475708,0.0062321508303284645,-0.005657225847244263,-0.006167653016746044,0.9999455809593201,0.006103524938225746,-0.005679268855601549,-0.006352577358484268,0.9999450445175171,0.005953541956841946,-0.005696355365216732,-0.006545669864863157,0.9999446272850037,0.005827122833579779,-0.005709003657102585,-0.0066860271617770195,0.9999443888664246],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999999403953552,0.9999999403953552,0.9999998807907104,1,0.9999998807907104,0.9999997615814209],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,3.6666667461395264],"values":[4.36639928817749,7.562896728515625,-0.17679953575134277,4.366410255432129,7.562903881072998,-0.17680178582668304],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.6769972443580627,0.4557754695415497,-0.4862802028656006,0.3122098743915558,0.6752280592918396,0.4561983346939087,-0.48738494515419006,0.31369748711586,0.6707746386528015,0.4555429518222809,-0.49191445112228394,0.3171153962612152,0.6679614186286926,0.45428189635276794,-0.49517661333084106,0.3197743594646454,0.6697814464569092,0.45393553376197815,-0.4926040470600128,0.32043197751045227,0.6750361323356628,0.4531550705432892,-0.4851168394088745,0.32192909717559814,0.6831578612327576,0.450156033039093,-0.47280755639076233,0.32727351784706116,0.6894806027412415,0.44557371735572815,-0.45663905143737793,0.3428722620010376,0.6920132040977478,0.4487990438938141,-0.42479971051216125,0.37315189838409424,0.688362181186676,0.45296746492385864,-0.3794616758823395,0.4206980764865875,0.6724917888641357,0.465628057718277,-0.3276016116142273,0.4728875458240509,0.644309937953949,0.48302486538887024,-0.2767541706562042,0.5243651270866394,0.6101078987121582,0.5013219118118286,-0.23626965284347534,0.5662344098091125,0.5643408298492432,0.5300620794296265,-0.21352873742580414,0.5957844853401184,0.5082033276557922,0.5658079385757446,-0.21103525161743164,0.6140479445457458,0.4520026743412018,0.5960158109664917,-0.21134355664253235,0.6291205286979675,0.4143914580345154,0.6136406064033508,-0.21503859758377075,0.6367757320404053,0.42320945858955383,0.6085631251335144,-0.22021274268627167,0.6340749263763428,0.4565923511981964,0.5898250341415405,-0.22482720017433167,0.6269629001617432,0.49514368176460266,0.563626766204834,-0.2377307116985321,0.6169617772102356,0.5294377207756042,0.540010929107666,-0.26474907994270325,0.5983242392539978,0.5743806958198547,0.5032347440719604,-0.30549997091293335,0.5687807202339172,0.617053210735321,0.4614020884037018,-0.3493505120277405,0.5332052111625671,0.6316816806793213,0.4358108639717102,-0.38870102167129517,0.5098614692687988,0.6287010312080383,0.4309287369251251,-0.4179159700870514,0.49434977769851685,0.6254976391792297,0.43504637479782104,-0.4461279809474945,0.4695286750793457,0.6213355660438538,0.4420139789581299,-0.4729286730289459,0.4414795935153961,0.6169083118438721,0.446342796087265,-0.4975223243236542,0.4155404269695282,0.6155505776405334,0.44750308990478516,-0.5164281129837036,0.3926072418689728,0.6137452721595764,0.46624746918678284,-0.5217916369438171,0.36560022830963135,0.6060850024223328,0.5143724083900452,-0.500985860824585,0.3421916365623474,0.5815369486808777,0.5677086710929871,-0.47499364614486694,0.3374949097633362,0.5625810623168945,0.6085392236709595,-0.4520186185836792,0.3299422562122345,0.5452069044113159,0.6416908502578735,-0.43715471029281616,0.3160352110862732,0.5234130620956421,0.6736350059509277,-0.4317220151424408,0.293037474155426,0.49745866656303406,0.7031186819076538,-0.4301803708076477,0.2703774869441986,0.4670320749282837,0.7303438186645508,-0.4302166700363159,0.25177904963493347,0.4341468811035156,0.7534467577934265,-0.43259868025779724,0.2381027489900589,0.4032151401042938,0.7722282409667969,-0.4342558979988098,0.22913527488708496,0.3733404874801636,0.7877197265625,-0.4375535547733307,0.2205934375524521,0.3515465557575226,0.7971722483634949,-0.44635725021362305,0.20419766008853912,0.33688369393348694,0.8014001250267029,-0.4597395956516266,0.18140219151973724,0.3208853602409363,0.8053102493286133,-0.4713209569454193,0.16237156093120575,0.30615705251693726,0.807641863822937,-0.48233774304389954,0.14605756103992462,0.2898235321044922,0.8102957606315613,-0.4917110204696655,0.13282868266105652,0.2757653295993805,0.8126367926597595,-0.49825718998908997,0.12375279515981674,0.27126696705818176,0.810776948928833,-0.5041947364807129,0.12183067202568054,0.270542174577713,0.8084750175476074,-0.5073127746582031,0.1257333755493164,0.2797092795372009,0.8002662062644958,-0.5144472718238831,0.12915383279323578,0.31867602467536926,0.7759782671928406,-0.5254698991775513,0.14207324385643005,0.4064892530441284,0.7117902040481567,-0.5503386855125427,0.1588982194662094,0.5177189111709595,0.6122421622276306,-0.5638391375541687,0.19802072644233704,0.6057642102241516,0.5269207954406738,-0.5306321978569031,0.2717236280441284,0.6760531067848206,0.4442841410636902,-0.47271043062210083,0.3494405746459961,0.7136368751525879,0.3882807791233063,-0.39095401763916016,0.43256843090057373,0.7412067651748657,0.3331497311592102,-0.30587005615234375,0.49605175852775574,0.7534306049346924,0.2905519902706146,-0.2403465211391449,0.5386608242988586,0.7545471787452698,0.27019208669662476,-0.20897448062896729,0.5603432059288025,0.7504657506942749,0.2702375054359436,-0.20506641268730164,0.5672042369842529,0.7471175193786621,0.2761135995388031,-0.2111606001853943,0.5665579438209534,0.7425538301467896,0.2844705879688263,-0.22061967849731445,0.5648161768913269,0.7350746393203735,0.29751500487327576,-0.2313494086265564,0.563584566116333,0.7197094559669495,0.322428435087204,-0.24037013947963715,0.5659332275390625,0.6951179504394531,0.36105233430862427,-0.2481635957956314,0.5699711441993713,0.6613513827323914,0.41057243943214417,-0.25329795479774475,0.5743560194969177,0.62214195728302,0.46264657378196716,-0.2580035626888275,0.5764822363853455,0.5780397057533264,0.5165975689888,-0.26510635018348694,0.5733374357223511,0.527841329574585,0.5725623369216919,-0.2730717957019806,0.5647900104522705,0.4700300991535187,0.6334267854690552,-0.28221389651298523,0.5460746884346008,0.4082396328449249,0.6900728940963745,-0.29215967655181885,0.5213277339935303,0.34559616446495056,0.7294595837593079,-0.2964690625667572,0.5104489326477051,0.2879619300365448,0.7547807693481445,-0.2904806435108185,0.5128399729728699,0.23448488116264343,0.7775615453720093,-0.2809221148490906,0.511368453502655,0.21506717801094055,0.7903558611869812,-0.2799110412597656,0.5007330179214478,0.22036825120449066,0.800781786441803,-0.28758472204208374,0.47695016860961914,0.24597567319869995,0.8067421913146973,-0.30481261014938354,0.4424390494823456,0.29944396018981934,0.7981733083724976,-0.338437557220459,0.39838773012161255,0.3648087978363037,0.7653298377990723,-0.3762620687484741,0.37364643812179565,0.4031740128993988,0.7334445714950562,-0.41362735629081726,0.3583604395389557,0.407646507024765,0.7106614112854004,-0.45819178223609924,0.3447391390800476,0.40646567940711975,0.6976760029792786,-0.49507617950439453,0.3208325207233429,0.40416139364242554,0.6910068988800049,-0.5260526537895203,0.28710925579071045,0.40504416823387146,0.6850913763046265,-0.5504196286201477,0.2522447407245636,0.4071129262447357,0.6809729933738708,-0.5676726698875427,0.21973314881324768,0.41335582733154297,0.6768873333930969,-0.5719851851463318,0.2092689573764801,0.42183470726013184,0.6733430027961731,-0.5666090250015259,0.21821779012680054,0.43750256299972534,0.6648207902908325,-0.5592696666717529,0.23199637234210968,0.455771267414093,0.6534353494644165,-0.5506749153137207,0.2491026371717453,0.4832969903945923,0.6320400238037109,-0.5451775193214417,0.2640663683414459,0.5157454013824463,0.6039159297943115,-0.5405081510543823,0.2777467370033264,0.5481206178665161,0.5737835168838501,-0.5359761714935303,0.2882115840911865,0.5762302279472351,0.5470182299613953,-0.5320531129837036,0.29265904426574707,0.6043243408203125,0.5215300917625427,-0.5299707055091858,0.2862332761287689,0.6291849613189697,0.4983753561973572,-0.5273122191429138,0.2787294089794159,0.645011305809021,0.48288998007774353,-0.5212120413780212,0.28127506375312805,0.6573334336280823,0.4702039062976837,-0.5140119791030884,0.28742432594299316,0.6670132875442505,0.4597232937812805,-0.5065357089042664,0.2952445447444916,0.6746780276298523,0.4511781334877014,-0.499853253364563,0.302315354347229,0.679818332195282,0.44584134221076965,-0.49466121196746826,0.30721792578697205,0.6806541085243225,0.44619348645210266,-0.49261221289634705,0.30814701318740845,0.6801823973655701,0.4488281011581421,-0.49019694328308105,0.30921220779418945,0.6784639954566956,0.45293474197387695,-0.48716118931770325,0.31178635358810425,0.6774123311042786,0.4555324912071228,-0.48559731245040894,0.3127266466617584,0.6771519184112549,0.4565749168395996,-0.48521706461906433,0.3123604953289032,0.676910400390625,0.4573981761932373,-0.48501190543174744,0.3119979202747345,0.676764965057373,0.457908034324646,-0.4848681092262268,0.3117888867855072,0.676806628704071,0.45761916041374207,-0.48513510823249817,0.31170734763145447,0.6768180131912231,0.4570872187614441,-0.4855533838272095,0.3118116855621338,0.6767944693565369,0.4565127193927765,-0.4860196113586426,0.3119781017303467,0.6767663955688477,0.45603975653648376,-0.4864066541194916,0.3121275007724762],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998807907104,0.9999997615814209,0.9999999403953552,0.9999996423721313,0.9999995827674866,0.9999997019767761],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.0000013105150173942093,17.630788803100586,0.000004139733391639311,0.0000034821696317521855,17.630786895751953,0.000017836679035099223],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.03342096507549286,-0.19026678800582886,0.186282679438591,0.963317334651947,-0.03156771510839462,-0.19002534449100494,0.18450139462947845,0.9637702703475952,-0.012271196581423283,-0.1760803759098053,0.18037627637386322,0.967630922794342,0.02212049998342991,-0.1467941552400589,0.17598950862884521,0.9731340408325195,0.06358688324689865,-0.10316572338342667,0.1747402846813202,0.9771281480789185,0.10185356438159943,-0.0475110225379467,0.1841297745704651,0.9764552116394043,0.1263841688632965,0.014910689555108547,0.21232251822948456,0.9688776135444641,0.12391114979982376,0.07587658613920212,0.2840160131454468,0.9477466344833374,0.0751032903790474,0.11408478021621704,0.3957110345363617,0.9081613421440125,-0.0005055320798419416,0.11147865653038025,0.5028091073036194,0.8571786880493164,-0.07327603548765182,0.041130855679512024,0.5904592275619507,0.8026809692382812,-0.12450163066387177,-0.07646577060222626,0.6549069285392761,0.7414507865905762,-0.16033968329429626,-0.19070686399936676,0.6900454759597778,0.6795287728309631,-0.17388182878494263,-0.2909739017486572,0.7015174031257629,0.6268753409385681,-0.1657618284225464,-0.38270115852355957,0.6965895891189575,0.5838028788566589,-0.09257014095783234,-0.46561822295188904,0.670049786567688,0.5706695914268494,-0.07669924944639206,-0.5152723789215088,0.6017200946807861,0.6054292321205139,-0.1218481957912445,-0.4580751657485962,0.5014999508857727,0.7237526774406433,-0.13887500762939453,-0.3274170160293579,0.36416298151016235,0.8607538342475891,-0.11495984345674515,-0.1793227344751358,0.22935879230499268,0.9497484564781189,-0.063278429210186,-0.08427087962627411,0.13010437786579132,0.985883891582489,-0.020361771807074547,-0.018745800480246544,0.04657435417175293,0.9985313415527344,0.023860707879066467,0.03364669159054756,-0.0507626011967659,0.9978585839271545,0.06774337589740753,0.07212706655263901,-0.12792576849460602,0.9868351221084595,0.1102486327290535,0.10707233846187592,-0.18547014892101288,0.9705573320388794,0.15280285477638245,0.1416502445936203,-0.2338014543056488,0.9496964812278748,0.18953625857830048,0.1645062118768692,-0.2577419877052307,0.933050274848938,0.2163582742214203,0.16872850060462952,-0.26201167702674866,0.9252403378486633,0.220438152551651,0.15130053460597992,-0.21438348293304443,0.9394439458847046,0.21581098437309265,0.11946450173854828,-0.06442021578550339,0.9669560194015503,0.22268134355545044,0.08822161704301834,0.08485075831413269,0.9671764969825745,0.2440240979194641,0.06354027986526489,0.21534007787704468,0.9434211850166321,0.24929609894752502,0.051804494112730026,0.28330904245376587,0.9246100783348083,0.2479984611272812,0.05370186269283295,0.2806756794452667,0.925653338432312,0.2524586617946625,0.05711773410439491,0.2400716245174408,0.9356109499931335,0.2639070749282837,0.07101766020059586,0.1463337242603302,0.9507344961166382,0.2529945969581604,0.07242727279663086,0.0823899656534195,0.9612283706665039,0.2297162115573883,0.062251199036836624,0.053788941353559494,0.9697741866111755,0.20917867124080658,0.045007314532995224,0.049353115260601044,0.975593626499176,0.1929980218410492,0.018604766577482224,0.06576255708932877,0.9788160920143127,0.20656193792819977,0.002429252490401268,-0.04125329852104187,0.9775604605674744,0.21312817931175232,-0.027327075600624084,-0.11467745900154114,0.9698859453201294,0.22239653766155243,-0.05758664757013321,-0.19189506769180298,0.9541487693786621,0.2235734462738037,-0.07910960912704468,-0.22617900371551514,0.9447749257087708,0.22129365801811218,-0.09246165305376053,-0.21754640340805054,0.9461255669593811,0.22139044106006622,-0.10447559505701065,-0.18956880271434784,0.9508600234985352,0.21088314056396484,-0.11744676530361176,-0.13993066549301147,0.9602885246276855,0.19010396301746368,-0.12032106518745422,-0.06056440621614456,0.9724789261817932,0.18810512125492096,-0.13091729581356049,-0.007736849132925272,0.9733536243438721,0.19180552661418915,-0.1624876856803894,0.017424898222088814,0.967731773853302,0.18198364973068237,-0.22679591178894043,0.01634790562093258,0.9566495418548584,0.14727772772312164,-0.2850981652736664,0.041721098124980927,0.946196436882019,0.1719137728214264,-0.3286588490009308,0.0361752063035965,0.9279657602310181,0.21929433941841125,-0.29659411311149597,0.10130902379751205,0.9239472150802612,0.2217220813035965,-0.2480458915233612,0.20908500254154205,0.9195629358291626,0.20038940012454987,-0.19352176785469055,0.3017920255661011,0.9117648005485535,0.22362123429775238,-0.10627073794603348,0.39265841245651245,0.885731041431427,0.23826411366462708,-0.08049824088811874,0.4425759017467499,0.8607420325279236,0.2520764172077179,-0.0760507583618164,0.4660024642944336,0.8446984887123108,0.25992467999458313,-0.0800868421792984,0.4766676723957062,0.835950493812561,0.25531357526779175,-0.09008577466011047,0.4844017028808594,0.8318981528282166,0.23142175376415253,-0.10329286754131317,0.4983202815055847,0.8291269540786743,0.17937587201595306,-0.12026108056306839,0.5191718339920044,0.8269354701042175,0.10323365777730942,-0.14257316291332245,0.5367817282676697,0.8251551985740662,0.01641250029206276,-0.16890451312065125,0.5448570251464844,0.8211776614189148,-0.06652814149856567,-0.19799843430519104,0.5421738624572754,0.8138907551765442,-0.14241239428520203,-0.24193665385246277,0.5212647914886475,0.8058960437774658,-0.21490880846977234,-0.2996335029602051,0.49847832322120667,0.7845720648765564,-0.26218533515930176,-0.36904269456863403,0.4806361198425293,0.7510361671447754,-0.2802290916442871,-0.4370698630809784,0.4762863516807556,0.7096428275108337,-0.2502446174621582,-0.49034634232521057,0.4928486943244934,0.6738236546516418,-0.18161003291606903,-0.5374391078948975,0.5246484279632568,0.6347607970237732,-0.07962583005428314,-0.5575932860374451,0.5514363050460815,0.6153596043586731,-0.01614988036453724,-0.5497522354125977,0.5584281086921692,0.6210231781005859,0.015379369258880615,-0.5191563367843628,0.5645171999931335,0.6415300369262695,0.0325939916074276,-0.46312177181243896,0.5436143279075623,0.6992419958114624,0.0183882974088192,-0.3558158278465271,0.48035556077957153,0.8014459013938904,0.009743579663336277,-0.22113114595413208,0.410841166973114,0.8844295740127563,0.03016965463757515,-0.10833500325679779,0.32667967677116394,0.938420832157135,0.07453811913728714,-0.03536735847592354,0.22153134644031525,0.9716569185256958,0.11535865813493729,0.014458012767136097,0.0896926000714302,0.9891605377197266,0.152541846036911,0.0475861020386219,-0.06093433499336243,0.9852682948112488,0.17706575989723206,0.06203096732497215,-0.1557229906320572,0.9698196649551392,0.18273983895778656,0.06692199409008026,-0.19834314286708832,0.9606183767318726,0.17812559008598328,0.0691976547241211,-0.21319442987442017,0.9581394195556641,0.16564826667308807,0.06992986053228378,-0.20596244931221008,0.9618991017341614,0.1472395658493042,0.06814416497945786,-0.18509645760059357,0.9692348837852478,0.1255677342414856,0.06317037343978882,-0.16043005883693695,0.9769874811172485,0.09612875431776047,0.05068446695804596,-0.12588931620121002,0.9860741496086121,0.06510042399168015,0.03269391134381294,-0.08447089046239853,0.9937593936920166,0.03724534437060356,0.008074628189206123,-0.03933263570070267,0.9984991550445557,0.014686823822557926,-0.025404078885912895,0.007093366235494614,0.9995442032814026,-0.004498915746808052,-0.06718679517507553,0.059664782136678696,0.9959446787834167,-0.01870783232152462,-0.09893669188022614,0.11068418622016907,0.988741934299469,-0.025494376197457314,-0.12376397848129272,0.14157964289188385,0.9818287491798401,-0.028329025954008102,-0.14338025450706482,0.16383500397205353,0.9756011962890625,-0.029982570558786392,-0.15786497294902802,0.17585526406764984,0.9712129235267639,-0.031184745952486992,-0.16899284720420837,0.18270549178123474,0.9680328965187073,-0.03214879706501961,-0.1778876632452011,0.18592846393585205,0.9657914042472839,-0.03276009112596512,-0.18580599129199982,0.1881093531847,0.9638556838035583,-0.03311295807361603,-0.19304893910884857,0.18992038071155548,0.9620633721351624,-0.033293478190898895,-0.1961665004491806,0.19040369987487793,0.9613307118415833,-0.03335524722933769,-0.19597400724887848,0.18933948874473572,0.9615779519081116,-0.0333779938519001,-0.1938813477754593,0.1880706548690796,0.9622501730918884,-0.03334404155611992,-0.19150762259960175,0.1868082731962204,0.9629722833633423,-0.03331449627876282,-0.1903526484966278,0.18620902299880981,0.9633183479309082,-0.03331537917256355,-0.19033293426036835,0.186196967959404,0.9633245468139648,-0.033315327018499374,-0.19033290445804596,0.18619713187217712,0.9633245468139648,-0.03331512585282326,-0.19033296406269073,0.186196967959404,0.9633245468139648,-0.03331531584262848,-0.19033296406269073,0.186196967959404,0.9633245468139648],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998807907104,1,0.9999998807907104,1.000000238418579,1.000000238418579,1],"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.0000016853808801897685,17.827802658081055,0.000009396909263159614,-0.0000022316960439638933,17.82776641845703,-0.000009064871846931055],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.03974367678165436,-0.15917351841926575,0.27428779006004333,0.9475497007369995,-0.03904819115996361,-0.1557043194770813,0.2794036865234375,0.9466599225997925,-0.03399323299527168,-0.14528332650661469,0.29504963755607605,0.9437599778175354,-0.0277420524507761,-0.12905164062976837,0.32561194896698,0.9362440705299377,-0.023023786023259163,-0.1080060601234436,0.37202632427215576,0.9216295480728149,-0.021554378792643547,-0.08461131900548935,0.42665985226631165,0.900187611579895,-0.024293197318911552,-0.06320558488368988,0.47946056723594666,0.874947190284729,-0.04648890718817711,-0.05853261053562164,0.5250250697135925,0.8477979898452759,-0.08175566792488098,-0.06529182940721512,0.5759201645851135,0.8107829689979553,-0.12990404665470123,-0.08901898562908173,0.6209871768951416,0.7678382396697998,-0.15856775641441345,-0.11264372617006302,0.6412281394004822,0.7422898411750793,-0.15874452888965607,-0.12346426397562027,0.6434476971626282,0.7386012673377991,-0.14654450118541718,-0.12164726853370667,0.6456864476203918,0.7394698858261108,-0.13293248414993286,-0.107051782310009,0.6620403528213501,0.7297750115394592,-0.11805476993322372,-0.08488050848245621,0.6860731840133667,0.7128548622131348,-0.10332714766263962,-0.06107001006603241,0.7135067582130432,0.6902914047241211,-0.09326007217168808,-0.048760801553726196,0.7212120890617371,0.6846737265586853,-0.10131967067718506,-0.06262364983558655,0.7107450366020203,0.6932922601699829,-0.12014371901750565,-0.09175977855920792,0.6833772659301758,0.7142416834831238,-0.13911211490631104,-0.12119819968938828,0.6407104730606079,0.7452844977378845,-0.14185857772827148,-0.14353087544441223,0.5394642353057861,0.8174676895141602,-0.12573735415935516,-0.1573350578546524,0.35433393716812134,0.9131721258163452,-0.108049176633358,-0.1622825711965561,0.2004534900188446,0.9601083993911743,-0.09980185329914093,-0.16736596822738647,0.1346741020679474,0.971540629863739,-0.09219080209732056,-0.17758165299892426,0.12213277816772461,0.9721364378929138,-0.0790097787976265,-0.18883965909481049,0.10678090900182724,0.9729824662208557,-0.06547044217586517,-0.19481953978538513,0.09058371931314468,0.974450409412384,-0.05481712892651558,-0.19652129709720612,0.0737423524260521,0.9761846661567688,-0.05227798596024513,-0.20420126616954803,0.11045657098293304,0.9712714552879333,-0.04191543161869049,-0.192641019821167,0.04048386588692665,0.9795374870300293,-0.036478884518146515,-0.17396171391010284,0.018573487177491188,0.9839012622833252,-0.03284657746553421,-0.15809333324432373,0.011078058741986752,0.9868155121803284,-0.02927839383482933,-0.16230611503124237,0.003834672039374709,0.9862985610961914,-0.026257406920194626,-0.1716374158859253,-0.002317579695954919,0.9848074913024902,-0.02508515492081642,-0.1693866103887558,-0.003222526516765356,0.9852251410484314,-0.040422387421131134,-0.17783288657665253,0.12587380409240723,0.9751396775245667,-0.04661618918180466,-0.18235290050506592,0.20325927436351776,0.960864245891571,-0.045018602162599564,-0.18493671715259552,0.20018425583839417,0.9610921144485474,-0.041135381907224655,-0.18297423422336578,0.13708454370498657,0.9726438522338867,-0.0370272733271122,-0.17147640883922577,0.03257080912590027,0.9839532375335693,-0.07079042494297028,-0.17334577441215515,0.1829170137643814,0.9651327729225159,-0.09720905870199203,-0.18720991909503937,0.21910472214221954,0.9526258707046509,-0.14012564718723297,-0.1997712254524231,0.3001005947589874,0.9221691489219666,-0.1842898279428482,-0.21838213503360748,0.3820153772830963,0.8788690567016602,-0.22649219632148743,-0.2440076470375061,0.47522976994514465,0.8144435286521912,-0.26182329654693604,-0.27846524119377136,0.5763721466064453,0.7222886085510254,-0.2963564395904541,-0.3021518886089325,0.6603344082832336,0.6203511953353882,-0.32034894824028015,-0.33620837330818176,0.6869169473648071,0.5590040683746338,-0.33313894271850586,-0.3755539655685425,0.6952263116836548,0.5144299268722534,-0.3475782573223114,-0.39291414618492126,0.7013548016548157,0.48260679841041565,-0.3415733575820923,-0.39510053396224976,0.68007493019104,0.5145107507705688,-0.3423672914505005,-0.3616761565208435,0.6200600862503052,0.6062182784080505,-0.3081599473953247,-0.34577783942222595,0.6659037470817566,0.584848165512085,-0.28154700994491577,-0.33214980363845825,0.7016679644584656,0.5639768838882446,-0.2896587550640106,-0.290825217962265,0.48053139448165894,0.774989128112793,-0.21291285753250122,-0.23569758236408234,0.2786654233932495,0.906344473361969,-0.1577182561159134,-0.21962398290634155,0.1966554969549179,0.9424525499343872,-0.11299062520265579,-0.2115219682455063,0.15938986837863922,0.9576463103294373,-0.0712006613612175,-0.2052765190601349,0.13934673368930817,0.9661130905151367,-0.034805070608854294,-0.1997128576040268,0.13019995391368866,0.970541775226593,-0.009030628018081188,-0.19431759417057037,0.1322648972272873,0.9719389081001282,0.0010047107934951782,-0.18904735147953033,0.1456727832555771,0.971102237701416,-0.002669571666046977,-0.17792218923568726,0.190876767039299,0.9653510451316833,-0.01300576701760292,-0.15966586768627167,0.2715223729610443,0.9490064978599548,-0.025638936087489128,-0.14248886704444885,0.3623708486557007,0.9207208752632141,-0.03310397267341614,-0.1350819617509842,0.44034191966056824,0.8869926929473877,-0.03064214438199997,-0.14106950163841248,0.49924391508102417,0.8543512225151062,-0.03640909120440483,-0.13001437485218048,0.5414320826530457,0.8298324942588806,-0.042006731033325195,-0.11947744339704514,0.5642272233963013,0.815848171710968,-0.045669618993997574,-0.11219489574432373,0.5804314017295837,0.805249035358429,-0.049972668290138245,-0.10438110679388046,0.5943084955215454,0.7958672642707825,-0.05542680621147156,-0.09529842436313629,0.6050730347633362,0.7885003089904785,-0.06853816658258438,-0.07650082558393478,0.6090021729469299,0.7864900231361389,-0.06290148943662643,-0.08148407191038132,0.5951310992240906,0.7970086336135864,-0.06803736090660095,-0.07987459748983383,0.573331356048584,0.812577486038208,-0.06574937701225281,-0.09411154687404633,0.5573118329048157,0.8223281502723694,-0.06782466918230057,-0.11135714501142502,0.4970696270465851,0.8578584790229797,-0.061989668756723404,-0.13588950037956238,0.35291752219200134,0.9236561059951782,-0.043594907969236374,-0.15141414105892181,0.1876436024904251,0.969516932964325,-0.0028014301788061857,-0.15962785482406616,0.04852082207798958,0.9859801530838013,0.017516784369945526,-0.16380566358566284,0.002488445956259966,0.9863339066505432,0.019322246313095093,-0.16517697274684906,0.005803159903734922,0.9860575795173645,0.020140191540122032,-0.16633856296539307,0.010730195790529251,0.9858046174049377,0.02002212405204773,-0.16682016849517822,0.01689443364739418,0.9856392741203308,0.020589986816048622,-0.16642265021800995,0.022385865449905396,0.9855853319168091,0.021973861381411552,-0.16560669243335724,0.02727353945374489,0.9855697751045227,0.02317105606198311,-0.1644541174173355,0.032149579375982285,0.9855883717536926,0.023202313110232353,-0.16306233406066895,0.03760463744401932,0.9856258034706116,0.019533520564436913,-0.16102007031440735,0.04393220692873001,0.9857794046401978,0.013739357702434063,-0.15908803045749664,0.05197250470519066,0.9857997298240662,0.006615995429456234,-0.15777792036533356,0.06251545250415802,0.9854715466499329,-0.0026684883050620556,-0.15754954516887665,0.07951223105192184,0.9843012094497681,-0.020241515710949898,-0.15801137685775757,0.134367898106575,0.9780429601669312,-0.03387143090367317,-0.15975289046764374,0.18861672282218933,0.9683777689933777,-0.04064541682600975,-0.16053561866283417,0.24213676154613495,0.9560052752494812,-0.045136675238609314,-0.1610564887523651,0.28979218006134033,0.9423608779907227,-0.047324925661087036,-0.1619110405445099,0.32547101378440857,0.9303836822509766,-0.04751548543572426,-0.1631232649087906,0.3465838134288788,0.9225035309791565,-0.04673899710178375,-0.1640397161245346,0.35593652725219727,0.918812096118927,-0.04594998061656952,-0.16409508883953094,0.3577708303928375,0.9181293249130249,-0.045265886932611465,-0.16416621208190918,0.3552863895893097,0.9191148281097412,-0.04413122683763504,-0.1645842045545578,0.3460181951522827,0.9226244688034058,-0.04268813878297806,-0.16509486734867096,0.3319866359233856,0.927742600440979,-0.041223883628845215,-0.16531789302825928,0.31579309701919556,0.9334052801132202,-0.04003378748893738,-0.16494733095169067,0.3005619943141937,0.9385372996330261,-0.039435964077711105,-0.1638539880514145,0.2893171012401581,0.9422803521156311,-0.039339594542980194,-0.1623171716928482,0.28235170245170593,0.9446603059768677,-0.03946536034345627,-0.16073137521743774,0.2781645357608795,0.9461672306060791,-0.03965011611580849,-0.15950100123882294,0.27602118253707886,0.9469950795173645,-0.039600834250450134,-0.15912391245365143,0.27416756749153137,0.9475988149642944],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999996423721313,0.9999999403953552,0.9999997615814209,0.9999997019767761,0.9999998807907104,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,3.6666667461395264],"values":[-0.000003757190597752924,18.7510986328125,-0.000012833245818910655,-0.000002801991058731801,18.751081466674805,0.0000065251742853433825],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[0.05503769963979721,0.051962316036224365,-0.01394518744200468,0.9970337748527527,0.0550377331674099,0.051962364464998245,-0.013945182785391808,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999999403953552,1.0000001192092896,0.9999999403953552,0.9999999403953552,1.0000001192092896,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,3.6666667461395264],"values":[-4.513199806213379,8.24638557434082,-0.08130142837762833,-4.513206481933594,8.246400833129883,-0.08130490034818649],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.6286430954933167,-0.5262147188186646,0.3862745463848114,0.4227267801761627,0.6292889714241028,-0.5238685011863708,0.3877001106739044,0.42337435483932495,0.6309317946434021,-0.5169757008552551,0.3929033875465393,0.42460355162620544,0.6337267160415649,-0.5104073286056519,0.39990073442459106,0.4218462109565735,0.6382554173469543,-0.5076460242271423,0.4073322117328644,0.41110360622406006,0.6428852677345276,-0.5074833631515503,0.4162360429763794,0.39485037326812744,0.645591676235199,-0.5092106461524963,0.4281085729598999,0.3750185966491699,0.6451955437660217,-0.5092376470565796,0.44390177726745605,0.35686272382736206,0.6435471177101135,-0.5107952952384949,0.4603504240512848,0.3361736238002777,0.6387819051742554,-0.509575605392456,0.4822130501270294,0.315849632024765,0.6348620057106018,-0.5022545456886292,0.5079938173294067,0.2943347692489624,0.6310873627662659,-0.4903852939605713,0.5344257950782776,0.27502748370170593,0.6243166327476501,-0.4793405532836914,0.5608044862747192,0.2568261921405792,0.6163973212242126,-0.46985992789268494,0.5836613774299622,0.2421267330646515,0.6068047285079956,-0.46287956833839417,0.6026555299758911,0.2331029176712036,0.5930248498916626,-0.4642902910709381,0.61650550365448,0.22951483726501465,0.570779025554657,-0.48074886202812195,0.6252251267433167,0.2284412980079651,0.5433774590492249,-0.5061272978782654,0.6284725666046143,0.23151318728923798,0.5117377638816833,-0.5434951186180115,0.6222103238105774,0.23577938973903656,0.467550665140152,-0.6015621423721313,0.6019351482391357,0.2391514778137207,0.4083849787712097,-0.6585161685943604,0.5855221748352051,0.23820582032203674,0.32874569296836853,-0.7006824016571045,0.5858709216117859,0.24026186764240265,0.20797450840473175,-0.7098528742790222,0.6109560132026672,0.28211402893066406,0.10684771835803986,-0.6925049424171448,0.6267670392990112,0.34085729718208313,0.05420792102813721,-0.671995222568512,0.6264758706092834,0.39116743206977844,0.022288449108600616,-0.6506505608558655,0.6171843409538269,0.44186046719551086,0.015437196008861065,-0.6298914551734924,0.603750467300415,0.4883481562137604,0.0373368002474308,-0.6137650012969971,0.5876501202583313,0.5258952379226685,0.09678434580564499,-0.6100854873657227,0.5607119202613831,0.551389753818512,0.24349889159202576,-0.6413687467575073,0.48796579241752625,0.5396701097488403,0.42050695419311523,-0.6604270339012146,0.3798271417617798,0.4926879405975342,0.5427310466766357,-0.6003801226615906,0.34470412135124207,0.4755689203739166,0.6416685581207275,-0.5226211547851562,0.3294624090194702,0.4545142352581024,0.7224399447441101,-0.4431987702846527,0.3219018280506134,0.42194148898124695,0.7967609167098999,-0.36487945914268494,0.3227439522743225,0.3575912117958069,0.8556890487670898,-0.28516116738319397,0.3259151577949524,0.28329962491989136,0.885110080242157,-0.23236359655857086,0.318788081407547,0.24690377712249756,0.8951749801635742,-0.20710629224777222,0.30506670475006104,0.25040578842163086,0.8948475122451782,-0.1990608423948288,0.2916831076145172,0.27302688360214233,0.8843346238136292,-0.2078724503517151,0.27378973364830017,0.31588053703308105,0.8618721961975098,-0.23628105223178864,0.24559754133224487,0.3755389153957367,0.8276126384735107,-0.2730098068714142,0.20979580283164978,0.4432930648326874,0.7876498103141785,-0.30285459756851196,0.17741705477237701,0.5063695311546326,0.7466132640838623,-0.3260239064693451,0.14976786077022552,0.5602201819419861,0.7099464535713196,-0.34251534938812256,0.13044396042823792,0.6013681292533875,0.6757062673568726,-0.36131566762924194,0.12139123678207397,0.6309804320335388,0.6378343105316162,-0.3884058892726898,0.12140179425477982,0.6538882851600647,0.5951592922210693,-0.4227033257484436,0.12742242217063904,0.6714691519737244,0.5524240732192993,-0.45601117610931396,0.13568031787872314,0.6844504475593567,0.5105377435684204,-0.49766606092453003,0.1529148519039154,0.6843221187591553,0.4750484526157379,-0.5407646298408508,0.17716503143310547,0.6712043285369873,0.4654664993286133,-0.5808922052383423,0.22273792326450348,0.6295180320739746,0.46342581510543823,-0.6219730377197266,0.2929535210132599,0.5590745806694031,0.46070554852485657,-0.6493814587593079,0.38045600056648254,0.47043320536613464,0.47234025597572327,-0.6413443684577942,0.4809781014919281,0.3663770854473114,0.46058517694473267,-0.6315225958824158,0.5602601766586304,0.2741333544254303,0.43190935254096985,-0.6315016150474548,0.6061950325965881,0.21722713112831116,0.4136022925376892,-0.6340686082839966,0.6235595345497131,0.19509918987751007,0.4081982672214508,-0.6367371082305908,0.6245394945144653,0.19465488195419312,0.4070082902908325,-0.6385015249252319,0.6225250959396362,0.19779430329799652,0.4073474705219269,-0.6419771909713745,0.6156241297721863,0.2072204053401947,0.40721407532691956,-0.6480399966239929,0.6041175723075867,0.2219523936510086,0.4050217568874359,-0.6567551493644714,0.5882531404495239,0.24205031991004944,0.39669671654701233,-0.6726828813552856,0.5625237226486206,0.2714710533618927,0.3872404098510742,-0.6896302103996277,0.5326955318450928,0.3011488914489746,0.37110796570777893,-0.7095476388931274,0.49698054790496826,0.3344118893146515,0.34649866819381714,-0.7324857711791992,0.45532819628715515,0.36888957023620605,0.30256155133247375,-0.7641422748565674,0.39540109038352966,0.41012337803840637,0.2358645498752594,-0.7995911240577698,0.32814016938209534,0.4442364275455475,0.18116062879562378,-0.8217829465866089,0.310881644487381,0.44182154536247253,0.17352886497974396,-0.8190828561782837,0.30767762660980225,0.45202380418777466,0.21101900935173035,-0.7950629591941833,0.2942133843898773,0.4866049885749817,0.2645614445209503,-0.7584289312362671,0.2790076434612274,0.5262580513954163,0.3071156442165375,-0.725597083568573,0.26429373025894165,0.5561813712120056,0.3458167016506195,-0.6905432343482971,0.24808819591999054,0.5848188996315002,0.3896692097187042,-0.655778169631958,0.23133239150047302,0.603819727897644,0.44841375946998596,-0.6263766884803772,0.20843341946601868,0.6026050448417664,0.5000777244567871,-0.6128237247467041,0.2019977867603302,0.5775519013404846,0.522526204586029,-0.5963428616523743,0.23574885725975037,0.561928927898407,0.511147677898407,-0.5744104981422424,0.3096987307071686,0.5593454241752625,0.49574047327041626,-0.5572649240493774,0.39020422101020813,0.5398499369621277,0.4816870391368866,-0.5509150624275208,0.4760199189186096,0.48772451281547546,0.4641624391078949,-0.5567811131477356,0.5338496565818787,0.4353763461112976,0.44867029786109924,-0.5714066624641418,0.547895610332489,0.4147286117076874,0.45415282249450684,-0.5803532600402832,0.5322871804237366,0.4166603684425354,0.4721767008304596,-0.5771755576133728,0.5167515873908997,0.4205774962902069,0.48944151401519775,-0.5724388360977173,0.4964355230331421,0.4316394031047821,0.5092989206314087,-0.5652311444282532,0.4779697358608246,0.4389457404613495,0.5311025977134705,-0.5555823445320129,0.4608503580093384,0.4437062740325928,0.5506083369255066,-0.5457179546356201,0.445235937833786,0.4480929672718048,0.570280134677887,-0.5355294346809387,0.4282349944114685,0.45233139395713806,0.5907333493232727,-0.5247913002967834,0.40916135907173157,0.4563060998916626,0.6113649010658264,-0.5159527659416199,0.3884468674659729,0.45731258392333984,0.6299532055854797,-0.5079332590103149,0.3690412938594818,0.45713379979133606,0.6436276435852051,-0.4993797242641449,0.354591965675354,0.4589422643184662,0.6518166065216064,-0.49521854519844055,0.3460872769355774,0.4583856165409088,0.6503130197525024,-0.4994147717952728,0.3498271107673645,0.45309925079345703,0.6470469832420349,-0.5042725801467896,0.3556404113769531,0.44783854484558105,0.6437826752662659,-0.5085349082946777,0.3613356053829193,0.4431396722793579,0.6427327990531921,-0.5104748010635376,0.36446550488471985,0.4398578703403473,0.6421008706092834,-0.5117992758750916,0.36850765347480774,0.4358556568622589,0.6421630382537842,-0.5117989182472229,0.373891681432724,0.4311537742614746,0.6409783959388733,-0.513247013092041,0.37767457962036133,0.42788565158843994,0.6388463973999023,-0.5159496068954468,0.37974441051483154,0.42598769068717957,0.6368787288665771,-0.5183072686195374,0.38129520416259766,0.42468470335006714,0.6351042985916138,-0.520306408405304,0.3823726773262024,0.4239279627799988,0.6333093643188477,-0.5220346450805664,0.38325101137161255,0.4236953854560852,0.6315155625343323,-0.5236140489578247,0.3839770555496216,0.4237666130065918,0.629910945892334,-0.5250157117843628,0.3847893476486206,0.42368361353874207,0.6288413405418396,-0.5260043144226074,0.38618552684783936,0.4227752387523651],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1,0.9999998211860657,0.9999999403953552,0.9999997019767761,0.9999998211860657,0.9999998807907104],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,3.6666667461395264],"values":[-0.000004136385086894734,17.92139434814453,0.0000017989732441492379,0.000003488479705993086,17.921398162841797,0.000004475974037632113],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.06121024861931801,-0.29321208596229553,-0.5803633332252502,0.7572703957557678,-0.06098426878452301,-0.2939640283584595,-0.5775229930877686,0.7591661810874939,-0.04881610348820686,-0.29904666543006897,-0.5710197687149048,0.7629709243774414,-0.029415257275104523,-0.3010281026363373,-0.5588909983634949,0.7721125483512878,0.0009752532350830734,-0.30404239892959595,-0.5414766669273376,0.7838114500045776,0.0350024588406086,-0.30576130747795105,-0.5184409022331238,0.7978119254112244,0.07118692249059677,-0.3050520420074463,-0.4911457598209381,0.8128047585487366,0.10718747973442078,-0.3008469343185425,-0.4610021412372589,0.827936589717865,0.14028242230415344,-0.2933250367641449,-0.42974138259887695,0.8423797488212585,0.16435615718364716,-0.2837725281715393,-0.38464048504829407,0.862851083278656,0.17358268797397614,-0.2665212154388428,-0.32933926582336426,0.8890281915664673,0.1700439602136612,-0.23930999636650085,-0.2745167911052704,0.9156726598739624,0.1529841274023056,-0.20084208250045776,-0.22914162278175354,0.9400811195373535,0.124660424888134,-0.1535864919424057,-0.20154954493045807,0.9592959880828857,0.08834555000066757,-0.1018897220492363,-0.19191201031208038,0.9721025228500366,0.04488842561841011,-0.04283921793103218,-0.16941367089748383,0.983589768409729,0.01435873657464981,0.006554040592163801,-0.14308345317840576,0.9895848035812378,-0.01538707036525011,0.04787474870681763,-0.11188768595457077,0.9924477338790894,-0.04176783189177513,0.09039752930402756,-0.0829901397228241,0.9915626049041748,-0.043581221252679825,0.1393168866634369,-0.057475779205560684,0.9876173734664917,-0.04381535202264786,0.18396314978599548,-0.049100518226623535,0.9807277917861938,-0.040077243000268936,0.2041914463043213,-0.059294164180755615,0.9763113856315613,-0.03171731159090996,0.1375436931848526,-0.08127634972333908,0.9866458177566528,-0.03505672514438629,0.06614717096090317,-0.13127365708351135,0.9885154962539673,-0.03176979348063469,0.036802757531404495,-0.18478168547153473,0.9815763235092163,-0.020038049668073654,0.041308846324682236,-0.20311576128005981,0.97807776927948,0.0000295606969302753,0.07320152223110199,-0.2108299881219864,0.9747780561447144,0.009605804458260536,0.12485513836145401,-0.23721659183502197,0.9633520841598511,0.0021088828798383474,0.17425595223903656,-0.29406973719596863,0.9397624135017395,0.025965018197894096,0.29868802428245544,-0.3217191994190216,0.8981136083602905,0.04425356909632683,0.4751876890659332,-0.29247382283210754,0.8286720514297485,0.07655651867389679,0.486471027135849,-0.2750813364982605,0.8257210850715637,0.11081581562757492,0.46662279963493347,-0.25326427817344666,0.8401430249214172,0.13998934626579285,0.4129945933818817,-0.24224482476711273,0.8666925430297852,0.20133161544799805,0.2990458309650421,-0.2748250663280487,0.8913519978523254,0.27443063259124756,0.13575802743434906,-0.3539082407951355,0.8837457895278931,0.3204082250595093,-0.014601100236177444,-0.44366565346717834,0.8368310332298279,0.3218044340610504,-0.15463624894618988,-0.516345202922821,0.7784068584442139,0.255075603723526,-0.17447219789028168,-0.6533302664756775,0.6911263465881348,0.18268820643424988,-0.15535606443881989,-0.6976941823959351,0.675064742565155,0.1201135665178299,-0.1250913292169571,-0.6983568072319031,0.6944225430488586,0.05666042119264603,-0.09300661087036133,-0.7110348343849182,0.6946717500686646,-0.010451370850205421,-0.05516034737229347,-0.7036858201026917,0.7082898020744324,-0.06186400353908539,-0.015378418378531933,-0.6798849701881409,0.7305428385734558,-0.09074655175209045,0.014327618293464184,-0.661302924156189,0.7444718480110168,-0.10082928836345673,0.04758816957473755,-0.6644116640090942,0.7390034198760986,-0.07725054025650024,0.0945790633559227,-0.6668782830238342,0.7350922226905823,-0.03213190659880638,0.1425095796585083,-0.6742996573448181,0.7238636016845703,0.01632234826683998,0.18733064830303192,-0.695568859577179,0.6934152841567993,0.06272576004266739,0.21928903460502625,-0.7104787230491638,0.6657310128211975,0.08983268588781357,0.2405427098274231,-0.7260555028915405,0.6378971338272095,0.07840555906295776,0.20681191980838776,-0.7068354487419128,0.6719115376472473,0.03871921822428703,0.14920860528945923,-0.6441189646720886,0.7492318153381348,-0.0033980447333306074,0.0697028711438179,-0.5508336424827576,0.8316924571990967,-0.018544232472777367,0.0023997169919312,-0.4074995815753937,0.9130139350891113,-0.010995341464877129,-0.04855462163686752,-0.2166588008403778,0.9749771952629089,-0.0001926535042002797,-0.06529296934604645,-0.1571153700351715,0.985419511795044,0.005648536141961813,-0.07496332377195358,-0.13589724898338318,0.9878666996955872,0.008076099678874016,-0.0798121988773346,-0.12168224900960922,0.9893221259117126,0.004760207142680883,-0.07682940363883972,-0.11295555531978607,0.9906137585639954,-0.001519770477898419,-0.05749638006091118,-0.110614113509655,0.9921977519989014,-0.007232389412820339,-0.025444475933909416,-0.1108439713716507,0.9934857487678528,-0.012977235019207,0.017114488407969475,-0.11762957274913788,0.9928252696990967,-0.015493822284042835,0.08230798691511154,-0.13210858404636383,0.9876905679702759,-0.01881677657365799,0.17955102026462555,-0.1523091197013855,0.9717043042182922,-0.01860218681395054,0.3390392065048218,-0.1830221563577652,0.9226100444793701,-0.012285707518458366,0.49290579557418823,-0.22401055693626404,0.8406618237495422,-0.0055543831549584866,0.6072236895561218,-0.27285781502723694,0.7461884617805481,0.005727727897465229,0.6864114999771118,-0.32285240292549133,0.6515925526618958,0.0051093618385493755,0.7124651074409485,-0.3687823414802551,0.5969647765159607,-0.009969587437808514,0.7071940302848816,-0.40834328532218933,0.5770901441574097,-0.030846841633319855,0.7009220123291016,-0.44130438566207886,0.559470534324646,-0.05287162959575653,0.6894406080245972,-0.46596619486808777,0.5520432591438293,-0.08225676417350769,0.6565749645233154,-0.48005029559135437,0.5759297013282776,-0.12338190525770187,0.6081013083457947,-0.4854057729244232,0.61593097448349,-0.1414046734571457,0.538014829158783,-0.4474667012691498,0.7002273201942444,-0.1369425505399704,0.4014703333377838,-0.3668559193611145,0.8279402852058411,-0.14857003092765808,0.23185287415981293,-0.3090222179889679,0.9103167057037354,-0.09320540726184845,0.08821795135736465,-0.22346800565719604,0.9662258625030518,0.024181516841053963,-0.06668742001056671,-0.15926840901374817,0.9846835136413574,0.044693514704704285,-0.15397261083126068,-0.1975536197423935,0.9670922756195068,0.056279800832271576,-0.16811206936836243,-0.0489639975130558,0.982941210269928,0.051660627126693726,-0.17945459485054016,0.06903647631406784,0.9799802303314209,0.08647839725017548,-0.17869621515274048,0.1411907970905304,0.9698733687400818,0.13364510238170624,-0.16164971888065338,0.18621081113815308,0.9598613977432251,0.14740478992462158,-0.1483016014099121,0.15080556273460388,0.9661967754364014,0.13542667031288147,-0.13551442325115204,0.08493824303150177,0.9777939319610596,0.09784238040447235,-0.12309987097978592,0.03695487231016159,0.9868676066398621,0.03806241974234581,-0.0892300084233284,-0.09643761068582535,0.9906003475189209,0.0047647603787481785,-0.043098870664834976,-0.18970459699630737,0.9808832406997681,-0.005874503869563341,0.0067377774976193905,-0.2853311598300934,0.9583873152732849,0.0008288251119665802,0.023580489680171013,-0.34991350769996643,0.9364848136901855,0.010282618924975395,0.014620198868215084,-0.39076346158981323,0.9203175902366638,0.014963245019316673,-0.012160464189946651,-0.4288671314716339,0.9031618237495422,0.01409879606217146,-0.04953232407569885,-0.4633939266204834,0.8846546411514282,0.009082021191716194,-0.08911990374326706,-0.4932084083557129,0.8652864694595337,-0.00010409121023258194,-0.12773220241069794,-0.517173171043396,0.8462956547737122,-0.010856525972485542,-0.16474907100200653,-0.534476101398468,0.8289000391960144,-0.021032797172665596,-0.1995026022195816,-0.5444737672805786,0.8144351840019226,-0.028424665331840515,-0.2319943755865097,-0.5434565544128418,0.8062416911125183,-0.03436148166656494,-0.2605193257331848,-0.5415396690368652,0.798551082611084,-0.04473084583878517,-0.2819840610027313,-0.5454168319702148,0.7880384922027588,-0.04979097843170166,-0.2912324368953705,-0.5523380041122437,0.7795045971870422,-0.051899947226047516,-0.2941269874572754,-0.5597982406616211,0.7729306221008301,-0.05384165793657303,-0.295381098985672,-0.5669277310371399,0.7671011090278625,-0.05561797693371773,-0.29558753967285156,-0.5730777978897095,0.7623100280761719,-0.05728497728705406,-0.2953677475452423,-0.5777221322059631,0.7587578892707825,-0.059127237647771835,-0.2947726249694824,-0.5807012915611267,0.7565706372261047,-0.060929372906684875,-0.29368582367897034,-0.5817722082138062,0.7560274004936218,-0.06120840087532997,-0.2932127118110657,-0.5803448557853699,0.7572844624519348],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999997019767761,0.9999998211860657,0.9999996423721313,0.9999999403953552,0.9999999403953552,0.9999997019767761],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.000008550558050046675,17.203697204589844,-0.000002213190327893244,0.000005062018772150623,17.203699111938477,0.00000490313414047705],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[-0.19362016022205353,0.17722903192043304,-0.6406020522117615,0.7216163277626038,-0.19251056015491486,0.174772247672081,-0.6413215398788452,0.7218732833862305,-0.18880589306354523,0.16886933147907257,-0.634064257144928,0.7306147813796997,-0.18288062512874603,0.16055575013160706,-0.620346188545227,0.7456186413764954,-0.17512573301792145,0.15084490180015564,-0.6020030379295349,0.7643095850944519,-0.16506223380565643,0.14133216440677643,-0.5733596086502075,0.789961040019989,-0.14969530701637268,0.13019725680351257,-0.5395485162734985,0.8182465434074402,-0.13042016327381134,0.1194257065653801,-0.5016845464706421,0.8467825651168823,-0.10807235538959503,0.11002057045698166,-0.4620479345321655,0.8733426928520203,-0.09336438775062561,0.097647525370121,-0.4466562569141388,0.8844468593597412,-0.08274208009243011,0.08650344610214233,-0.44512617588043213,0.8874309062957764,-0.06908580660820007,0.08153365552425385,-0.4367940127849579,0.8931912183761597,-0.05468294396996498,0.08331063389778137,-0.4180852472782135,0.9029251933097839,-0.04436651989817619,0.08791360259056091,-0.3871317207813263,0.9167507290840149,-0.04121514409780502,0.09190970659255981,-0.34439679980278015,0.9334049224853516,-0.058297865092754364,0.09554516524076462,-0.34807395935058594,0.9307615160942078,-0.08879018574953079,0.09541162103414536,-0.38708415627479553,0.9127863049507141,-0.11801031231880188,0.10030561685562134,-0.4133927524089813,0.8972841501235962,-0.13993287086486816,0.11154250055551529,-0.4155530333518982,0.8917918801307678,-0.17042365670204163,0.12426496297121048,-0.4430372714996338,0.8713392019271851,-0.199798583984375,0.14254017174243927,-0.46211689710617065,0.8521800637245178,-0.22850055992603302,0.16594573855400085,-0.4732188582420349,0.8344539999961853,-0.27238497138023376,0.2006276249885559,-0.46680891513824463,0.8170951008796692,-0.30607593059539795,0.24135343730449677,-0.45701947808265686,0.7994993925094604,-0.3274773955345154,0.26930952072143555,-0.4577237367630005,0.7814857363700867,-0.3462604284286499,0.2980234920978546,-0.46705153584480286,0.7570657730102539,-0.36549657583236694,0.33983635902404785,-0.5155978202819824,0.696478545665741,-0.366753488779068,0.3906426727771759,-0.6036021709442139,0.5903851389884949,-0.3346858322620392,0.4275003671646118,-0.7021651268005371,0.4606441259384155,-0.2762020230293274,0.42908182740211487,-0.7908385992050171,0.33789873123168945,-0.2787187695503235,0.45404407382011414,-0.783674418926239,0.3193967342376709,-0.42563396692276,0.48411697149276733,-0.574979305267334,0.5038505792617798,-0.4553118944168091,0.3629770278930664,-0.34565865993499756,0.7358388900756836,-0.2598390579223633,0.207503542304039,-0.12351571768522263,0.934970498085022,-0.10867010056972504,0.16105177998542786,-0.06896710395812988,0.9785175919532776,0.029320556670427322,0.11470682173967361,-0.052117969840765,0.9915978908538818,0.08246678858995438,0.07875744998455048,-0.06465376913547516,0.9913709759712219,0.053333159536123276,0.04970039799809456,-0.14412038028240204,0.9868711829185486,0.053614843636751175,0.0011805786052718759,-0.07505341619253159,0.9957364797592163,-0.0038863245863467455,-0.041883401572704315,-0.15672704577445984,0.9867458343505859,-0.08444834500551224,-0.0718497633934021,-0.2875114679336548,0.9513376355171204,-0.16374114155769348,-0.10526130348443985,-0.360077828168869,0.9123885631561279,-0.23761749267578125,-0.1259060949087143,-0.4251898527145386,0.8642333149909973,-0.29870498180389404,-0.13527151942253113,-0.4839276373386383,0.8113513588905334,-0.34478968381881714,-0.1429288685321808,-0.5357066988945007,0.7574363350868225,-0.38685131072998047,-0.16962337493896484,-0.5714694261550903,0.7035599946975708,-0.4258866310119629,-0.21461345255374908,-0.5964710116386414,0.6455880999565125,-0.4187605082988739,-0.23949050903320312,-0.6261704564094543,0.6125311255455017,-0.4148785173892975,-0.2839013338088989,-0.633082926273346,0.5886270403862,-0.4226691424846649,-0.331768274307251,-0.6276041865348816,0.5633769631385803,-0.4060279428958893,-0.3418559432029724,-0.6318108439445496,0.5648813843727112,-0.3810455799102783,-0.32971376180648804,-0.5938124060630798,0.6272798180580139,-0.2770020067691803,-0.30023059248924255,-0.47974029183387756,0.7765183448791504,-0.13964223861694336,-0.26640456914901733,-0.26957350969314575,0.9147998690605164,-0.027603087946772575,-0.21810920536518097,-0.08068975061178207,0.9721911549568176,-0.027285443618893623,-0.17133253812789917,-0.07713023573160172,0.9818103909492493,-0.014359071850776672,-0.1224546954035759,-0.08199208974838257,0.9889771938323975,0.0011644113110378385,-0.0708196684718132,-0.08647697418928146,0.9937328696250916,0.015793872997164726,-0.01751820184290409,-0.0908261239528656,0.9955874085426331,0.024064993485808372,0.03642761707305908,-0.09545395523309708,0.9944760203361511,0.024219749495387077,0.08978959918022156,-0.10291213542222977,0.9903334975242615,0.016887418925762177,0.1413586437702179,-0.1165124922990799,0.9829331040382385,-0.00503156241029501,0.191248819231987,-0.13813145458698273,0.9717604517936707,-0.053480710834264755,0.23981142044067383,-0.19985947012901306,0.9485180377960205,-0.09527760744094849,0.28438305854797363,-0.29762396216392517,0.9063490033149719,-0.10090480744838715,0.3117576241493225,-0.42019644379615784,0.8462035059928894,-0.05238596349954605,0.30136361718177795,-0.5517444014549255,0.7758954763412476,0.011333908885717392,0.25347575545310974,-0.6464491486549377,0.7195311188697815,0.054307132959365845,0.19447270035743713,-0.6998134851455688,0.6851950883865356,0.04988928511738777,0.138014018535614,-0.7318710684776306,0.6654531955718994,0.020021818578243256,0.0996442586183548,-0.7483231425285339,0.6555018424987793,-0.011844868771731853,0.07499188184738159,-0.7385392189025879,0.6699222922325134,-0.03805629909038544,0.05746902897953987,-0.716300368309021,0.694379448890686,-0.05268465727567673,0.050318989902734756,-0.6810033321380615,0.7286472916603088,-0.04848562553524971,0.05979226157069206,-0.6141072511672974,0.7854593396186829,-0.05406270548701286,0.07680828869342804,-0.6020188331604004,0.792938232421875,-0.08889233320951462,0.11518911272287369,-0.5961140394210815,0.7896060347557068,-0.09815560281276703,0.2056187242269516,-0.4985552430152893,0.8363785743713379,-0.1102822944521904,0.24958977103233337,-0.48809388279914856,0.8290399312973022,-0.11335185915231705,0.28730979561805725,-0.41962727904319763,0.8535323143005371,-0.045464664697647095,0.3050076365470886,-0.0798884704709053,0.947903573513031,-0.013529225252568722,0.34488698840141296,-0.06190009415149689,0.9365032315254211,0.015599662438035011,0.37267598509788513,-0.045439738780260086,0.9267170429229736,0.02837292104959488,0.379243403673172,-0.03186962008476257,0.9243125915527344,0.032660432159900665,0.37559619545936584,-0.020510977134108543,0.9259806275367737,0.03525421768426895,0.36387360095977783,-0.009948775172233582,0.9307277798652649,0.036350008100271225,0.3477828800678253,-0.004501443821936846,0.9368593692779541,0.035918742418289185,0.31718122959136963,-0.09358462691307068,0.943052351474762,0.03721776232123375,0.28107714653015137,-0.002061596605926752,0.958961009979248,0.03667430207133293,0.2548445463180542,-0.00333206495270133,0.9662805795669556,0.035611506551504135,0.23666435480117798,-0.00584848178550601,0.9709210395812988,0.023176301270723343,0.22512926161289215,-0.11229375004768372,0.967558741569519,-0.001606294885277748,0.2031986266374588,-0.338948130607605,0.9185978174209595,-0.017651371657848358,0.18392205238342285,-0.4613012969493866,0.8677915930747986,-0.040701184421777725,0.17363981902599335,-0.5325910449028015,0.827368974685669,-0.0733012929558754,0.1709233671426773,-0.5876328349113464,0.7874641418457031,-0.10716117173433304,0.17501291632652283,-0.6286301016807556,0.7501407265663147,-0.13729913532733917,0.18255367875099182,-0.6631003022193909,0.712826132774353,-0.16087929904460907,0.1895051747560501,-0.6937959790229797,0.6759089231491089,-0.17517732083797455,0.19252102077007294,-0.7231245636940002,0.6397964358329773,-0.18206310272216797,0.18846774101257324,-0.7418961524963379,0.6171896457672119,-0.1861877292394638,0.18710435926914215,-0.7341920137405396,0.6255303025245667,-0.18955987691879272,0.18636737763881683,-0.7191517353057861,0.64199298620224,-0.19190537929534912,0.18506109714508057,-0.7035071849822998,0.6587886214256287,-0.1933199167251587,0.1833800971508026,-0.6880571246147156,0.6749641299247742,-0.19402864575386047,0.1815948784351349,-0.6735866069793701,0.6896791458129883,-0.1942174732685089,0.1798810213804245,-0.6610701680183411,0.7020745873451233,-0.19406327605247498,0.17847229540348053,-0.6507717370986938,0.7120275497436523,-0.1937689632177353,0.1775602400302887,-0.6433154344558716,0.7190766334533691,-0.19362476468086243,0.17722472548484802,-0.6406163573265076,0.7216034531593323],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998807907104,1,0.9999999403953552,1.0000001192092896,1,1],"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,3.6666667461395264],"values":[-5.922699664040465e-9,19.45050621032715,-0.000007174266102083493,0.000003545454319464625,19.45050048828125,-0.000008868591066857334],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[0.14937159419059753,-0.14748121798038483,-0.019200168550014496,0.9775319695472717,0.14937159419059753,-0.14748121798038483,-0.019200162962079048,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999998211860657,1,0.9999997615814209,0.9999998807907104,1,0.9999997019767761],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.14689970016479492,6.409495830535889,0.2581002116203308,0.1468733847141266,6.409515380859375,0.2580951750278473],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[0.020259661599993706,0.012515297159552574,-0.011196219362318516,0.999653697013855,0.020259661599993706,0.01251528225839138,-0.011196221224963665,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.9999999403953552,1.0000003576278687,1.0000001192092896,0.9999999403953552,1.000000238418579,0.9999999403953552],"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,3.6666667461395264],"values":[7.996055728654028e-8,6.81589412689209,0.000001158637701337284,-7.078493808876374e-7,6.81591272354126,-0.000003891726009896956],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264],"values":[0.1753217726945877,0.42554473876953125,-0.0429491251707077,0.8867521286010742,0.17180055379867554,0.4284171760082245,-0.04267574101686478,0.8860711455345154,0.1616627722978592,0.43672317266464233,-0.04269091784954071,0.8839205503463745,0.15154404938220978,0.4468965232372284,-0.04148752987384796,0.8806796669960022,0.14642252027988434,0.45606529712677,-0.03671290725469589,0.8770502209663391,0.14401830732822418,0.4634927809238434,-0.029993141070008278,0.8738040924072266,0.1424514502286911,0.46759551763534546,-0.022891037166118622,0.8720883131027222,0.13726823031902313,0.46969589591026306,-0.01856125146150589,0.8718937635421753,0.13487163186073303,0.46876123547554016,-0.013776041567325592,0.8728589415550232,0.13036052882671356,0.4628923237323761,-0.01193225011229515,0.8766952157020569,0.12564082443714142,0.4538663625717163,-0.01215140987187624,0.8820839524269104,0.11906496435403824,0.4451531767845154,-0.014997679740190506,0.887376606464386,0.11220240592956543,0.43516454100608826,-0.019878696650266647,0.8931110501289368,0.10515465587377548,0.4284752607345581,-0.025289516896009445,0.8970573544502258,0.09614098817110062,0.4265960156917572,-0.030580949038267136,0.898797869682312,0.08736447244882584,0.4309918284416199,-0.03331838175654411,0.8974984288215637,0.08138412982225418,0.43722060322761536,-0.031057223677635193,0.8951258063316345,0.08001363277435303,0.4495079219341278,-0.025180958211421967,0.8893291354179382,0.08598168939352036,0.4632996618747711,-0.01725100912153721,0.8818520307540894,0.09552013128995895,0.46943631768226624,-0.009116154164075851,0.8777371048927307,0.10814448446035385,0.463136225938797,-0.0007129984442144632,0.8796642422676086,0.11822712421417236,0.44022098183631897,0.005431579891592264,0.890055239200592,0.12617841362953186,0.4102995991706848,0.0020781373605132103,0.9031771421432495,0.12553617358207703,0.38825830817222595,-0.010069598443806171,0.9129046201705933,0.12736642360687256,0.35139086842536926,-0.015987487509846687,0.927386999130249,0.13319745659828186,0.28380975127220154,-0.013995164074003696,0.9494812488555908,0.13311262428760529,0.1933738738298416,-0.0106155751273036,0.9719952940940857,0.1203446090221405,0.09030946344137192,-0.007621971424669027,0.9885864853858948,0.10099810361862183,-0.011475890874862671,-0.0027958806604146957,0.9948164820671082,0.09592661261558533,-0.0999223068356514,0.0105645302683115,0.9903039932250977,0.12217782437801361,-0.17423294484615326,0.04217087849974632,0.9761849641799927,0.15653423964977264,-0.2278354912996292,0.0836992859840393,0.9573831558227539,0.1888325959444046,-0.2762050926685333,0.10360094904899597,0.9366534948348999,0.20732170343399048,-0.32263365387916565,0.10981094837188721,0.9169878959655762,0.21634770929813385,-0.3636799156665802,0.10773385316133499,0.8996244072914124,0.22084327042102814,-0.3956564664840698,0.10590386390686035,0.8851376175880432,0.22234070301055908,-0.415903776884079,0.1069081500172615,0.8753052949905396,0.2211911529302597,-0.42406535148620605,0.11206497997045517,0.8710249662399292,0.21906830370426178,-0.42285776138305664,0.11829427629709244,0.8713247776031494,0.21593311429023743,-0.4127943813800812,0.12548227608203888,0.8759154677391052,0.20676195621490479,-0.3983669877052307,0.12182574719190598,0.8852749466896057,0.19557371735572815,-0.3798286020755768,0.10748948156833649,0.8977344632148743,0.18986721336841583,-0.3607594072818756,0.09196890145540237,0.908484935760498,0.18681319057941437,-0.3458315432071686,0.06897161900997162,0.9169211387634277,0.188370481133461,-0.3300132155418396,0.045195624232292175,0.9238859415054321,0.19558551907539368,-0.3099677264690399,0.025767045095562935,0.9300550222396851,0.2050613909959793,-0.28880417346954346,0.01000149641185999,0.9351160526275635,0.21550418436527252,-0.26618877053260803,-0.0018685210961848497,0.9395201206207275,0.22555910050868988,-0.2350037395954132,-0.010655110701918602,0.9454008936882019,0.23361173272132874,-0.2098493129014969,-0.014488124288618565,0.949304461479187,0.2392076551914215,-0.18146951496601105,-0.016640011221170425,0.9537146687507629,0.24813567101955414,-0.13882458209991455,-0.03341520577669144,0.9581440091133118,0.2577207684516907,-0.09897080063819885,-0.050241950899362564,0.9598231911659241,0.2625219225883484,-0.06639471650123596,-0.05388665571808815,0.9611296653747559,0.2568318843841553,-0.03623220697045326,-0.012237846851348877,0.9656991362571716,0.24056300520896912,-0.03692815080285072,0.03091210126876831,0.9694380760192871,0.21723654866218567,-0.03377129137516022,0.049294423311948776,0.9742884039878845,0.1903594732284546,-0.003955174703150988,0.05409623309969902,0.9802148938179016,0.16362285614013672,0.043936070054769516,0.05160250514745712,0.9841922521591187,0.14016659557819366,0.0894245132803917,0.04806447774171829,0.9849093556404114,0.1230618953704834,0.13052983582019806,0.04362550750374794,0.9828095436096191,0.1145918145775795,0.16542015969753265,0.04079125449061394,0.9786934852600098,0.11742829531431198,0.19199588894844055,0.04036472365260124,0.9735085368156433,0.1555415838956833,0.1955154538154602,0.04807272180914879,0.9670933485031128,0.22926762700080872,0.16791750490665436,0.05873876437544823,0.9569690823554993,0.31488168239593506,0.11875514686107635,0.057141926139593124,0.9399369955062866,0.3939693570137024,0.06186868995428085,0.04355918988585472,0.9160038232803345,0.4545077085494995,0.014515594579279423,0.02560275048017502,0.8902564644813538,0.4883425235748291,-0.005207753274589777,0.008338702842593193,0.8725966811180115,0.497627854347229,-0.004957163706421852,-0.0042971475049853325,0.867365837097168,0.4957476556301117,-0.003089978825300932,-0.01027829572558403,0.8684003353118896,0.4875974655151367,-0.0006568612297996879,-0.013095550239086151,0.8729701042175293,0.4763212203979492,0.011311386711895466,-0.018200745806097984,0.8790102005004883,0.4602597653865814,0.023220257833600044,-0.029230572283267975,0.8869990706443787,0.4319439232349396,0.048438217490911484,-0.04640134051442146,0.8994026184082031,0.38871482014656067,0.08899744600057602,-0.062159765511751175,0.9149407148361206,0.3357585072517395,0.14006349444389343,-0.0719141885638237,0.9286962747573853,0.2776734232902527,0.1707409918308258,-0.06804905086755753,0.9429286122322083,0.21222098171710968,0.19261234998703003,-0.061682529747486115,0.9560638070106506,0.13622348010540009,0.2071102410554886,-0.05771278962492943,0.9670665860176086,0.07969371229410172,0.21553756296634674,-0.05308178439736366,0.9717894792556763,0.044828664511442184,0.2189558893442154,-0.04890645295381546,0.9734767079353333,0.020731758326292038,0.22253009676933289,-0.04739600047469139,0.9735523462295532,0.004214937333017588,0.22636689245700836,-0.04595615714788437,0.9729482531547546,-0.005937038920819759,0.23070195317268372,-0.0452863946557045,0.9719519019126892,-0.010875673033297062,0.2365279644727707,-0.04748028516769409,0.9704029560089111,-0.011901386082172394,0.24380634725093842,-0.059299372136592865,0.9679361581802368,-0.01115025207400322,0.2497754991054535,-0.06426210701465607,0.9661046862602234,-0.006846161559224129,0.25754299759864807,-0.06011872738599777,0.9643704891204834,0.0026317816227674484,0.2651793956756592,-0.05301453173160553,0.962736964225769,0.016718031838536263,0.2733571529388428,-0.04439903423190117,0.9607419967651367,0.03450367972254753,0.2835225462913513,-0.03447846695780754,0.9577242732048035,0.05701258033514023,0.2936059236526489,-0.023609289899468422,0.9539327621459961,0.07926668971776962,0.30303671956062317,-0.013754399493336678,0.9495769143104553,0.09495531022548676,0.3114287555217743,-0.007665334269404411,0.9454823732376099,0.1066531166434288,0.3189958333969116,-0.005363731645047665,0.9417207837104797,0.11724606901407242,0.32824867963790894,-0.006464778911322355,0.9372643232345581,0.12755396962165833,0.3391694128513336,-0.010215911082923412,0.93198162317276,0.13769495487213135,0.352096825838089,-0.015449493192136288,0.9256507158279419,0.14763037860393524,0.3694063127040863,-0.020974790677428246,0.9172264337539673,0.156268909573555,0.3857622444629669,-0.025424310937523842,0.9089120626449585,0.16245552897453308,0.4004378914833069,-0.027836723253130913,0.9013782739639282,0.16810497641563416,0.4124234914779663,-0.032110076397657394,0.8947718143463135,0.1729162335395813,0.42092978954315186,-0.03714153915643692,0.8896845579147339,0.1756119728088379,0.425480455160141,-0.04006266966462135,0.8868606090545654,0.17611302435398102,0.4260075092315674,-0.04048279672861099,0.8864891529083252,0.1760733723640442,0.42594847083091736,-0.040906719863414764,0.8865059018135071,0.17581887543201447,0.4258769452571869,-0.04153591766953468,0.8865615129470825,0.17547957599163055,0.4258062541484833,-0.042207442224025726,0.8866310119628906,0.17518480122089386,0.42574986815452576,-0.0427577942609787,0.8866899609565735],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,3.6666667461395264],"values":[0.574601948261261,51.81689453125,-20.52269172668457,0.5746039152145386,51.81690216064453,-20.522687911987305],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[-0.18743081390857697,8.782136262652784e-8,-0.005247786641120911,0.9822638034820557,-0.18743082880973816,1.0305084430228817e-7,-0.005247787572443485,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1.000000238418579,1.000000238418579,1.0000001192092896,1.0000001192092896,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,3.6666667461395264],"values":[-0.5745983719825745,4.330301284790039,20.522703170776367,-0.5746002197265625,4.330293655395508,20.522720336914062],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[0.6296830773353577,-8.633882231379175e-8,0.017631012946367264,0.7766520380973816,0.6296830773353577,-8.034307086290937e-8,0.017631011083722115,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,3.6666667461395264],"values":[1,1.000000238418579,1.0000003576278687,0.9999999403953552,1.0000003576278687,1.0000003576278687],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,3.6666667461395264],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,3.6666667461395264],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,3.6666667461395264],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"acea0248-a8c0-4b9e-a2dd-ed673390a3e3","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/greataxe.json b/public/anims/baked/sword_shield/greataxe.json new file mode 100644 index 0000000..cb07bc6 --- /dev/null +++ b/public/anims/baked/sword_shield/greataxe.json @@ -0,0 +1 @@ +{"name":"sword_shield/greataxe","duration":5.4666666984558105,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[-0.0938979983329773,60.45436477661133,0.6171496510505676,0.009043097496032715,60.417236328125,0.5738087892532349,0.16406702995300293,60.28213882446289,0.5226539969444275,0.35561156272888184,60.07107925415039,0.45108482241630554,0.5433737635612488,59.807586669921875,0.3604719340801239,0.6150652766227722,59.513404846191406,0.25841349363327026,0.46138423681259155,59.210243225097656,0.14744921028614044,0.18887531757354736,58.927494049072266,0.023396998643875122,-0.09919029474258423,58.69479751586914,-0.11660140752792358,-0.3890048861503601,58.54396057128906,-0.2716079354286194,-0.6878907680511475,58.56214141845703,-0.4892844557762146,-0.9870961904525757,59.00672149658203,-0.7813423871994019,-1.2487943172454834,59.719512939453125,-1.0873258113861084,-1.4603006839752197,60.5260124206543,-1.4449752569198608,-1.6362820863723755,61.30628204345703,-1.9203757047653198,-1.7660073041915894,62.080230712890625,-2.4051008224487305,-1.869450569152832,62.88758850097656,-2.8950252532958984,-1.965582013130188,63.782161712646484,-3.3831827640533447,-2.078456401824951,64.63605499267578,-3.8524646759033203,-2.2154250144958496,65.28491973876953,-4.284931659698486,-2.359588623046875,65.6962890625,-4.661418914794922,-2.5029096603393555,65.93727111816406,-4.98468542098999,-2.6479380130767822,66.04023742675781,-5.214686393737793,-2.7854361534118652,66.12870788574219,-5.345162868499756,-2.909959077835083,66.2154541015625,-5.391411304473877,-3.016369342803955,66.31275939941406,-5.137402057647705,-3.0953521728515625,66.3843994140625,-4.816097259521484,-3.1318018436431885,66.35137939453125,-4.430700778961182,-3.136629104614258,66.17852783203125,-3.997512102127075,-3.1208419799804688,65.9894027709961,-3.527796506881714,-3.0904159545898438,65.75263214111328,-2.782557249069214,-3.041201114654541,65.40918731689453,-1.7900586128234863,-2.9799811840057373,64.95552825927734,-0.6114951372146606,-2.9042885303497314,64.40177154541016,0.7064189314842224,-2.8222320079803467,63.73834991455078,2.08970046043396,-2.714663028717041,62.98381423950195,3.4987504482269287,-2.591273069381714,62.12213897705078,4.863399505615234,-2.4698095321655273,61.11302185058594,6.117923736572266,-2.379246234893799,60.17427444458008,7.17780876159668,-2.3651700019836426,59.33848571777344,7.915165424346924,-2.4099199771881104,58.631656646728516,8.28927230834961,-2.512547254562378,58.113372802734375,8.493109703063965,-2.3683905601501465,57.81110382080078,8.613317489624023,-2.2120344638824463,57.719139099121094,8.681910514831543,-2.0721781253814697,57.813880920410156,8.722750663757324,-1.972979187965393,57.979637145996094,8.75468635559082,-1.895787239074707,58.19485092163086,8.791988372802734,-1.8234608173370361,58.43401336669922,8.831976890563965,-1.7417068481445312,58.68949508666992,8.861527442932129,-1.6753522157669067,58.96168899536133,8.897796630859375,-1.8261263370513916,59.230926513671875,8.997718811035156,-2.158029079437256,59.51665496826172,9.163278579711914,-2.5064282417297363,59.848384857177734,9.376021385192871,-2.902244806289673,60.17542266845703,9.678621292114258,-3.207396984100342,60.44833755493164,10.124994277954102,-3.319572687149048,60.675209045410156,10.691272735595703,-3.5234432220458984,60.93395233154297,11.235340118408203,-3.9016079902648926,61.29128646850586,11.725415229797363,-4.316300392150879,61.46086120605469,12.469321250915527,-4.773827075958252,61.56110763549805,13.380005836486816,-5.250581741333008,61.74922561645508,14.763317108154297,-5.571595191955566,62.02729797363281,16.611970901489258,-5.758999824523926,62.39075469970703,18.81138038635254,-5.829380989074707,62.59366989135742,21.211044311523438,-5.849626064300537,62.77317810058594,23.875272750854492,-5.871424674987793,62.99474334716797,26.588964462280273,-5.821850776672363,63.38927459716797,29.198951721191406,-5.721139907836914,63.81977844238281,31.629602432250977,-5.625797271728516,64.11463165283203,33.87855911254883,-5.558770179748535,64.26343536376953,36.072998046875,-5.545677185058594,64.29265594482422,38.24422836303711,-5.618888854980469,64.17249298095703,40.42158126831055,-5.812500953674316,63.90888977050781,42.58197021484375,-6.066824436187744,63.530982971191406,44.70140838623047,-6.347760200500488,62.975799560546875,46.8101921081543,-6.696667671203613,62.18096160888672,48.97113037109375,-7.049544811248779,61.327335357666016,51.081966400146484,-7.42243766784668,60.51496887207031,53.046627044677734,-7.936205863952637,59.50413513183594,54.97266387939453,-8.619865417480469,58.274879455566406,56.83043670654297,-9.621676445007324,56.7708854675293,58.67626953125,-10.81128215789795,55.69463348388672,60.161888122558594,-11.99224853515625,55.03572082519531,60.95814514160156,-12.883750915527344,55.06282043457031,61.645626068115234,-13.600265502929688,55.25722122192383,62.21072769165039,-14.07634449005127,55.729400634765625,62.663307189941406,-14.287580490112305,56.43083190917969,63.01231002807617,-14.318056106567383,57.20566177368164,63.317413330078125,-14.13151741027832,57.93035888671875,63.60331344604492,-13.698637008666992,58.73297882080078,63.87957763671875,-13.051828384399414,59.53697204589844,64.16083526611328,-12.416674613952637,60.03154373168945,64.43814086914062,-11.83544921875,60.20752716064453,64.63652038574219,-11.204238891601562,60.266700744628906,64.77208709716797,-10.531355857849121,60.36701202392578,64.9668960571289,-9.863394737243652,60.48847961425781,65.27598571777344,-9.155431747436523,60.66264343261719,65.69989776611328,-8.394787788391113,61.03191375732422,66.127685546875,-7.578310012817383,61.44440460205078,66.5483627319336,-6.83834171295166,61.544029235839844,67.08787536621094,-6.2886199951171875,61.4827880859375,67.67272186279297,-5.863576889038086,61.47795486450195,68.27944946289062,-5.551907539367676,61.431922912597656,68.9275894165039,-5.3918914794921875,61.31748580932617,69.60041046142578,-5.364602088928223,61.128501892089844,70.3603744506836,-5.477888107299805,60.845909118652344,71.26653289794922,-5.675118446350098,60.43460464477539,72.31169891357422,-5.948775291442871,59.90610122680664,73.52537536621094,-6.226788520812988,59.35483169555664,75.06808471679688,-6.463822364807129,58.85859298706055,76.73509216308594,-6.581114292144775,58.3032341003418,78.33660888671875,-6.6132121086120605,57.89540100097656,79.92621612548828,-6.5906596183776855,57.993896484375,81.42213439941406,-6.430418968200684,58.40074157714844,82.8197250366211,-6.087199687957764,58.903018951416016,84.07013702392578,-5.609021186828613,59.253883361816406,85.36100769042969,-4.994798183441162,59.48223114013672,87.16020202636719,-4.186915874481201,59.709720611572266,89.76373291015625,-3.3911404609680176,59.81947326660156,92.76988983154297,-2.498441696166992,59.88142013549805,95.43460845947266,-1.567173957824707,59.92106246948242,98.37228393554688,-0.8207821846008301,59.74388885498047,101.55611419677734,-0.27678680419921875,59.41773223876953,104.80608367919922,0.17517662048339844,59.53484344482422,107.76204681396484,0.4687685966491699,60.08730697631836,110.42662811279297,0.45159149169921875,60.79010772705078,112.9594497680664,0.2281665802001953,61.635196685791016,115.2869873046875,-0.07925796508789062,62.49617004394531,117.24413299560547,-0.41565418243408203,63.29377746582031,118.757568359375,-0.7816534042358398,64.07086944580078,119.88082122802734,-1.1618423461914062,64.763916015625,120.66130065917969,-1.4949493408203125,65.27005767822266,121.07393646240234,-1.7277803421020508,65.56330871582031,121.17200469970703,-1.8344001770019531,65.69404602050781,121.06062316894531,-1.8035812377929688,65.69654846191406,120.69254302978516,-1.6123762130737305,65.57337188720703,120.13541412353516,-1.323690414428711,65.376708984375,119.40464782714844,-0.9650554656982422,65.08829498291016,118.44277954101562,-0.6028909683227539,64.63008880615234,117.25990295410156,-0.28969669342041016,64.16497039794922,115.97891235351562,-0.013332366943359375,63.64897918701172,114.48479461669922,0.2513771057128906,63.04108428955078,112.78282928466797,0.5656890869140625,62.394981384277344,111.13568115234375,0.9011144638061523,61.742332458496094,109.53070068359375,1.2310371398925781,61.0229606628418,107.87608337402344,1.5237641334533691,60.26226043701172,106.31421661376953,1.7435708045959473,59.50409698486328,104.91178894042969,1.8577876091003418,58.79733657836914,103.63410186767578,1.8438091278076172,58.18204879760742,102.51383972167969,1.7336668968200684,57.65635681152344,101.56083679199219,1.5503573417663574,57.29574966430664,100.76443481445312,1.3188767433166504,57.096256256103516,100.1758804321289,1.05808687210083,57.07862854003906,99.77869415283203,0.7892818450927734,57.21430587768555,99.54132080078125,0.5349335670471191,57.47005081176758,99.43743896484375,0.3184366226196289,57.81572341918945,99.41273498535156,0.16194772720336914,58.222198486328125,99.42985534667969,0.07709503173828125,58.661346435546875,99.48979949951172,0.02093648910522461,59.103416442871094,99.58230590820312,-0.01814746856689453,59.521419525146484,99.70989227294922,-0.04463005065917969,59.8895149230957,99.86941528320312,-0.06321859359741211,60.183082580566406,100.05007934570312,-0.07925128936767578,60.38007354736328,100.26203155517578,-0.09391117095947266,60.454383850097656,100.43960571289062],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.23087634146213531,-0.5010440945625305,0.26560184359550476,0.7906368374824524,0.2302902340888977,-0.48288506269454956,0.2619984745979309,0.8032093644142151,0.22962725162506104,-0.46435147523880005,0.25815528631210327,0.8154783248901367,0.2288459688425064,-0.44543886184692383,0.25412115454673767,0.8274275064468384,0.22795391082763672,-0.42617595195770264,0.24989496171474457,0.8390254378318787,0.22695843875408173,-0.4065955579280853,0.24547599256038666,0.850242018699646,0.22585691511631012,-0.3867338299751282,0.2408783882856369,0.8610477447509766,0.22461457550525665,-0.36664214730262756,0.23616380989551544,0.871406078338623,0.22321347892284393,-0.34639352560043335,0.2313799113035202,0.881277859210968,0.22167706489562988,-0.3260796070098877,0.2265239804983139,0.8906280398368835,0.21989522874355316,-0.3057156801223755,0.22180978953838348,0.8994356393814087,0.21778139472007751,-0.28539976477622986,0.21744312345981598,0.9076545238494873,0.21554578840732574,-0.26533958315849304,0.2131553441286087,0.9152594208717346,0.21318002045154572,-0.24557533860206604,0.2090284377336502,0.9222549200057983,0.21056018769741058,-0.22670942544937134,0.20580124855041504,0.928392767906189,0.2076537162065506,-0.20828987658023834,0.20335480570793152,0.9338855147361755,0.2048865407705307,-0.19094447791576385,0.20093490183353424,0.9387155771255493,0.20226353406906128,-0.17467224597930908,0.19871510565280914,0.9429164528846741,0.19988371431827545,-0.15948167443275452,0.19667939841747284,0.9465354681015015,0.1982887089252472,-0.14822152256965637,0.19591595232486725,0.9488566517829895,0.19746272265911102,-0.13794085383415222,0.19506245851516724,0.9507530927658081,0.19715745747089386,-0.12841811776161194,0.19395853579044342,0.9523748159408569,0.197371706366539,-0.11974968016147614,0.1927330642938614,0.953707754611969,0.19816064834594727,-0.11219614744186401,0.1914989948272705,0.9547107219696045,0.1996505707502365,-0.10612214356660843,0.1905016154050827,0.955294132232666,0.20156514644622803,-0.10170283913612366,0.18988515436649323,0.9554955363273621,0.2031913846731186,-0.09822959452867508,0.18941819667816162,0.9556071162223816,0.20426015555858612,-0.0956810936331749,0.18894270062446594,0.955731987953186,0.20395588874816895,-0.09502407163381577,0.1889284998178482,0.9558653235435486,0.20298004150390625,-0.09630752354860306,0.18900549411773682,0.9559293389320374,0.20369590818881989,-0.0986165702342987,0.18885619938373566,0.9555710554122925,0.20690828561782837,-0.1021164208650589,0.18803545832633972,0.9546747803688049,0.21278494596481323,-0.10454262793064117,0.18627452850341797,0.9534648656845093,0.2217818945646286,-0.10763069987297058,0.1839321255683899,0.9515237212181091,0.23419341444969177,-0.11142390966415405,0.18145596981048584,0.9485841393470764,0.2486409693956375,-0.11770510673522949,0.17788922786712646,0.9448167085647583,0.2642533779144287,-0.12567353248596191,0.17380519211292267,0.9403021335601807,0.27932748198509216,-0.13267679512500763,0.17029735445976257,0.9356130957603455,0.29131051898002625,-0.13986794650554657,0.16860027611255646,0.9312084317207336,0.30227339267730713,-0.14690060913562775,0.1715904176235199,0.9260711073875427,0.3126955032348633,-0.15242896974086761,0.1785290688276291,0.9203881621360779,0.3222520351409912,-0.1557929664850235,0.18843406438827515,0.9145352840423584,0.3299148976802826,-0.16191455721855164,0.19936755299568176,0.9084010124206543,0.3358038365840912,-0.16818994283676147,0.20985835790634155,0.9027222394943237,0.3387003242969513,-0.1726185381412506,0.21972960233688354,0.8984452486038208,0.3383241295814514,-0.17694085836410522,0.23041078448295593,0.8950640559196472,0.3348531424999237,-0.18300969898700714,0.24022026360034943,0.8925665616989136,0.3289296329021454,-0.18946684896945953,0.24708016216754913,0.8915486931800842,0.31893789768218994,-0.19430270791053772,0.24953623116016388,0.8934521675109863,0.3086096942424774,-0.19956542551517487,0.24409827589988708,0.8974128365516663,0.3009619116783142,-0.20632560551166534,0.2362426221370697,0.9005782008171082,0.2944631278514862,-0.22344334423542023,0.23143106698989868,0.899891197681427,0.28487545251846313,-0.25950756669044495,0.22986474633216858,0.8936800360679626,0.27213403582572937,-0.3053053915500641,0.23409953713417053,0.8820028901100159,0.2593541443347931,-0.34554585814476013,0.24083274602890015,0.8690990209579468,0.2502249479293823,-0.37010452151298523,0.24510078132152557,0.8604276776313782,0.24190574884414673,-0.38266605138778687,0.24760521948337555,0.856586217880249,0.23142284154891968,-0.3898632824420929,0.25077110528945923,0.8553151488304138,0.2224692702293396,-0.38979092240333557,0.25253236293792725,0.8572035431861877,0.21591289341449738,-0.37229159474372864,0.25084489583969116,0.8670971393585205,0.21621368825435638,-0.33587607741355896,0.24314840137958527,0.8839218020439148,0.22111104428768158,-0.2886485159397125,0.23116439580917358,0.9024162292480469,0.2284383773803711,-0.24630703032016754,0.21811960637569427,0.9162819981575012,0.23423616588115692,-0.22147546708583832,0.20805534720420837,0.923469066619873,0.23898878693580627,-0.20440246164798737,0.19948388636112213,0.9280680418014526,0.2440146952867508,-0.18895255029201508,0.19071784615516663,0.9318693280220032,0.24800489842891693,-0.182312473654747,0.18316440284252167,0.9336522817611694,0.25299006700515747,-0.1825370043516159,0.17519673705101013,0.9337999820709229,0.25934725999832153,-0.1824111044406891,0.16670164465904236,0.9336357712745667,0.2652028799057007,-0.17478248476982117,0.156683087348938,0.9351838827133179,0.2715801000595093,-0.159889355301857,0.1454746574163437,0.937825620174408,0.2797520160675049,-0.1395258903503418,0.13299287855625153,0.9405233860015869,0.28775620460510254,-0.11537936329841614,0.12176527082920074,0.9428982734680176,0.2949461042881012,-0.08737475425004959,0.11232341825962067,0.9448575973510742,0.30256494879722595,-0.054271150380373,0.10289660841226578,0.9460028409957886,0.31107309460639954,-0.015072844922542572,0.09217076003551483,0.9457858204841614,0.3204866349697113,0.03137258067727089,0.07753247767686844,0.9435533285140991,0.33092737197875977,0.08632858097553253,0.05848168581724167,0.9378775954246521,0.342253178358078,0.154273122549057,0.03748740255832672,0.9260978698730469,0.35277825593948364,0.2267501801252365,0.01560874842107296,0.9076828956604004,0.36120742559432983,0.29476818442344666,-0.0051649888046085835,0.8846548795700073,0.36745670437812805,0.3521169126033783,-0.023354293778538704,0.8604904413223267,0.3705432116985321,0.38316288590431213,-0.03457793965935707,0.8453924059867859,0.3714534044265747,0.40199559926986694,-0.041288889944553375,0.8358930349349976,0.37283575534820557,0.4327191114425659,-0.050261154770851135,0.8192811012268066,0.37284812331199646,0.4711107313632965,-0.062045659869909286,0.7969876527786255,0.3698940575122833,0.5020646452903748,-0.0692041888833046,0.7786657214164734,0.36541280150413513,0.5208256244659424,-0.06886181235313416,0.7684219479560852,0.3618946075439453,0.5350209474563599,-0.06637978553771973,0.7605120539665222,0.3593307435512543,0.5585673451423645,-0.06911734491586685,0.7443835139274597,0.35399752855300903,0.5908019542694092,-0.0757424607872963,0.721042275428772,0.345961332321167,0.6152912974357605,-0.08316085487604141,0.7034285664558411,0.34075114130973816,0.628641664981842,-0.09106212854385376,0.693113386631012,0.33744317293167114,0.6385095715522766,-0.09993527829647064,0.6844345927238464,0.3348478674888611,0.6482264399528503,-0.11021304130554199,0.674931526184082,0.33333900570869446,0.6587204337120056,-0.12185424566268921,0.6634185314178467,0.3327528238296509,0.6693615317344666,-0.13421590626239777,0.6505512595176697,0.3325183689594269,0.6785537004470825,-0.14622485637664795,0.6384471654891968,0.33298423886299133,0.6869981288909912,-0.1570206880569458,0.6264978647232056,0.33465611934661865,0.6955614686012268,-0.16542784869670868,0.613867461681366,0.33814477920532227,0.7028008103370667,-0.1715613603591919,0.6019101738929749,0.34407156705856323,0.7092738151550293,-0.17427726089954376,0.5900617837905884,0.3508831858634949,0.7133764028549194,-0.17333418130874634,0.5813178420066833,0.3583015501499176,0.7147830724716187,-0.17014086246490479,0.5759837031364441,0.36631786823272705,0.7113643884658813,-0.16414672136306763,0.5769123435020447,0.3724993169307709,0.6995517015457153,-0.15274034440517426,0.5903745889663696,0.37854447960853577,0.6730685234069824,-0.13379256427288055,0.6211138367652893,0.3832840919494629,0.6276673674583435,-0.10481910407543182,0.6694326400756836,0.38441893458366394,0.5578821897506714,-0.06518411636352539,0.7326259016990662,0.3847902715206146,0.4649530351161957,-0.018740719184279442,0.7971222996711731,0.383175253868103,0.3681717813014984,0.024046171456575394,0.8467869162559509,0.3717508912086487,0.2863965630531311,0.0575435496866703,0.8811736702919006,0.3557255268096924,0.2192881554365158,0.09173698723316193,0.9038563966751099,0.3425661623477936,0.16064569354057312,0.12573491036891937,0.9170780777931213,0.3301829993724823,0.10870600491762161,0.15477052330970764,0.924774706363678,0.3182888925075531,0.05684233456850052,0.1828048974275589,0.9284629821777344,0.3042270541191101,0.011851311661303043,0.20484237372875214,0.9302392601966858,0.28582391142845154,-0.01658944971859455,0.20984894037246704,0.9348759055137634,0.2626478374004364,-0.040036268532276154,0.20021651685237885,0.9430411458015442,0.23828676342964172,-0.0698380172252655,0.19141049683094025,0.9495810270309448,0.22130966186523438,-0.10479765385389328,0.1842237263917923,0.9518934488296509,0.21145515143871307,-0.15072445571422577,0.18014304339885712,0.9487451910972595,0.19955721497535706,-0.21265313029289246,0.18448923528194427,0.9385730028152466,0.18634355068206787,-0.26964664459228516,0.18905769288539886,0.9256478548049927,0.177153542637825,-0.30125123262405396,0.18634766340255737,0.9182259440422058,0.17143477499485016,-0.32314738631248474,0.1892978847026825,0.9112366437911987,0.16652894020080566,-0.3432369530200958,0.19639591872692108,0.9032636284828186,0.16352632641792297,-0.35949626564979553,0.20560142397880554,0.8954047560691833,0.162473663687706,-0.3712584972381592,0.2149324268102646,0.8885794878005981,0.16388066112995148,-0.3782162070274353,0.22284914553165436,0.8834217190742493,0.16955578327178955,-0.3850260376930237,0.22588014602661133,0.8786261677742004,0.1745200902223587,-0.3926128149032593,0.2288699448108673,0.8735081553459167,0.17921262979507446,-0.40080171823501587,0.2315385341644287,0.8681190609931946,0.18385067582130432,-0.40884101390838623,0.23344293236732483,0.8628745079040527,0.18863925337791443,-0.41666868329048157,0.23495908081531525,0.8576692938804626,0.1933743804693222,-0.4250278174877167,0.23640789091587067,0.8520968556404114,0.1978210061788559,-0.4331478774547577,0.23742304742336273,0.8466877341270447,0.20240084826946259,-0.43972375988960266,0.23812824487686157,0.8420047163963318,0.2065591812133789,-0.4448111355304718,0.2388163059949875,0.838118851184845,0.2098887711763382,-0.4491855502128601,0.23978838324546814,0.8346739411354065,0.21283946931362152,-0.4531160593032837,0.2409885972738266,0.8314504027366638,0.21548058092594147,-0.4566778540611267,0.2422463744878769,0.8284505009651184,0.21763521432876587,-0.4600262939929962,0.2437293529510498,0.8255948424339294,0.21947477757930756,-0.4634045362472534,0.2454824447631836,0.8226940631866455,0.22107605636119843,-0.46684199571609497,0.24726076424121857,0.8197841644287109,0.22255544364452362,-0.4698929786682129,0.24881307780742645,0.8171669244766235,0.22401519119739532,-0.4723410904407501,0.25020119547843933,0.8149297833442688,0.22552603483200073,-0.47453776001930237,0.25142401456832886,0.8128578662872314,0.22688676416873932,-0.47665923833847046,0.2525976300239563,0.8108716607093811,0.22803732752799988,-0.47863566875457764,0.25370413064956665,0.8090371489524841,0.22897203266620636,-0.4804127514362335,0.2546771168708801,0.8074125647544861,0.2297361195087433,-0.48217183351516724,0.2554592490196228,0.8058984279632568,0.23025231063365936,-0.4838968515396118,0.25624987483024597,0.8044648766517639,0.23057395219802856,-0.4855045974254608,0.25707072019577026,0.8031411170959473,0.23074521124362946,-0.4869242012500763,0.25791147351264954,0.80196213722229,0.23081502318382263,-0.4881804585456848,0.25874191522598267,0.8009100556373596,0.23083637654781342,-0.4893280267715454,0.2595810890197754,0.7999314665794373,0.23082461953163147,-0.4903813898563385,0.26044198870658875,0.7990095019340515,0.2307906150817871,-0.49143892526626587,0.26130491495132446,0.7980872392654419,0.23075704276561737,-0.4925403594970703,0.2621663510799408,0.7971349358558655,0.230768084526062,-0.4936823546886444,0.26303428411483765,0.7961387038230896,0.23084235191345215,-0.49493324756622314,0.2639302611351013,0.7950432896614075,0.23098354041576385,-0.4963158667087555,0.2648632824420929,0.793829083442688,0.23087649047374725,-0.5010441541671753,0.2656020224094391,0.7906367182731628],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998807907104,0.9999998211860657,0.9999997019767761,0.9999997019767761,0.9999998211860657,0.9999997615814209],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,5.4666666984558105],"values":[8.305602073669434,-6.7116007804870605,5.719300270080566,8.305598258972168,-6.71159553527832,5.719296455383301],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.8224596381187439,0.08969991654157639,-0.35752636194229126,0.43323075771331787,0.8310777544975281,0.0896136611700058,-0.32407712936401367,0.44300466775894165,0.8328028917312622,0.08747918903827667,-0.3030652403831482,0.45490455627441406,0.8329141736030579,0.0854395255446434,-0.28120434284210205,0.46891167759895325,0.8319082260131836,0.08288605511188507,-0.2588982582092285,0.4837668538093567,0.8306654691696167,0.08008792251348495,-0.23677705228328705,0.4975112974643707,0.8300366401672363,0.0777171179652214,-0.2156669646501541,0.5084161162376404,0.8298109769821167,0.07515321671962738,-0.196450337767601,0.5168878436088562,0.8297012448310852,0.07151802629232407,-0.18090908229351044,0.5232140421867371,0.8305904269218445,0.06733561307191849,-0.1676633208990097,0.5267584323883057,0.8321067094802856,0.05806281045079231,-0.15842825174331665,0.5283253788948059,0.8384909629821777,0.04153311997652054,-0.15043620765209198,0.5220889449119568,0.8492217063903809,0.02134987711906433,-0.1428368091583252,0.5079019069671631,0.861434817314148,-0.0006707414286211133,-0.13536246120929718,0.4894963502883911,0.8700131773948669,-0.01829143799841404,-0.13101798295974731,0.4749491810798645,0.8604015707969666,-0.028811603784561157,-0.1293438822031021,0.49208664894104004,0.8516913056373596,-0.04069381207227707,-0.1281762719154358,0.5064947009086609,0.8446366190910339,-0.05098439007997513,-0.1287689507007599,0.5171152949333191,0.8396279811859131,-0.05819648131728172,-0.1328096091747284,0.5234497785568237,0.8363706469535828,-0.0629422590136528,-0.13930261135101318,0.5264192223548889,0.8318487405776978,-0.06592946499586105,-0.14423462748527527,0.531862199306488,0.8248157501220703,-0.06683380901813507,-0.1467038094997406,0.5419319272041321,0.8183436989784241,-0.06806560605764389,-0.14980559051036835,0.5506713390350342,0.8130934238433838,-0.06924675405025482,-0.15334175527095795,0.5572883486747742,0.8095676898956299,-0.07118355482816696,-0.15667933225631714,0.5612349510192871,0.8078657984733582,-0.0737270787358284,-0.1599450558423996,0.5624364614486694,0.8063310980796814,-0.07640421390533447,-0.16306354105472565,0.5633852481842041,0.8041583895683289,-0.07854751497507095,-0.16671399772167206,0.5651248097419739,0.8021177053451538,-0.0802963525056839,-0.17226627469062805,0.5661131143569946,0.8040658235549927,-0.0822998508810997,-0.17508892714977264,0.5621821880340576,0.8080673813819885,-0.08452421426773071,-0.1747576743364334,0.5561857223510742,0.8100462555885315,-0.08535898476839066,-0.1719527244567871,0.5540498495101929,0.8120381832122803,-0.08537299931049347,-0.16561858355998993,0.5530605316162109,0.8139438033103943,-0.08337744325399399,-0.159250408411026,0.5524337291717529,0.8127801418304443,-0.07979880273342133,-0.1536056250333786,0.5562606453895569,0.8077440857887268,-0.07771622389554977,-0.14836512506008148,0.5652411580085754,0.8016713857650757,-0.07083479315042496,-0.14533035457134247,0.5754863619804382,0.7952979803085327,-0.060770511627197266,-0.14331747591495514,0.5858909487724304,0.7837812900543213,-0.05087313801050186,-0.1439969390630722,0.6019666194915771,0.7729981541633606,-0.04893353208899498,-0.14519809186458588,0.6156272292137146,0.7629973292350769,-0.043241314589977264,-0.15099282562732697,0.6270298957824707,0.7440032958984375,-0.04022781178355217,-0.15627145767211914,0.6483981609344482,0.732150673866272,-0.038351405411958694,-0.16391782462596893,0.6600118279457092,0.7273117899894714,-0.03856772556900978,-0.17020776867866516,0.6637465953826904,0.7294946908950806,-0.0431547537446022,-0.1716153621673584,0.6606992483139038,0.7350877523422241,-0.05184432864189148,-0.17082811892032623,0.6540459394454956,0.7440102696418762,-0.059948865324258804,-0.1704523116350174,0.643273651599884,0.7543838620185852,-0.06570757925510406,-0.1687835156917572,0.6309514045715332,0.768011212348938,-0.06732270866632462,-0.16424958407878876,0.615344226360321,0.7802002429962158,-0.058943696320056915,-0.15917862951755524,0.6020593643188477,0.7887870669364929,-0.045762304216623306,-0.15748059749603271,0.5923855304718018,0.7939454913139343,-0.02920505777001381,-0.17016001045703888,0.5829607844352722,0.7979031801223755,-0.004128108732402325,-0.20607776939868927,0.5664499402046204,0.7998272776603699,0.02530013583600521,-0.2566310465335846,0.5420117378234863,0.801735520362854,0.052292630076408386,-0.30278280377388,0.5126481652259827,0.8020837903022766,0.06721766293048859,-0.3286300599575043,0.49411091208457947,0.8043704032897949,0.06904592365026474,-0.33831748366355896,0.4834894835948944,0.8103166818618774,0.05833902582526207,-0.3413040041923523,0.47275254130363464,0.8174825310707092,0.04641274735331535,-0.3417114317417145,0.46130403876304626,0.83088219165802,0.026521021500229836,-0.32525429129600525,0.45071175694465637,0.8448752760887146,0.004639901220798492,-0.290290504693985,0.4493280351161957,0.8590878248214722,-0.016566801816225052,-0.24460451304912567,0.4492909014225006,0.873721182346344,-0.024892471730709076,-0.20604175329208374,0.4399300217628479,0.8823026418685913,-0.028210531920194626,-0.18710148334503174,0.4309748411178589,0.8895441293716431,-0.03310411050915718,-0.17512795329093933,0.4206489622592926,0.898431122303009,-0.03765266388654709,-0.1643364280462265,0.4054594933986664,0.909690797328949,-0.04269785434007645,-0.16118933260440826,0.38033878803253174,0.919959545135498,-0.04557615891098976,-0.1645880788564682,0.35285699367523193,0.927521824836731,-0.04711505398154259,-0.16923117637634277,0.3299155831336975,0.93425053358078,-0.0481303445994854,-0.16811297833919525,0.3108013868331909,0.9396355748176575,-0.04901479184627533,-0.16557854413986206,0.29540878534317017,0.942937433719635,-0.0471976138651371,-0.16552072763442993,0.2850339710712433,0.9457336664199829,-0.04454554617404938,-0.1647499054670334,0.27651581168174744,0.9484127759933472,-0.04247552156448364,-0.16234438121318817,0.2689857482910156,0.949776291847229,-0.03866996616125107,-0.15895992517471313,0.26676103472709656,0.9490493535995483,-0.03063332661986351,-0.15655356645584106,0.2717680335044861,0.9456303119659424,-0.018575116991996765,-0.15588349103927612,0.28484854102134705,0.9391034841537476,-0.0036638753954321146,-0.15143807232379913,0.308444082736969,0.9274722933769226,0.011626497842371464,-0.1364452838897705,0.34791192412376404,0.9106569886207581,0.02651618793606758,-0.11712291091680527,0.3953264057636261,0.8878555297851562,0.04341605305671692,-0.10015611350536346,0.4469859302043915,0.862203061580658,0.056523386389017105,-0.08758212625980377,0.4957220256328583,0.840825080871582,0.06224605813622475,-0.09763818979263306,0.5287773013114929,0.83107590675354,0.0581723153591156,-0.11451300978660583,0.5411244034767151,0.8228145837783813,0.047906022518873215,-0.10953664034605026,0.5555923581123352,0.8175964951515198,0.03724339231848717,-0.09136010706424713,0.5672762393951416,0.8197917938232422,0.0278105977922678,-0.07923214137554169,0.5664718151092529,0.8281517624855042,0.01910623349249363,-0.0796692743897438,0.5544840097427368,0.8380104303359985,0.009535497054457664,-0.08404622972011566,0.539058268070221,0.8455389738082886,-0.00010925326205324382,-0.07510785758495331,0.5286045074462891,0.852444589138031,-0.006785131990909576,-0.053379133343696594,0.520041286945343,0.8572808504104614,-0.007516882847994566,-0.03954252973198891,0.513273298740387,0.857537567615509,-0.007498134393244982,-0.0374683141708374,0.5130003094673157,0.8553687930107117,-0.00768449017778039,-0.03832079470157623,0.5165430307388306,0.8529716730117798,-0.006039882078766823,-0.040521733462810516,0.5203468799591064,0.8498889207839966,-0.003092308295890689,-0.04473620653152466,0.5250504612922668,0.8468834757804871,0.0006163432262837887,-0.05308709293603897,0.5291217565536499,0.846307635307312,0.004397660493850708,-0.06802526861429214,0.5283149480819702,0.8472307920455933,0.007217006292194128,-0.08438126742839813,0.5244309306144714,0.8460432291030884,0.008888470008969307,-0.09550288319587708,0.5244150161743164,0.8438769578933716,0.009332003071904182,-0.10248617082834244,0.5265750288963318,0.8450822830200195,0.007863577455282211,-0.10548687726259232,0.5240674614906311,0.8488776087760925,0.006152361165732145,-0.1074812263250351,0.517510175704956,0.8527803421020508,0.007163436152040958,-0.1134621873497963,0.5097458362579346,0.8575217723846436,0.007555481046438217,-0.12352704256772995,0.49934008717536926,0.8652132153511047,0.013596003875136375,-0.13613681495189667,0.48237746953964233,0.8784855604171753,0.02700941078364849,-0.13956786692142487,0.45612984895706177,0.9023336172103882,0.04486294835805893,-0.12420499324798584,0.41031014919281006,0.9275505542755127,0.058509450405836105,-0.10105713456869125,0.3549846410751343,0.9438765645027161,0.05600167065858841,-0.10160283744335175,0.3092535436153412,0.9330300092697144,0.03980366513133049,-0.1113264411687851,0.3398192822933197,0.9079047441482544,0.005217231344431639,-0.12163063138723373,0.4011082053184509,0.8687129616737366,-0.027386605739593506,-0.13615019619464874,0.4754481017589569,0.8124977350234985,-0.05774594843387604,-0.15844865143299103,0.5580384135246277,0.7611783742904663,-0.0899408832192421,-0.17324145138263702,0.6184704303741455,0.7358019948005676,-0.10589301586151123,-0.18128731846809387,0.6438300013542175,0.7380065321922302,-0.10853985697031021,-0.17927299439907074,0.6414255499839783,0.7695547938346863,-0.10264800488948822,-0.15659017860889435,0.6105148792266846,0.7964792251586914,-0.08530568331480026,-0.12706692516803741,0.5849767923355103,0.8111500144004822,-0.06234627217054367,-0.10920582711696625,0.5711590647697449,0.8254250288009644,-0.05363243445754051,-0.09346640855073929,0.5541309714317322,0.8356491923332214,-0.044256918132305145,-0.10130871087312698,0.5380226373672485,0.8396304249763489,-0.041925713419914246,-0.11906147003173828,0.5282872319221497,0.8535456657409668,-0.03180311247706413,-0.14328716695308685,0.4999171793460846,0.8718789219856262,-0.032465167343616486,-0.14785583317279816,0.4657379686832428,0.8896026015281677,-0.043342698365449905,-0.1417478621006012,0.4320141673088074,0.9047271609306335,-0.05937202647328377,-0.13286331295967102,0.4003637433052063,0.9182884097099304,-0.07575012743473053,-0.11967962980270386,0.3697095215320587,0.9279965758323669,-0.09062177687883377,-0.1057727038860321,0.345575213432312,0.9335967898368835,-0.09976879507303238,-0.0966421440243721,0.33030828833580017,0.9359928965568542,-0.0960666760802269,-0.09487425535917282,0.3250959813594818,0.9363747239112854,-0.09024614840745926,-0.09680576622486115,0.3250948488712311,0.9352615475654602,-0.0846061035990715,-0.1019635796546936,0.3282241225242615,0.9341019988059998,-0.07895398139953613,-0.11023621261119843,0.3302539885044098,0.9329212307929993,-0.07509390264749527,-0.11688509583473206,0.33219993114471436,0.9338347911834717,-0.07060632109642029,-0.12531812489032745,0.32750990986824036,0.9346129894256592,-0.06100519746541977,-0.1407628208398819,0.32087814807891846,0.9339927434921265,-0.05172087252140045,-0.1604604423046112,0.3150157928466797,0.9303224682807922,-0.042161229997873306,-0.18078872561454773,0.3162878751754761,0.9174647927284241,-0.026401488110423088,-0.2030249834060669,0.34108978509902954,0.9030573964118958,-0.007808438036590815,-0.22626079618930817,0.36501017212867737,0.8867005705833435,0.011071481741964817,-0.2505400478839874,0.38841888308525085,0.869003176689148,0.025528835132718086,-0.2745983302593231,0.41082537174224854,0.8509331345558167,0.03876226395368576,-0.29827627539634705,0.43062925338745117,0.8319453597068787,0.05430690199136734,-0.3227366507053375,0.44806107878685,0.8129132986068726,0.06864403933286667,-0.3446092903614044,0.46443983912467957,0.7952733039855957,0.08094228804111481,-0.3615879416465759,0.47983628511428833,0.779776394367218,0.09327209740877151,-0.3756231367588043,0.4920938014984131,0.7669250965118408,0.10564624518156052,-0.3884812593460083,0.49974700808525085,0.7570653557777405,0.11693388223648071,-0.39818456768989563,0.5046062469482422,0.751988410949707,0.1253938227891922,-0.40136194229125977,0.50764000415802,0.7506238222122192,0.1313895285129547,-0.40106669068336487,0.508375883102417,0.7524075508117676,0.13441181182861328,-0.3996284306049347,0.506076455116272,0.7569791674613953,0.13488256931304932,-0.39663952589035034,0.5014640092849731,0.763635516166687,0.1331644207239151,-0.39233267307281494,0.49517977237701416,0.7717105746269226,0.12976261973381042,-0.38705649971961975,0.48765939474105835,0.7805649042129517,0.1251058280467987,-0.3812026083469391,0.47932398319244385,0.789638340473175,0.11965522915124893,-0.37510406970977783,0.47058558464050293,0.7986480593681335,0.114101842045784,-0.369284063577652,0.46127134561538696,0.8069760799407959,0.10780000686645508,-0.3637828230857849,0.45258238911628723,0.8140602111816406,0.10092361271381378,-0.3588457405567169,0.44536513090133667,0.8194556832313538,0.0947568416595459,-0.35520005226135254,0.43971171975135803,0.8226296305656433,0.0901806578040123,-0.3534068167209625,0.4361782371997833,0.8224594593048096,0.08969999104738235,-0.35752642154693604,0.43323108553886414],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,1.0000001192092896,0.9999992847442627,0.9999997615814209,1.0000001192092896,0.9999992251396179],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,5.4666666984558105],"values":[-6.609118372580269e-7,25.812705993652344,-5.998030019327416e-7,0.0000030368473744601943,25.812700271606445,-0.0000070801793299324345],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.25263047218322754,0.0797063335776329,0.08682072907686234,0.9603577256202698,0.2530601918697357,0.07212654501199722,0.0836217850446701,0.9611273407936096,0.2592679560184479,0.07201637327671051,0.08381709456443787,0.959462583065033,0.26966115832328796,0.0719013512134552,0.0832415372133255,0.9566524624824524,0.28187549114227295,0.07212615013122559,0.08125370740890503,0.9532794952392578,0.2933315932750702,0.07326176762580872,0.07794320583343506,0.9500074982643127,0.30182164907455444,0.0759732574224472,0.07391873002052307,0.9474533200263977,0.30673372745513916,0.08053860068321228,0.07017791271209717,0.9457817077636719,0.3072609305381775,0.08789386600255966,0.06797949969768524,0.9451159834861755,0.30186164379119873,0.0963730663061142,0.06730568408966064,0.9460770487785339,0.2977108955383301,0.08773546665906906,0.06080513820052147,0.9486693143844604,0.28421518206596375,0.047623664140701294,0.044608768075704575,0.9565374255180359,0.25393345952033997,-0.007743805646896362,0.025665564462542534,0.9668501019477844,0.2245868444442749,-0.05782118812203407,0.010229090228676796,0.972683310508728,0.2244039624929428,-0.07988917827606201,0.0031410467345267534,0.9712109565734863,0.30229488015174866,-0.06874189525842667,-0.0034928591921925545,0.9507261514663696,0.3669399619102478,-0.05550378933548927,-0.007506095338612795,0.9285569787025452,0.4167769253253937,-0.04368861764669418,-0.00935431756079197,0.907910168170929,0.4526468813419342,-0.0311387050896883,-0.004664890933781862,0.8911337852478027,0.4838305115699768,-0.013140534050762653,0.004935713019222021,0.8750491738319397,0.5100980997085571,0.010475982911884785,0.01747981831431389,0.8598747253417969,0.5286832451820374,0.03515073284506798,0.03170474246144295,0.8474982380867004,0.542995035648346,0.05812425538897514,0.045711200684309006,0.836473822593689,0.5481909513473511,0.07516336441040039,0.0571337454020977,0.831007182598114,0.5519465804100037,0.0761493369936943,0.05835883319377899,0.8283420205116272,0.5597589015960693,0.06581878662109375,0.05211932212114334,0.8243915438652039,0.5658280253410339,0.0519951730966568,0.043412428349256516,0.8217363357543945,0.5655004382133484,0.04174893721938133,0.038278691470623016,0.8228006958961487,0.5591151118278503,0.038052964955568314,0.0389014333486557,0.8273022174835205,0.5417176485061646,0.03566717728972435,0.04123485088348389,0.8387905359268188,0.5162535905838013,0.030816053971648216,0.04197405278682709,0.8548513650894165,0.48118382692337036,0.024527305737137794,0.04197274520993233,0.8752707242965698,0.4363200068473816,0.013771891593933105,0.04003497585654259,0.8987950086593628,0.3811543881893158,0.006463725119829178,0.04194600507616997,0.9235367178916931,0.3289206624031067,0.004954247269779444,0.045207854360342026,0.9432618618011475,0.27726972103118896,0.016534939408302307,0.0403829850256443,0.9598007202148438,0.22921393811702728,0.04342145845293999,0.04169055446982384,0.9715129733085632,0.20548602938652039,0.07051647454500198,0.04713095724582672,0.9749777317047119,0.19297973811626434,0.07988432049751282,0.04496026411652565,0.9769114255905151,0.18479251861572266,0.06825805455446243,0.04284115135669708,0.9794678688049316,0.18184779584407806,0.05692693591117859,0.05263141542673111,0.9802656173706055,0.2189374417066574,0.04830837994813919,0.06468567252159119,0.9723932147026062,0.23776540160179138,0.03865039348602295,0.07711659371852875,0.9674847722053528,0.23920457065105438,0.028731925413012505,0.08878099173307419,0.9664748311042786,0.22761815786361694,0.020762702450156212,0.09202840924263,0.9691696166992188,0.21312934160232544,0.014133070595562458,0.08866402506828308,0.9728899598121643,0.19614997506141663,0.008267540484666824,0.08312545716762543,0.9770092368125916,0.18074196577072144,0.003217145102098584,0.07611824572086334,0.9805753231048584,0.16555671393871307,-0.0018064472824335098,0.07167382538318634,0.9835906624794006,0.15869788825511932,-0.006417868193238974,0.07004247605800629,0.9848187565803528,0.15983685851097107,-0.009173722006380558,0.06865768879652023,0.9847102165222168,0.17138409614562988,-0.010221224278211594,0.07099373638629913,0.9825899004936218,0.19346146285533905,-0.01150511670857668,0.08520711213350296,0.9773331880569458,0.21912255883216858,-0.01618437096476555,0.1170930489897728,0.9685105085372925,0.23691187798976898,-0.026456251740455627,0.16102644801139832,0.9577282071113586,0.2536616921424866,-0.03699682652950287,0.18572694063186646,0.9485739469528198,0.27642226219177246,-0.06770703196525574,0.18018506467342377,0.9415624141693115,0.3079591691493988,-0.13303957879543304,0.1513463407754898,0.9298149943351746,0.3405636250972748,-0.20305520296096802,0.12606707215309143,0.9093360900878906,0.34574374556541443,-0.2387119084596634,0.11018912494182587,0.9007420539855957,0.34395667910575867,-0.25570985674858093,0.1021871343255043,0.8976993560791016,0.3372170627117157,-0.26202142238616943,0.09988932311534882,0.8986943960189819,0.3240192234516144,-0.2530745565891266,0.10450097173452377,0.9055630564689636,0.3298988342285156,-0.2397090196609497,0.11066195368766785,0.906344473361969,0.34247976541519165,-0.22613652050495148,0.11272094398736954,0.9049109816551208,0.3465673625469208,-0.2127343863248825,0.11533079296350479,0.9062747955322266,0.33468151092529297,-0.20065422356128693,0.11932599544525146,0.9129553437232971,0.31571024656295776,-0.18986748158931732,0.1238090991973877,0.9213841557502747,0.299639493227005,-0.17885585129261017,0.12642817199230194,0.9285702705383301,0.28749632835388184,-0.16655348241329193,0.12550018727779388,0.9348024129867554,0.2791275978088379,-0.1492060273885727,0.12120769917964935,0.9408156275749207,0.27742236852645874,-0.12491185963153839,0.11672509461641312,0.9454147815704346,0.2788555920124054,-0.09845537692308426,0.11171115189790726,0.9487184882164001,0.28155195713043213,-0.0701950192451477,0.10550900548696518,0.9511408805847168,0.29214179515838623,-0.03867591544985771,0.09896863996982574,0.9504538774490356,0.31429436802864075,-0.0011074839858338237,0.0945964902639389,0.9446001052856445,0.3429320156574249,0.04303864762187004,0.09091082960367203,0.9339596629142761,0.3752046525478363,0.08781012892723083,0.08560021966695786,0.918794572353363,0.41821613907814026,0.13408510386943817,0.0781106948852539,0.8949944972991943,0.4632715582847595,0.1781844198703766,0.07248789072036743,0.8650869131088257,0.5114087462425232,0.20903059840202332,0.07061372697353363,0.83053058385849,0.550870954990387,0.20786577463150024,0.0653294175863266,0.805645763874054,0.5806611776351929,0.18503451347351074,0.05422435328364372,0.790983259677887,0.587901771068573,0.15342262387275696,0.029992567375302315,0.7936834692955017,0.5833264589309692,0.12517791986465454,0.00391248008236289,0.8025244474411011,0.5641961097717285,0.10566549748182297,-0.014207085594534874,0.8187281489372253,0.5352590084075928,0.09957123547792435,-0.0201155636459589,0.8385575413703918,0.5042410492897034,0.1059807762503624,-0.016800886020064354,0.8568703532218933,0.4739769995212555,0.1174163892865181,-0.011345630511641502,0.8725998401641846,0.4402996301651001,0.12980502843856812,-0.005629762541502714,0.8884003758430481,0.4049784541130066,0.14305302500724792,0.0005387436831369996,0.9030659198760986,0.38408035039901733,0.15826961398124695,0.004637161269783974,0.9096216559410095,0.3807178735733032,0.17328616976737976,0.005200913175940514,0.9082944393157959,0.38434943556785583,0.18762071430683136,0.0048963818699121475,0.9039081931114197,0.38462379574775696,0.20322224497795105,0.006424608640372753,0.9004021286964417,0.3803887665271759,0.2210904210805893,0.010372272692620754,0.8979508876800537,0.37030208110809326,0.24288307130336761,0.017554996535182,0.8964239954948425,0.34913384914398193,0.26874029636383057,0.02819041535258293,0.8972678184509277,0.32217976450920105,0.29502350091934204,0.039281923323869705,0.8986758589744568,0.30436092615127563,0.31700795888900757,0.0474809855222702,0.8970038890838623,0.2902924120426178,0.33359038829803467,0.05330540984869003,0.89532470703125,0.2661760449409485,0.3451819121837616,0.0577378049492836,0.8981459140777588,0.23930178582668304,0.35185277462005615,0.060149919241666794,0.9029486179351807,0.2120267003774643,0.3561176061630249,0.06931816786527634,0.9074249267578125,0.18844525516033173,0.35723307728767395,0.07450488209724426,0.9117686152458191,0.17446012794971466,0.3476235270500183,0.0768374353647232,0.9180509448051453,0.17780429124832153,0.30943208932876587,0.06996894627809525,0.9315266013145447,0.1880626082420349,0.23025955259799957,0.04958215728402138,0.9534960389137268,0.21767543256282806,0.1375228464603424,0.0167939942330122,0.9661380648612976,0.2717404067516327,0.07170724868774414,-0.009392766281962395,0.959649384021759,0.4033987522125244,0.026270104572176933,-0.049504686146974564,0.9133064150810242,0.47928619384765625,0.050200045108795166,-0.04273694381117821,0.8751789927482605,0.5785667300224304,0.08684680610895157,-0.01733551174402237,0.8108129501342773,0.6538528203964233,0.08128959685564041,-0.017270099371671677,0.7520440816879272,0.6539748907089233,0.061001941561698914,-0.03501806780695915,0.7532392740249634,0.5840151309967041,0.07673053443431854,-0.01959405280649662,0.8078705668449402,0.4383298456668854,0.14752447605133057,0.01424187608063221,0.8865103721618652,0.2180657684803009,0.21669046580791473,0.032337386161088943,0.9510241150856018,0.06756173074245453,0.24883024394512177,0.020524505525827408,0.9659698605537415,0.14167863130569458,0.22300460934638977,0.012467525899410248,0.9643861651420593,0.21784736216068268,0.17856016755104065,-0.004482350777834654,0.9594992399215698,0.280642569065094,0.16580034792423248,-0.015106185339391232,0.9452627897262573,0.39248690009117126,0.1506349742412567,-0.012500978074967861,0.9072523713111877,0.43377363681793213,0.12004905194044113,-0.001869925414212048,0.8929866552352905,0.446015328168869,0.09574427455663681,-0.009615199640393257,0.8898375630378723,0.4410828649997711,0.0681677758693695,-0.021312236785888672,0.8946199417114258,0.428801029920578,0.03553156182169914,-0.03037279099225998,0.902188777923584,0.40180307626724243,0.005466309376060963,-0.036645594984292984,0.9149762392044067,0.3705097436904907,-0.016736047342419624,-0.04164370894432068,0.927743673324585,0.34244993329048157,-0.030374130234122276,-0.04052457585930824,0.9381701350212097,0.32370245456695557,-0.03276459872722626,-0.03297567367553711,0.9450163245201111,0.31255078315734863,-0.028022704645991325,-0.02517545223236084,0.9491538405418396,0.30853936076164246,-0.024909034371376038,-0.018734807148575783,0.9507008194923401,0.3047800064086914,-0.02475476637482643,-0.013044715858995914,0.9520117044448853,0.2995051145553589,-0.028476811945438385,-0.013156960718333721,0.9535788893699646,0.2837729752063751,-0.03241109102964401,-0.008876491338014603,0.9583024978637695,0.26495715975761414,-0.02747303619980812,0.008196241222321987,0.9638338685035706,0.2439323514699936,-0.01587347872555256,0.02443222887814045,0.969354510307312,0.2339782863855362,-0.0012491458328440785,0.03711685538291931,0.9715322852134705,0.2662138342857361,0.014323394745588303,0.05124358460307121,0.9624444246292114,0.2949794828891754,0.028692498803138733,0.06921931356191635,0.9525610208511353,0.3202846944332123,0.04430820047855377,0.08577317744493484,0.9423892498016357,0.34310466051101685,0.060675136744976044,0.09622791409492493,0.9323829412460327,0.36106184124946594,0.07639804482460022,0.10689748823642731,0.9232391715049744,0.37506914138793945,0.09159620106220245,0.12290474772453308,0.9142361283302307,0.3879609704017639,0.10556275397539139,0.13649490475654602,0.9053794741630554,0.40016308426856995,0.11647538095712662,0.14519014954566956,0.8973420858383179,0.4082598388195038,0.12528343498706818,0.15385210514068604,0.891042947769165,0.4111779034137726,0.13235846161842346,0.164036825299263,0.8868517279624939,0.41159260272979736,0.13712818920612335,0.172258660197258,0.8843722939491272,0.4094676673412323,0.1381251960992813,0.1733047068119049,0.8849989771842957,0.4054088890552521,0.1369912028312683,0.17054620385169983,0.8875759243965149,0.39793136715888977,0.13499701023101807,0.1656685322523117,0.8921773433685303,0.387581467628479,0.13176342844963074,0.15898236632347107,0.898411750793457,0.37495893239974976,0.12730906903743744,0.15102316439151764,0.9057539701461792,0.35985031723976135,0.12195316702127457,0.142421692609787,0.9139755368232727,0.34241992235183716,0.11541664600372314,0.13359631597995758,0.9228107333183289,0.3235541582107544,0.10806570947170258,0.12527425587177277,0.931633472442627,0.30359014868736267,0.1005333885550499,0.11819446086883545,0.9400830864906311,0.2848389744758606,0.09350399672985077,0.110109843313694,0.9476284384727478,0.26911062002182007,0.0875902846455574,0.1008216068148613,0.9538041949272156,0.25737348198890686,0.08305483311414719,0.09276743233203888,0.958256185054779,0.25175347924232483,0.08019112050533295,0.08755845576524734,0.9604806900024414,0.25263041257858276,0.07970623672008514,0.08682049065828323,0.9603577256202698],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999999403953552,1.0000001192092896,1,0.9999999403953552,1,1],"interpolation":2300,"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,5.4666666984558105],"values":[0.000003227581601095153,22.742704391479492,9.182267604046501e-7,-1.3116718378114456e-7,22.742712020874023,0.0000074516965469229035],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[-0.47721678018569946,-0.09428819268941879,0.12424730509519577,0.8648332357406616,-0.4754442572593689,-0.0995573028922081,0.12230125069618225,0.8654960989952087,-0.4744723439216614,-0.09761249274015427,0.11918457597494125,0.8666849732398987,-0.47490599751472473,-0.09591061621904373,0.11660326272249222,0.8669885396957397,-0.4758383631706238,-0.0947485864162445,0.11505232006311417,0.8668122887611389,-0.47672781348228455,-0.09454114735126495,0.11516791582107544,0.8663307428359985,-0.47717389464378357,-0.09555062651634216,0.11733075976371765,0.865683913230896,-0.47633224725723267,-0.09699111431837082,0.12011758238077164,0.8656050562858582,-0.47323399782180786,-0.09785572439432144,0.12188591808080673,0.8669589161872864,-0.46717122197151184,-0.09826875478029251,0.12273429334163666,0.8700750470161438,-0.45671409368515015,-0.09698767960071564,0.12003874033689499,0.8761257529258728,-0.4357776939868927,-0.09276601672172546,0.1109885424375534,0.8883545994758606,-0.40612003207206726,-0.08521143347024918,0.09571568667888641,0.9047895073890686,-0.38685253262519836,-0.07490935176610947,0.0773991048336029,0.9158291816711426,-0.39039385318756104,-0.06814064830541611,0.06994558125734329,0.9154545664787292,-0.4206697344779968,-0.06473852694034576,0.06689661741256714,0.9024249315261841,-0.42469093203544617,-0.0500870905816555,0.0423564612865448,0.9029589891433716,-0.4081706404685974,-0.034861985594034195,0.016379356384277344,0.9120926856994629,-0.3858620822429657,-0.024208523333072662,-0.001979926833882928,0.9222366809844971,-0.36515066027641296,-0.022759629413485527,-0.005243134219199419,0.9306554198265076,-0.3362559378147125,-0.02626035176217556,0.00027610803954303265,0.941404402256012,-0.3083241283893585,-0.03257588669657707,0.011415185406804085,0.950654923915863,-0.30391648411750793,-0.03885315731167793,0.02293483540415764,0.9516297578811646,-0.30185723304748535,-0.04402540624141693,0.03222976624965668,0.9517905712127686,-0.3069293200969696,-0.044022511690855026,0.032746780663728714,0.9501495361328125,-0.3287179470062256,-0.041185371577739716,0.028690222650766373,0.9430934190750122,-0.3515167534351349,-0.037693798542022705,0.02333364635705948,0.9351313710212708,-0.36249926686286926,-0.03443580120801926,0.017960114404559135,0.9311745166778564,-0.3680507242679596,-0.030240356922149658,0.011010914109647274,0.9292486310005188,-0.37872663140296936,-0.025434475392103195,0.0033207787200808525,0.9251530766487122,-0.39570724964141846,-0.02152305096387863,-0.0026236388366669416,0.9181207418441772,-0.40612277388572693,-0.01750725321471691,-0.009116395376622677,0.9136053323745728,-0.41939157247543335,-0.014798646792769432,-0.013411794789135456,0.9075857400894165,-0.43374696373939514,-0.012477291747927666,-0.01706262305378914,0.9007867574691772,-0.4488256573677063,-0.012289411388337612,-0.01706094853579998,0.8933719396591187,-0.46042585372924805,-0.012370983138680458,-0.016522984951734543,0.8874581456184387,-0.4795132875442505,-0.013706940226256847,-0.01244438998401165,0.8773393034934998,-0.4989152252674103,-0.023736687377095222,0.007505317684262991,0.86629319190979,-0.41863030195236206,-0.04300432279706001,0.04109735041856766,0.9062066078186035,-0.3398343622684479,-0.044692911207675934,0.03626490384340286,0.938722550868988,-0.2979501187801361,-0.04183636233210564,0.032411277294158936,0.953113317489624,-0.31447532773017883,-0.03934565186500549,0.025802265852689743,0.94809889793396,-0.32490065693855286,-0.03630294278264046,0.01873374730348587,0.9448654055595398,-0.32693105936050415,-0.03305131569504738,0.011003884486854076,0.9444059133529663,-0.323269784450531,-0.03251193091273308,0.007839113473892212,0.9457157254219055,-0.31899169087409973,-0.03302224725484848,0.0070548290386796,0.947155773639679,-0.31430184841156006,-0.03511596843600273,0.00976560264825821,0.9486231803894043,-0.3105345070362091,-0.03783378005027771,0.013662626966834068,0.9497106075286865,-0.3067018389701843,-0.03982573002576828,0.01602194830775261,0.9508371353149414,-0.306357204914093,-0.04159615933895111,0.018132885918021202,0.9508345127105713,-0.3104894757270813,-0.04409297928214073,0.022339949384331703,0.9492907524108887,-0.3225122094154358,-0.04648078978061676,0.02698792703449726,0.9450381398200989,-0.3439966142177582,-0.046218667179346085,0.02798885852098465,0.9374149441719055,-0.3627792298793793,-0.03832138702273369,0.015323850326240063,0.9309607148170471,-0.37371399998664856,-0.022220948711037636,-0.012576118111610413,0.9271925091743469,-0.37808939814567566,-0.011520469561219215,-0.031301356852054596,0.9251680374145508,-0.38231831789016724,-0.011124513112008572,-0.0312575064599514,0.923434853553772,-0.3994819223880768,-0.00803761463612318,-0.033080313354730606,0.9161087870597839,-0.42864325642585754,-0.008927230723202229,-0.0348978228867054,0.9027554392814636,-0.45600956678390503,-0.010337869636714458,-0.03203265368938446,0.8893381357192993,-0.485792338848114,-0.002671839902177453,-0.04322059080004692,0.873000979423523,-0.5143222212791443,0.006451008841395378,-0.05723503604531288,0.8556606769561768,-0.5248465538024902,-0.0028760062996298075,-0.03423617035150528,0.8505032658576965,-0.5461254715919495,-0.007384640630334616,-0.023351959884166718,0.83734530210495,-0.572511613368988,-0.008699099533259869,-0.019230980426073074,0.8196249008178711,-0.5951448082923889,-0.009351406246423721,-0.016068313270807266,0.803403377532959,-0.6106073260307312,-0.009021085686981678,-0.014929047785699368,0.7917414307594299,-0.6216527223587036,-0.008408044464886189,-0.014577239751815796,0.7831122279167175,-0.6317740678787231,-0.008277416229248047,-0.013544660992920399,0.7749900221824646,-0.641804039478302,-0.008985205553472042,-0.011439057998359203,0.766730785369873,-0.6517775654792786,-0.010135101154446602,-0.00855736993253231,0.7582942247390747,-0.6633092761039734,-0.011196552775800228,-0.005696824286133051,0.7482399344444275,-0.6752716898918152,-0.012809120118618011,-0.0019235098734498024,0.7374553084373474,-0.6868599653244019,-0.014641530811786652,0.002341501647606492,0.7266385555267334,-0.7008184790611267,-0.016512852162122726,0.006484426558017731,0.7131190299987793,-0.7187625169754028,-0.017068272456526756,0.008147048763930798,0.6949983835220337,-0.7371840476989746,-0.014673207886517048,0.004457314033061266,0.6755179762840271,-0.7530089616775513,-0.011072729714214802,-0.000971110537648201,0.6579163670539856,-0.7664983868598938,-0.00952054187655449,-0.0027264077216386795,0.6421698927879333,-0.778655469417572,-0.007977870292961597,-0.004550117999315262,0.6273845434188843,-0.7862225770950317,-0.012103098444640636,0.0028159490320831537,0.6178184747695923,-0.7793904542922974,-0.024255909025669098,0.02287944033741951,0.6256506443023682,-0.7683932185173035,-0.0361354798078537,0.0416949987411499,0.6375951766967773,-0.7542232275009155,-0.049143414944410324,0.06193099915981293,0.6518411040306091,-0.7386336326599121,-0.06125118210911751,0.0810096263885498,0.666412889957428,-0.7206201553344727,-0.07011106610298157,0.09517636895179749,0.6831782460212708,-0.7037747502326965,-0.0725453719496727,0.0986473336815834,0.6997906565666199,-0.6898500919342041,-0.06843138486146927,0.09168416261672974,0.7148553133010864,-0.6792172789573669,-0.062072113156318665,0.08056556433439255,0.7268563508987427,-0.6652097105979919,-0.05544288083910942,0.06844960898160934,0.7414423823356628,-0.6491208672523499,-0.04873726889491081,0.05644494295120239,0.7570210099220276,-0.6411647796630859,-0.04340505599975586,0.046511903405189514,0.7647616863250732,-0.6424658894538879,-0.03867898881435394,0.03815360367298126,0.764385998249054,-0.6471375226974487,-0.03413664177060127,0.030216509476304054,0.7610089778900146,-0.6496880054473877,-0.030388033017516136,0.023858709260821342,0.7592185139656067,-0.6495073437690735,-0.027134589850902557,0.0188099704682827,0.7596381902694702,-0.6465023756027222,-0.023647168651223183,0.013411578722298145,0.7624274492263794,-0.6385895609855652,-0.019602451473474503,0.006679302081465721,0.769268810749054,-0.6284323930740356,-0.016188282519578934,0.0005844241823069751,0.7776955366134644,-0.6226037740707397,-0.014986954629421234,-0.0018366595031693578,0.7823916077613831,-0.6180273294448853,-0.015611689537763596,-0.0013228653697296977,0.7860004305839539,-0.609009325504303,-0.018096107989549637,0.0018857948016375303,0.7929543852806091,-0.5995420217514038,-0.021824602037668228,0.007410833612084389,0.800011396408081,-0.5888172388076782,-0.02360803447663784,0.009799581952393055,0.8078619241714478,-0.5810814499855042,-0.02595406211912632,0.013366393744945526,0.8133217096328735,-0.5809285640716553,-0.02844422124326229,0.017696285620331764,0.8132649064064026,-0.5952673554420471,-0.031638916581869125,0.02390972524881363,0.8025485277175903,-0.6196286678314209,-0.03916468471288681,0.03499923273921013,0.783135712146759,-0.6255471706390381,-0.06189678609371185,0.07283864170312881,0.7743088006973267,-0.5957327485084534,-0.0877203494310379,0.11407848447561264,0.7901858687400818,-0.48207342624664307,-0.10392017662525177,0.13333266973495483,0.8596674799919128,-0.2773464322090149,-0.02864951081573963,0.00306688342243433,0.9603378772735596,-0.2776796817779541,0.007015972398221493,-0.05301794782280922,0.9591839909553528,-0.3857170045375824,0.033773329108953476,-0.0910947248339653,0.917487621307373,-0.46953970193862915,0.05814802646636963,-0.12985362112522125,0.8713721036911011,-0.5077529549598694,0.0592142678797245,-0.13165511190891266,0.8493218421936035,-0.48718854784965515,0.03316640108823776,-0.08649328351020813,0.8683698773384094,-0.4421709179878235,0.0016748410416767001,-0.03380299732089043,0.8962920308113098,-0.4497149586677551,-0.038495343178510666,0.036580514162778854,0.8915920853614807,-0.5343837738037109,-0.058974623680114746,0.07790686935186386,0.8395751714706421,-0.5612941384315491,-0.05055255442857742,0.06245243921875954,0.823706865310669,-0.5236763954162598,-0.07861639559268951,0.11936579644680023,0.8398418426513672,-0.5545465350151062,-0.07440018653869629,0.10247370600700378,0.822460949420929,-0.5829666256904602,-0.06310030072927475,0.08358962088823318,0.8057177066802979,-0.6136778593063354,-0.059826646000146866,0.0781085416674614,0.7834023833274841,-0.6300011873245239,-0.059504907578229904,0.07790346443653107,0.7703822255134583,-0.636272132396698,-0.054357998073101044,0.06741486489772797,0.7665887475013733,-0.6313670873641968,-0.05387834087014198,0.06421498209238052,0.7709404826164246,-0.6174880266189575,-0.05600738152861595,0.06504432111978531,0.7818829417228699,-0.5953881144523621,-0.05773896723985672,0.065243661403656,0.7987005114555359,-0.5705017447471619,-0.06171097978949547,0.06920640915632248,0.8160454034805298,-0.5488123893737793,-0.06474858522415161,0.07175189256668091,0.8303398489952087,-0.5373778939247131,-0.06550974398851395,0.07043439149856567,0.8378379940986633,-0.5344489812850952,-0.0657292976975441,0.06987287104129791,0.8397390842437744,-0.538054347038269,-0.06713155657052994,0.07258237898349762,0.8370918035507202,-0.5515478253364563,-0.06517580151557922,0.07248185575008392,0.828428328037262,-0.5666186809539795,-0.05972088873386383,0.06553079187870026,0.8191962242126465,-0.5803560018539429,-0.05394568294286728,0.05962849035859108,0.8103833794593811,-0.5943506360054016,-0.051582369953393936,0.05652089789509773,0.8005573153495789,-0.6008071899414062,-0.049858544021844864,0.054688069969415665,0.7959611415863037,-0.6020082831382751,-0.04783976078033447,0.05206430330872536,0.7953531742095947,-0.5997936725616455,-0.046632226556539536,0.050870999693870544,0.797173261642456,-0.5960938930511475,-0.04644688591361046,0.05148720368742943,0.7999148964881897,-0.589996337890625,-0.0460834726691246,0.05166545510292053,0.8044323325157166,-0.5812098383903503,-0.04457453265786171,0.04956911876797676,0.8110185861587524,-0.5722939372062683,-0.044129617512226105,0.04839612916111946,0.8174289464950562,-0.5644126534461975,-0.0449347160756588,0.049708589911460876,0.8227686882019043,-0.556033194065094,-0.046463485807180405,0.05150698125362396,0.8282604217529297,-0.5469603538513184,-0.04748666658997536,0.05313945561647415,0.8341196775436401,-0.5388035774230957,-0.04913508892059326,0.05597466230392456,0.8391324877738953,-0.5321446061134338,-0.053339432924985886,0.06264030933380127,0.8426465392112732,-0.5271761417388916,-0.058271374553442,0.07048555463552475,0.8448204398155212,-0.5226786136627197,-0.06304283440113068,0.07832538336515427,0.8465800881385803,-0.5185213685035706,-0.06775461882352829,0.0859435424208641,0.8480322360992432,-0.5144898891448975,-0.07217703759670258,0.09302850812673569,0.8493741154670715,-0.5098376274108887,-0.07650981098413467,0.09929677098989487,0.8510887026786804,-0.5040512084960938,-0.08026614040136337,0.1047360748052597,0.853533923625946,-0.49740952253341675,-0.08347779512405396,0.10930680483579636,0.856543779373169,-0.49023744463920593,-0.08619081974029541,0.11269696801900864,0.8599638342857361,-0.4836474061012268,-0.08873119950294495,0.1161312684416771,0.8629748225212097,-0.4784611463546753,-0.09123240411281586,0.11978521198034286,0.8651029467582703,-0.47509634494781494,-0.09322617202997208,0.12266343832015991,0.8663405776023865,-0.47468292713165283,-0.0941350907087326,0.12410049885511398,0.866264283657074,-0.4772164225578308,-0.09428807348012924,0.12424743920564651,0.864833414554596],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998807907104,0.9999998211860657,0.9999997615814209,1,0.9999998807907104,0.9999999403953552],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,5.4666666984558105],"values":[0.0000021128516891621985,13.415111541748047,7.694025043747388e-7,-0.000009847801266005263,13.41512393951416,-0.0000024983428374980576],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[-0.3069814443588257,-0.005662643350660801,-0.0018292381428182125,0.9516969323158264,-0.30698150396347046,-0.005662644747644663,-0.0018291991436854005,0.9516968727111816],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,1.0000001192092896,1.000000238418579,1,1.0000001192092896,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,5.4666666984558105],"values":[-11.13949966430664,-4.020101070404053,0.42030391097068787,-11.139496803283691,-4.020098686218262,0.42030346393585205],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.7949976325035095,-0.15647196769714355,0.2393011450767517,0.5350049734115601,0.7892870306968689,-0.15999683737754822,0.26764336228370667,0.5289556384086609,0.7912368774414062,-0.1690562516450882,0.27081358432769775,0.5215594172477722,0.7957699298858643,-0.18024314939975739,0.2648426294326782,0.5139271020889282,0.8025090098381042,-0.19207435846328735,0.2522144019603729,0.5054450631141663,0.8107181787490845,-0.2028142213821411,0.2373625785112381,0.4952387809753418,0.8194672465324402,-0.21090851724147797,0.2255859673023224,0.48280638456344604,0.82695072889328,-0.21642518043518066,0.22129473090171814,0.4694052040576935,0.831914484500885,-0.22013887763023376,0.22695888578891754,0.4560118615627289,0.8368750810623169,-0.22172364592552185,0.23531587421894073,0.4417070746421814,0.8427780270576477,-0.2186867594718933,0.2449662983417511,0.426488995552063,0.8502983450889587,-0.21333752572536469,0.25719451904296875,0.406608909368515,0.8582352995872498,-0.20694948732852936,0.2714467942714691,0.3833024501800537,0.8653110861778259,-0.2000475823879242,0.2867708206176758,0.3591381311416626,0.8703325390815735,-0.19299817085266113,0.30238407850265503,0.33739718794822693,0.8740459680557251,-0.18646766245365143,0.31861236691474915,0.31584760546684265,0.8770303130149841,-0.17975473403930664,0.33578789234161377,0.29283544421195984,0.8802583813667297,-0.1694364994764328,0.3539247214794159,0.2667842209339142,0.8838934898376465,-0.15545214712619781,0.37104254961013794,0.23852530121803284,0.8868563771247864,-0.14367276430130005,0.38343432545661926,0.21406066417694092,0.8879618644714355,-0.13476461172103882,0.39392736554145813,0.1954060047864914,0.8873518109321594,-0.12688203155994415,0.40236759185791016,0.1860322505235672,0.8855096101760864,-0.12088998407125473,0.4097931385040283,0.18255943059921265,0.8836144208908081,-0.11577916145324707,0.4163173735141754,0.18027906119823456,0.8811964988708496,-0.11358233541250229,0.4212501347064972,0.1820443570613861,0.8790946006774902,-0.11383408308029175,0.4249153137207031,0.18352532386779785,0.877120316028595,-0.11514924466609955,0.42801111936569214,0.18495169281959534,0.8755766153335571,-0.11707655340433121,0.4307393431663513,0.1847223937511444,0.8738316297531128,-0.12144976854324341,0.4317608177661896,0.18775209784507751,0.8729566335678101,-0.12647145986557007,0.4310435354709625,0.19013985991477966,0.8729897737503052,-0.13148769736289978,0.42991334199905396,0.18914102017879486,0.8735013604164124,-0.13603940606117249,0.42819133400917053,0.18745897710323334,0.873245120048523,-0.13917729258537292,0.4290541112422943,0.18435080349445343,0.8734664916992188,-0.139230877161026,0.4316803514957428,0.17698357999324799,0.8739296197891235,-0.1359322965145111,0.43556153774261475,0.16749823093414307,0.8756239414215088,-0.12899437546730042,0.43871399760246277,0.15547743439674377,0.8756182193756104,-0.1270894706249237,0.44023990631103516,0.1527411788702011,0.8727629780769348,-0.13135772943496704,0.441831111907959,0.16067099571228027,0.8693250417709351,-0.13819286227226257,0.44308510422706604,0.1698596477508545,0.862708330154419,-0.14827771484851837,0.4446815252304077,0.18975374102592468,0.8519481420516968,-0.15945954620838165,0.4475512206554413,0.22012493014335632,0.8390325903892517,-0.17235898971557617,0.45149874687194824,0.2499312311410904,0.8261269330978394,-0.18552851676940918,0.4520393908023834,0.28063130378723145,0.8140413761138916,-0.19343703985214233,0.4519927203655243,0.30922701954841614,0.803076982498169,-0.19863833487033844,0.45377305150032043,0.33121031522750854,0.7935255169868469,-0.20385806262493134,0.45447567105293274,0.34958702325820923,0.7870998382568359,-0.20799443125724792,0.4522906243801117,0.36420512199401855,0.7842122316360474,-0.20972047746181488,0.44921818375587463,0.37313753366470337,0.7830274105072021,-0.20999470353126526,0.4511912763118744,0.37309083342552185,0.7871496081352234,-0.20356936752796173,0.4549068212509155,0.36333850026130676,0.7937173247337341,-0.19303570687770844,0.4584862291812897,0.35005778074264526,0.8032583594322205,-0.18503695726394653,0.45157262682914734,0.34149599075317383,0.8174146413803101,-0.1857905238866806,0.4239925444126129,0.3428491950035095,0.8288811445236206,-0.19558733701705933,0.3850659430027008,0.3555642068386078,0.8345098495483398,-0.2074545919895172,0.35118013620376587,0.3704436123371124,0.8346404433250427,-0.21670693159103394,0.3337642550468445,0.3808082342147827,0.8324040770530701,-0.22517304122447968,0.3289045989513397,0.38499659299850464,0.8294663429260254,-0.2370356172323227,0.32804837822914124,0.38494673371315,0.8231588006019592,-0.24897071719169617,0.3285631537437439,0.39047345519065857,0.8141000270843506,-0.2556779682636261,0.33935314416885376,0.3958653211593628,0.8120729923248291,-0.2607153356075287,0.3568028509616852,0.3811255693435669,0.8183514475822449,-0.2491849958896637,0.3781699240207672,0.3538294732570648,0.8249852061271667,-0.2504461109638214,0.3867066502571106,0.32731351256370544,0.8366758823394775,-0.24649125337600708,0.3756081163883209,0.31326359510421753,0.8502498269081116,-0.22713519632816315,0.351267009973526,0.31952527165412903,0.8513079881668091,-0.22113724052906036,0.31855207681655884,0.3534085750579834,0.8434410691261292,-0.22251158952713013,0.2755317986011505,0.40395301580429077,0.8297380805015564,-0.2238219678401947,0.23120111227035522,0.456053227186203,0.8134979009628296,-0.22005273401737213,0.1951068788766861,0.501728355884552,0.7993314862251282,-0.21262910962104797,0.17291702330112457,0.53475022315979,0.7917098999023438,-0.20155693590641022,0.16250482201576233,0.55331951379776,0.7891213893890381,-0.18398325145244598,0.15685603022575378,0.5646536946296692,0.7875486612319946,-0.17174595594406128,0.15223388373851776,0.5719224214553833,0.7834755182266235,-0.1588321179151535,0.14468728005886078,0.5830987095832825,0.7743017077445984,-0.14711423218250275,0.14176209270954132,0.5989305377006531,0.7727862000465393,-0.13108034431934357,0.15052741765975952,0.6024624109268188,0.7610326409339905,-0.1215520054101944,0.15558740496635437,0.6179376840591431,0.7338704466819763,-0.12970241904258728,0.1539936065673828,0.6487661600112915,0.7164966464042664,-0.1512637436389923,0.1623862385749817,0.6613490581512451,0.7127600908279419,-0.17783550918102264,0.17789828777313232,0.6547517776489258,0.7196885347366333,-0.19897694885730743,0.2044173926115036,0.6329851746559143,0.691041886806488,-0.2199205607175827,0.20686329901218414,0.6567371487617493,0.6709352135658264,-0.231957346200943,0.18215171992778778,0.680340051651001,0.6689467430114746,-0.24229489266872406,0.16307663917541504,0.6835272312164307,0.6651126742362976,-0.2528075873851776,0.17237967252731323,0.6811745166778564,0.6645562052726746,-0.26278263330459595,0.20175279676914215,0.6697806715965271,0.6693131327629089,-0.2656925618648529,0.23581387102603912,0.6525483131408691,0.6764184236526489,-0.2667412757873535,0.2635789215564728,0.6339033246040344,0.6813274025917053,-0.26767444610595703,0.29170215129852295,0.6156730055809021,0.6825857162475586,-0.2681218087673187,0.3364025056362152,0.5907798409461975,0.6821362972259521,-0.26761916279792786,0.39307913184165955,0.5554808378219604,0.680765688419342,-0.2612980306148529,0.44054943323135376,0.523638904094696,0.6785162091255188,-0.25310277938842773,0.4709544777870178,0.5037427544593811,0.6762213110923767,-0.24536027014255524,0.4953066408634186,0.4870261549949646,0.6755492687225342,-0.23784905672073364,0.5188809633255005,0.4667157828807831,0.675769567489624,-0.23035219311714172,0.5428096055984497,0.44230222702026367,0.6774722933769226,-0.2234250158071518,0.5664352774620056,0.4126302897930145,0.6800270080566406,-0.21414659917354584,0.5842539668083191,0.3877522945404053,0.6808306574821472,-0.20305244624614716,0.5988074541091919,0.3696877658367157,0.6747865676879883,-0.19892388582229614,0.6143890023231506,0.3572373688220978,0.6679410338401794,-0.19454757869243622,0.6304337382316589,0.3443244695663452,0.6590665578842163,-0.1917548030614853,0.6467143297195435,0.3325987458229065,0.6462154388427734,-0.19871143996715546,0.6615102291107178,0.32453593611717224,0.6302966475486755,-0.20873814821243286,0.6747226715087891,0.3223411440849304,0.6106364130973816,-0.21840260922908783,0.6837552785873413,0.33451786637306213,0.5892602205276489,-0.22508087754249573,0.6839508414268494,0.36650004982948303,0.5721597075462341,-0.22685593366622925,0.6674302816390991,0.41917359828948975,0.5659717917442322,-0.22476038336753845,0.6304904222488403,0.4812905490398407,0.5768149495124817,-0.21801798045635223,0.5696656107902527,0.5433540940284729,0.5973271131515503,-0.20445863902568817,0.48890724778175354,0.6019691228866577,0.6163610816001892,-0.18744586408138275,0.4081483483314514,0.6468214392662048,0.6392142176628113,-0.17439623177051544,0.3465275764465332,0.6640104651451111,0.6600176095962524,-0.16861972212791443,0.3013307452201843,0.6671910881996155,0.6753481030464172,-0.16759993135929108,0.26543769240379333,0.6673516035079956,0.688746452331543,-0.16854281723499298,0.23749390244483948,0.6639415621757507,0.6989410519599915,-0.17320281267166138,0.20877325534820557,0.6617371439933777,0.710598349571228,-0.18099117279052734,0.18913741409778595,0.653084397315979,0.7312343716621399,-0.18865199387073517,0.1952122002840042,0.6257786750793457,0.7605001330375671,-0.1911364048719406,0.2120457887649536,0.5832178592681885,0.7872726321220398,-0.19628363847732544,0.21885332465171814,0.5420127511024475,0.8090423941612244,-0.20339615643024445,0.22038251161575317,0.5054819583892822,0.8237894177436829,-0.20996108651161194,0.20868270099163055,0.4834655225276947,0.8302664160728455,-0.2218957096338272,0.17259493470191956,0.481280654668808,0.8342606425285339,-0.23331712186336517,0.141934335231781,0.47898533940315247,0.8399242162704468,-0.2382940649986267,0.14412428438663483,0.4658019244670868,0.841647744178772,-0.2428199052810669,0.1562025249004364,0.456364244222641,0.8372551798820496,-0.2531055212020874,0.1782149076461792,0.4507557451725006,0.8278136253356934,-0.2653350830078125,0.20630484819412231,0.4491772949695587,0.8155156373977661,-0.27831462025642395,0.23094990849494934,0.4518156945705414,0.8044725656509399,-0.2892855405807495,0.2515639364719391,0.45371073484420776,0.7998219132423401,-0.2863706946372986,0.26642894744873047,0.45529377460479736,0.7968498468399048,-0.27875831723213196,0.27177974581718445,0.462017297744751,0.7921094298362732,-0.27019473910331726,0.2734195590019226,0.47412997484207153,0.7858852744102478,-0.2630722224712372,0.2773367762565613,0.4860675632953644,0.7793089747428894,-0.25390827655792236,0.2820384204387665,0.4986606538295746,0.7739585041999817,-0.23701803386211395,0.2840832769870758,0.5139138698577881,0.7711511254310608,-0.21523813903331757,0.28303447365760803,0.5281004309654236,0.7696655988693237,-0.19207659363746643,0.2772354185581207,0.5420904159545898,0.7660976648330688,-0.168015256524086,0.2720685303211212,0.557533860206604,0.7638375759124756,-0.15293708443641663,0.2667793035507202,0.5674427151679993,0.7636303305625916,-0.14991140365600586,0.25515827536582947,0.5738376379013062,0.7639431953430176,-0.15787863731384277,0.23632200062274933,0.5793247222900391,0.7670442461967468,-0.1721012443304062,0.2147025614976883,0.5795922875404358,0.7747979760169983,-0.18642400205135345,0.19267858564853668,0.5725463032722473,0.7852433323860168,-0.1964840143918991,0.17094790935516357,0.5617506504058838,0.7979021668434143,-0.2007737159729004,0.1510889232158661,0.5479180812835693,0.8113481998443604,-0.19804716110229492,0.13409112393856049,0.5333957076072693,0.8119217753410339,-0.2041398584842682,0.11802294105291367,0.5340229272842407,0.8067852854728699,-0.21474042534828186,0.1080288365483284,0.5397350192070007,0.8008582592010498,-0.21926192939281464,0.10543177276849747,0.5472060441970825,0.7957921028137207,-0.21634545922279358,0.10641150921583176,0.5555052757263184,0.7917006611824036,-0.2072867751121521,0.11009692400693893,0.5640220642089844,0.7893524765968323,-0.1982126086950302,0.11730024963617325,0.5691002607345581,0.7885655164718628,-0.1903432011604309,0.12748946249485016,0.5706840753555298,0.7888673543930054,-0.18364721536636353,0.13988488912582397,0.5695562362670898,0.7897541522979736,-0.17769140005111694,0.15370123088359833,0.5666481852531433,0.7907875180244446,-0.17223742604255676,0.1682066023349762,0.5627574324607849,0.7917484641075134,-0.16749775409698486,0.18280793726444244,0.5582653284072876,0.7927191257476807,-0.1633736789226532,0.1969815194606781,0.5532664060592651,0.7936554551124573,-0.16006675362586975,0.2101559191942215,0.5480183959007263,0.7944682836532593,-0.1577606052160263,0.22170384228229523,0.5429357290267944,0.7950466871261597,-0.15654028952121735,0.2309354841709137,0.538576602935791,0.7953581213951111,-0.15672612190246582,0.23722030222415924,0.5353213548660278,0.7949979901313782,-0.15647178888320923,0.23930107057094574,0.5350044965744019],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1.000000238418579,1.0000001192092896,1.000000238418579,1.000000238418579,0.9999998807907104,1.0000001192092896],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,5.4666666984558105],"values":[9.806589673644339e-8,25.527101516723633,-0.0000020554516595439054,-0.00000519445620739134,25.527099609375,-0.00000526846497450606],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.4091765284538269,-0.23358453810214996,-0.03469248488545418,0.8813678622245789,0.41569310426712036,-0.2388284057378769,-0.039913445711135864,0.8766795992851257,0.42636212706565857,-0.22131560742855072,-0.03653552755713463,0.8762990236282349,0.43956080079078674,-0.19517642259597778,-0.031027603894472122,0.876201868057251,0.4540649652481079,-0.1629718840122223,-0.02323491871356964,0.8756285309791565,0.4688054919242859,-0.12873776257038116,-0.014159259386360645,0.8737548589706421,0.4828057885169983,-0.09695464372634888,-0.005650307517498732,0.87032550573349,0.4948631227016449,-0.07172156870365143,-0.000025769277272047475,0.866006076335907,0.5041467547416687,-0.05557863041758537,0.0010557351633906364,0.8618271350860596,0.5098109245300293,-0.0419379398226738,0.0025397103745490313,0.8592599034309387,0.5096839070320129,-0.029447056353092194,0.009268577210605145,0.8598077297210693,0.4955415725708008,-0.01741238683462143,0.013220693916082382,0.8683090806007385,0.4708015024662018,-0.006325078662484884,0.013668705709278584,0.882110595703125,0.4409105181694031,0.0037267839070409536,0.0114302271977067,0.8974705934524536,0.41122114658355713,0.01201016828417778,0.008197768591344357,0.9114196300506592,0.3798251450061798,0.01892576366662979,0.003394941333681345,0.9248584508895874,0.34251275658607483,0.023728204891085625,-0.0044448114931583405,0.9392030239105225,0.29377737641334534,0.026874154806137085,-0.010249843820929527,0.9554409980773926,0.2364151030778885,0.029886452481150627,-0.012569735758006573,0.9711111187934875,0.18392060697078705,0.03287428244948387,-0.01768379658460617,0.9822320342063904,0.14545199275016785,0.03559184446930885,-0.02557344362139702,0.9883941411972046,0.12636490166187286,0.03815457224845886,-0.029051175341010094,0.9908239841461182,0.12097729742527008,0.0400908887386322,-0.02957947552204132,0.9914042353630066,0.11751626431941986,0.0415741391479969,-0.02965475060045719,0.9917570948600769,0.12114179134368896,0.04143165051937103,-0.02838851511478424,0.9913638234138489,0.12531644105911255,0.040679849684238434,-0.02695249393582344,0.9909160137176514,0.13034981489181519,0.039685871452093124,-0.02527296170592308,0.9903510808944702,0.13503967225551605,0.03884506598114967,-0.023770662024617195,0.989793062210083,0.1499038189649582,0.037651993334293365,-0.019092664122581482,0.9877989292144775,0.1639549285173416,0.03618709743022919,-0.014246008358895779,0.9857009053230286,0.1717362105846405,0.034463364630937576,-0.011488961055874825,0.9844729900360107,0.17786905169487,0.03216049075126648,-0.009629062376916409,0.9834814071655273,0.1812310665845871,0.028287839144468307,-0.009393691085278988,0.982988715171814,0.17352598905563354,0.02245497889816761,-0.013362373225390911,0.9844825863838196,0.15660330653190613,0.014857115224003792,-0.020289205014705658,0.9873414039611816,0.13013266026973724,0.005922939628362656,-0.028737526386976242,0.991062343120575,0.11941973865032196,-0.010714891366660595,-0.034087322652339935,0.9922006726264954,0.13175201416015625,-0.03240739554166794,-0.03595838323235512,0.990100085735321,0.146829754114151,-0.053281378000974655,-0.038375429809093475,0.9869799613952637,0.17548799514770508,-0.07505191117525101,-0.038891565054655075,0.9808458685874939,0.21729029715061188,-0.09755951166152954,-0.0386190302670002,0.970451295375824,0.2557147443294525,-0.11587478965520859,-0.04506484419107437,0.9587242603302002,0.2891179323196411,-0.12878049910068512,-0.050973668694496155,0.9472212791442871,0.316730260848999,-0.13560181856155396,-0.050716955214738846,0.9374017119407654,0.3354199230670929,-0.1394878625869751,-0.04757091775536537,0.9304695725440979,0.34972378611564636,-0.14251753687858582,-0.039693668484687805,0.9250980615615845,0.3598535656929016,-0.14436885714530945,-0.027486911043524742,0.9213618040084839,0.3652742803096771,-0.14541557431221008,-0.012957221828401089,0.9193808436393738,0.36610525846481323,-0.14957228302955627,-0.0001428040850441903,0.9184743165969849,0.35874009132385254,-0.15367752313613892,0.00951470248401165,0.9206510186195374,0.34620383381843567,-0.15783746540546417,0.014568780548870564,0.9246718287467957,0.33325088024139404,-0.160443976521492,0.01540364045649767,0.9289587140083313,0.3265058994293213,-0.16525697708129883,0.011858366429805756,0.930560827255249,0.33001941442489624,-0.17591208219528198,0.009387645870447159,0.927390992641449,0.3357137143611908,-0.18841907382011414,0.009909738786518574,0.9228739738464355,0.3389138877391815,-0.2020193338394165,0.010460863821208477,0.9188123941421509,0.34124717116355896,-0.21449562907218933,0.009012708440423012,0.9151288270950317,0.34389129281044006,-0.2243586778640747,0.004081874620169401,0.9118033051490784,0.36456918716430664,-0.21718639135360718,0.006912122946232557,0.9054676294326782,0.402231365442276,-0.16749981045722961,0.023665450513362885,0.8997742533683777,0.41715529561042786,-0.08595443516969681,0.0426776185631752,0.9037543535232544,0.3976089656352997,-0.018366530537605286,0.08428414911031723,0.9134910702705383,0.3730601966381073,0.028071701526641846,0.08257779479026794,0.9236985445022583,0.34686756134033203,0.041394129395484924,0.08332844078540802,0.9332876205444336,0.32931455969810486,0.00402683624997735,0.103191077709198,0.9385559558868408,0.3734988868236542,-0.05024851858615875,0.10415463894605637,0.9203941822052002,0.4448816776275635,-0.07760239392518997,0.11285596340894699,0.8850546479225159,0.4853152334690094,-0.1146300658583641,0.10386178642511368,0.8605473637580872,0.49407702684402466,-0.14296954870224,0.08663870394229889,0.8531947731971741,0.47246575355529785,-0.15331406891345978,0.07050959020853043,0.8650429248809814,0.4281351864337921,-0.14449161291122437,0.05914190784096718,0.8901262283325195,0.3706647455692291,-0.11787556856870651,0.051435742527246475,0.919819176197052,0.2983532249927521,-0.1117839515209198,0.025721058249473572,0.9475379586219788,0.24261416494846344,-0.1274530291557312,-0.0011879857629537582,0.961713433265686,0.19610419869422913,-0.15634647011756897,-0.04028334468603134,0.9672001600265503,0.11339565366506577,-0.20069535076618195,-0.06359758973121643,0.9709882140159607,0.07800987362861633,-0.23480992019176483,-0.06461446732282639,0.9667490720748901,0.10721204429864883,-0.24860458076000214,-0.06463859975337982,0.9604807496070862,0.12143757194280624,-0.25810036063194275,-0.0577571727335453,0.9567137360572815,0.12326860427856445,-0.2656625807285309,-0.048184603452682495,0.9549379944801331,0.09729231894016266,-0.2710474133491516,-0.0408187098801136,0.9567660689353943,0.19639641046524048,-0.26590636372566223,-0.03257203474640846,0.9432185888290405,0.29747194051742554,-0.23053905367851257,-0.013066104613244534,0.9263862371444702,0.33884111046791077,-0.19509148597717285,-0.006160369608551264,0.9203739166259766,0.36904871463775635,-0.17139792442321777,-0.004485192243009806,0.9134581089019775,0.38511890172958374,-0.15411296486854553,-0.006597105413675308,0.9098840951919556,0.3828088939189911,-0.14933300018310547,-0.006703484337776899,0.911653459072113,0.3690341114997864,-0.15224409103393555,-0.009788520634174347,0.9168095588684082,0.35203275084495544,-0.1604461371898651,-0.015190440230071545,0.9220082759857178,0.3306938707828522,-0.17131763696670532,-0.022447867318987846,0.9277865886688232,0.3057170510292053,-0.18253493309020996,-0.029763465747237206,0.9339872598648071,0.2853686213493347,-0.19543755054473877,-0.03532352298498154,0.9376146197319031,0.27336692810058594,-0.20613518357276917,-0.03879605606198311,0.938761830329895,0.26273325085639954,-0.21361196041107178,-0.04235656559467316,0.9399718642234802,0.2468816488981247,-0.21728330850601196,-0.05041586980223656,0.943024754524231,0.22686925530433655,-0.21683819591999054,-0.06175834685564041,0.9474689960479736,0.21046869456768036,-0.1932171732187271,-0.06855630874633789,0.9558609127998352,0.2225690335035324,-0.11891879141330719,-0.06523929536342621,0.965435266494751,0.24082674086093903,-0.02257111854851246,-0.05596618726849556,0.9686902761459351,0.2558726370334625,0.04274239391088486,-0.056702256202697754,0.9640991687774658,0.25699642300605774,0.07959093153476715,-0.053841669112443924,0.9616232514381409,0.2530622184276581,0.09058131277561188,-0.061906397342681885,0.961208701133728,0.2621169090270996,0.08684326708316803,-0.0856679379940033,0.9572951793670654,0.27917587757110596,0.0775090679526329,-0.10933760553598404,0.950840950012207,0.31132128834724426,0.06487719714641571,-0.11870010942220688,0.9406275749206543,0.3630262613296509,0.05372956767678261,-0.1036214604973793,0.9244391322135925,0.425076425075531,0.05062998831272125,-0.0683366060256958,0.901153028011322,0.47975173592567444,0.046766240149736404,-0.026201404631137848,0.8757652044296265,0.5209370851516724,0.02904595620930195,0.006136131007224321,0.8530787229537964,0.5480737090110779,0.002331099472939968,0.024931136518716812,0.8360552191734314,0.566414475440979,-0.027278805151581764,0.034539952874183655,0.8229444026947021,0.5744394063949585,-0.06154276430606842,0.03490370139479637,0.8154836297035217,0.5729758143424988,-0.09540437906980515,0.03514976426959038,0.813241183757782,0.5648588538169861,-0.12772272527217865,0.03667319938540459,0.8144178986549377,0.5515844821929932,-0.15804336965084076,0.038593556731939316,0.818099856376648,0.540610134601593,-0.18667328357696533,0.040625184774398804,0.8192944526672363,0.5347598791122437,-0.21350888907909393,0.04173038527369499,0.8165197372436523,0.5287870168685913,-0.2391970306634903,0.03912725672125816,0.8134114146232605,0.519620418548584,-0.26300379633903503,0.034848734736442566,0.8121632933616638,0.5042722821235657,-0.2863142788410187,0.02982897125184536,0.8141522407531738,0.4823371171951294,-0.3103111684322357,0.023909106850624084,0.818832278251648,0.4643741548061371,-0.33533111214637756,0.01880442351102829,0.8194852471351624,0.45923522114753723,-0.36384427547454834,0.014019847847521305,0.8102615475654602,0.44361236691474915,-0.3926553726196289,0.009187309071421623,0.8055714964866638,0.41715556383132935,-0.41425633430480957,0.008713172748684883,0.8088862895965576,0.3972574770450592,-0.42447707056999207,0.013840757310390472,0.8135195970535278,0.3713209927082062,-0.4371320605278015,0.00556705379858613,0.8191491365432739,0.3430348336696625,-0.4504171907901764,-0.01167733408510685,0.824205756187439,0.32034429907798767,-0.4539790749549866,-0.037536125630140305,0.830586314201355,0.29897239804267883,-0.4472489058971405,-0.06612135469913483,0.8403641581535339,0.2794473171234131,-0.4334198534488678,-0.08176521211862564,0.8528603911399841,0.27124902606010437,-0.41573378443717957,-0.08834277838468552,0.8635884523391724,0.27156078815460205,-0.3963581621646881,-0.09520063549280167,0.8718324303627014,0.28025028109550476,-0.36406242847442627,-0.10617810487747192,0.8818415999412537,0.2998164892196655,-0.3192916810512543,-0.11601860076189041,0.8914609551429749,0.32742464542388916,-0.2713695466518402,-0.11077829450368881,0.8982649445533752,0.35499924421310425,-0.2240101546049118,-0.09238698333501816,0.9029172658920288,0.3818858861923218,-0.176099494099617,-0.06658899784088135,0.9048303961753845,0.4129296541213989,-0.13569603860378265,-0.037215977907180786,0.8998281359672546,0.4358828663825989,-0.10719512403011322,-0.011321584694087505,0.8935251235961914,0.45517516136169434,-0.08686595410108566,0.00969125796109438,0.8861015439033508,0.47491905093193054,-0.07058286666870117,0.022879572585225105,0.8768959641456604,0.4897044003009796,-0.057835694402456284,0.030742758885025978,0.8694248199462891,0.4972585141658783,-0.04203173890709877,0.03760620579123497,0.8657673597335815,0.5012490749359131,-0.026009606197476387,0.043260812759399414,0.863829493522644,0.500292956829071,-0.009585000574588776,0.048295680433511734,0.8644551634788513,0.4940088987350464,0.003846114734187722,0.05086425319314003,0.8679592609405518,0.502005934715271,-0.016562962904572487,0.036911189556121826,0.8639174699783325,0.510224461555481,-0.05077127367258072,0.013388860039412975,0.8584370017051697,0.5171745419502258,-0.07797971367835999,-0.004807219374924898,0.8523066639900208,0.5252604484558105,-0.09398625046014786,-0.013157058507204056,0.8456328511238098,0.535406768321991,-0.10107413679361343,-0.012769024819135666,0.8384274840354919,0.5397926568984985,-0.10875222086906433,-0.012143761850893497,0.8346552848815918,0.5380381345748901,-0.11849963665008545,-0.01258996594697237,0.8344545364379883,0.5313437581062317,-0.12991952896118164,-0.013767803087830544,0.8370216488838196,0.520840048789978,-0.1426837295293808,-0.015454948879778385,0.8415035009384155,0.5074536800384521,-0.15628689527511597,-0.017400560900568962,0.847208559513092,0.4918569326400757,-0.17031410336494446,-0.019546546041965485,0.8536320924758911,0.4750581383705139,-0.18427801132202148,-0.021834732964634895,0.8601654767990112,0.4579460918903351,-0.19761882722377777,-0.024246396496891975,0.8663973212242126,0.4415757656097412,-0.20966945588588715,-0.026718275621533394,0.8719723224639893,0.42723193764686584,-0.21978873014450073,-0.02917954884469509,0.8765354156494141,0.41606462001800537,-0.2273353487253189,-0.031531620770692825,0.8798946738243103,0.4091746211051941,-0.2335861176252365,-0.03469354286789894,0.8813682794570923],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999999403953552,0.9999999403953552,0.9999998211860657,0.9999999403953552,0.9999999403953552,0.9999999403953552],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,5.4666666984558105],"values":[-3.065530904677871e-7,23.109195709228516,5.479544711306517e-7,-0.0000014837033859294024,23.10919952392578,0.0000013406444168140297],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[-0.6451200246810913,0.02403809130191803,0.026659712195396423,0.7632375955581665,-0.6503288149833679,0.02073153480887413,0.03086179681122303,0.7587425112724304,-0.6592508554458618,0.02340078540146351,0.026391757652163506,0.7510953545570374,-0.669477641582489,0.027963029220700264,0.018666617572307587,0.7420709133148193,-0.6803723573684692,0.03435272350907326,0.007905588485300541,0.7320183515548706,-0.691839337348938,0.041670043021440506,-0.00436550285667181,0.7208349108695984,-0.7039918899536133,0.048387136310338974,-0.01581411622464657,0.7083812952041626,-0.7155300378799438,0.053292833268642426,-0.024407321587204933,0.6961184740066528,-0.7255894541740417,0.055721770972013474,-0.028663910925388336,0.6852689385414124,-0.7337914109230042,0.057979267090559006,-0.032444585114717484,0.6761183142662048,-0.7389311790466309,0.06126605346798897,-0.037674564868211746,0.6699312925338745,-0.7373707890510559,0.06289485841989517,-0.03942963853478432,0.6713969707489014,-0.7307116389274597,0.06310413777828217,-0.03841336444020271,0.6786773204803467,-0.7212782502174377,0.06208855286240578,-0.035213831812143326,0.688957691192627,-0.7111842632293701,0.06003227084875107,-0.030564939603209496,0.6997706294059753,-0.7001690864562988,0.05712803080677986,-0.024135064333677292,0.71127849817276,-0.6861094832420349,0.05341256037354469,-0.016703253611922264,0.7253426313400269,-0.6667582392692566,0.04980984702706337,-0.009844311513006687,0.7435425519943237,-0.6435543298721313,0.046980660408735275,-0.004616069607436657,0.7639433145523071,-0.6222056150436401,0.0429082028567791,0.0029853598680347204,0.7816713452339172,-0.6071308851242065,0.03729724884033203,0.013860337436199188,0.7936050295829773,-0.6003719568252563,0.03181369602680206,0.025394773110747337,0.798684298992157,-0.5996481776237488,0.027914371341466904,0.033241529017686844,0.7990856766700745,-0.5985043048858643,0.024875247851014137,0.03914676606655121,0.7997758388519287,-0.5962608456611633,0.025101900100708008,0.038723982870578766,0.8014633059501648,-0.5919569730758667,0.026689259335398674,0.036251001060009,0.8047114610671997,-0.5880982875823975,0.028638193383812904,0.03290507197380066,0.8076122999191284,-0.5874613523483276,0.030856017023324966,0.028696412220597267,0.8081544637680054,-0.593510091304779,0.03446570038795471,0.021944891661405563,0.8037887811660767,-0.5990951657295227,0.038330309092998505,0.015043258666992188,0.7996183633804321,-0.6027333736419678,0.04272075369954109,0.0074419849552214146,0.7967634797096252,-0.6067034602165222,0.04797956719994545,-0.001412263954989612,0.7934777140617371,-0.6112709641456604,0.05326753482222557,-0.010524125769734383,0.789556622505188,-0.6168872714042664,0.0555465891957283,-0.015154361724853516,0.7849426865577698,-0.6241990327835083,0.054939813911914825,-0.015084718354046345,0.7791852951049805,-0.6307370066642761,0.051951292902231216,-0.011241740547120571,0.774174153804779,-0.6354089379310608,0.055892039090394974,-0.02184467203915119,0.7698404788970947,-0.6451115608215332,0.06467889994382858,-0.04234115034341812,0.7601677179336548,-0.6555908918380737,0.07083047181367874,-0.05955149605870247,0.7494246363639832,-0.6706401109695435,0.07475920021533966,-0.07315285503864288,0.7343716025352478,-0.6898760795593262,0.07559852302074432,-0.07893358916044235,0.7156293392181396,-0.7070010900497437,0.07176381349563599,-0.07449984550476074,0.6996064782142639,-0.7189728617668152,0.0664338767528534,-0.06542056053876877,0.6887559294700623,-0.7273575663566589,0.060456860810518265,-0.053234487771987915,0.6815145611763,-0.732105553150177,0.05407851189374924,-0.040115274488925934,0.6778552532196045,-0.7354072332382202,0.04943963885307312,-0.030543142929673195,0.6751289367675781,-0.7373466491699219,0.04621291533112526,-0.02370201237499714,0.6735150814056396,-0.7380183935165405,0.04391082376241684,-0.01841321773827076,0.6730985641479492,-0.7377231121063232,0.04116252437233925,-0.011991293169558048,0.6737406849861145,-0.7351509928703308,0.03727124258875847,-0.0032830890268087387,0.6768701076507568,-0.7318456172943115,0.031975556164979935,0.00935423094779253,0.6806555986404419,-0.7276696562767029,0.024599246680736542,0.02568911947309971,0.6850050091743469,-0.7186283469200134,0.017869530245661736,0.039924751967191696,0.6940172910690308,-0.7063280940055847,0.014908187091350555,0.04685048758983612,0.7061752080917358,-0.6936404705047607,0.014223056845366955,0.049738965928554535,0.7184613347053528,-0.6796572804450989,0.015291101299226284,0.04993157461285591,0.7316686511039734,-0.6653735637664795,0.01578051969408989,0.050615034997463226,0.7446255087852478,-0.6497421860694885,0.015078989788889885,0.05307428911328316,0.7581496238708496,-0.6299764513969421,0.0186673142015934,0.048045676201581955,0.7749018669128418,-0.6048789620399475,0.017568664625287056,0.05093478783965111,0.7944925427436829,-0.5652685761451721,0.019747722893953323,0.04891364648938179,0.8232186436653137,-0.509809672832489,0.032228704541921616,0.029548874124884605,0.8591753840446472,-0.4404281675815582,0.022158091887831688,0.049057524651288986,0.8961726427078247,-0.3775470554828644,0.0033409795723855495,0.08205977827310562,0.9223411679267883,-0.30528292059898376,-0.011404868215322495,0.10690135508775711,0.9461735486984253,-0.2668718993663788,-0.022822629660367966,0.12306361645460129,0.9555699229240417,-0.28734809160232544,-0.022088009864091873,0.12216877937316895,0.949746310710907,-0.30545300245285034,-0.006070674397051334,0.09453069418668747,0.9474838376045227,-0.33070167899131775,0.013530224561691284,0.06105412542819977,0.9416611790657043,-0.3546501696109772,0.0310618095099926,0.03078659623861313,0.9339756965637207,-0.3786012530326843,0.04546065255999565,0.005920100957155228,0.9244238138198853,-0.40109512209892273,0.05353640764951706,-0.008158639073371887,0.9144342541694641,-0.4058029055595398,0.05227334797382355,-0.006929257418960333,0.9124382734298706,-0.4238695204257965,0.04659770801663399,0.0004568189906422049,0.9045236706733704,-0.4389505982398987,0.032430097460746765,0.02069447934627533,0.8976872563362122,-0.4206763803958893,0.024934018030762672,0.02917659282684326,0.9063986539840698,-0.40950635075569153,0.02761750854551792,0.02081136591732502,0.9116516709327698,-0.40679624676704407,0.03569795936346054,0.008339088410139084,0.9127830862998962,-0.3821167051792145,0.05285444110631943,-0.020179426297545433,0.9223806262016296,-0.35138675570487976,0.0767834335565567,-0.056894682347774506,0.9313402771949768,-0.3103986978530884,0.10014724731445312,-0.09693775326013565,0.9403330683708191,-0.348119854927063,0.11883889138698578,-0.12397785484790802,0.921585202217102,-0.4024578332901001,0.12294421344995499,-0.12946717441082,0.8978589177131653,-0.42632171511650085,0.11977320164442062,-0.123592808842659,0.8880478739738464,-0.4473222494125366,0.11685837805271149,-0.11870677769184113,0.8787238597869873,-0.4648411273956299,0.11374816298484802,-0.11309441179037094,0.8707432746887207,-0.4734416902065277,0.1115303784608841,-0.1090330109000206,0.8669058680534363,-0.47446832060813904,0.10881265997886658,-0.10414593666791916,0.8672907948493958,-0.4737149178981781,0.1063058003783226,-0.09994123876094818,0.8685073852539062,-0.47465407848358154,0.1046539768576622,-0.09671255201101303,0.8685607314109802,-0.4774654805660248,0.10462456196546555,-0.09589718282222748,0.8671125769615173,-0.4804312288761139,0.10473944991827011,-0.09625937789678574,0.8654187917709351,-0.48360249400138855,0.10537711530923843,-0.09735690802335739,0.8634500503540039,-0.4862898886203766,0.10580366849899292,-0.09938584268093109,0.8616555333137512,-0.48709768056869507,0.10530336946249008,-0.09958864003419876,0.8612369894981384,-0.48664018511772156,0.10468980669975281,-0.1000111848115921,0.8615214228630066,-0.48914679884910583,0.10779273509979248,-0.10998313874006271,0.8584985733032227,-0.5004462003707886,0.09824472665786743,-0.09036661684513092,0.855415403842926,-0.49447014927864075,0.07223235815763474,-0.043345846235752106,0.8651028275489807,-0.4770677983760834,0.05340096354484558,-0.00235322886146605,0.877239465713501,-0.46021372079849243,0.07049351930618286,-0.026511183008551598,0.8846079111099243,-0.4565126299858093,0.07902326434850693,-0.04097309336066246,0.8852529525756836,-0.46828144788742065,0.07365155220031738,-0.032321494072675705,0.879910945892334,-0.4833937883377075,0.0668945461511612,-0.02165413647890091,0.8725747466087341,-0.5036944150924683,0.06187651678919792,-0.01395058911293745,0.8615501523017883,-0.5316265225410461,0.057428814470767975,-0.00790313258767128,0.8449927568435669,-0.5665370225906372,0.04837283864617348,0.0058577959425747395,0.822594404220581,-0.5952703356742859,0.04044633358716965,0.015849802643060684,0.8023504018783569,-0.6173319816589355,0.0313921757042408,0.027741150930523872,0.7855865359306335,-0.6324169039726257,0.017915168777108192,0.04764428362250328,0.7729540467262268,-0.6400444507598877,0.007539657410234213,0.06083047762513161,0.765889048576355,-0.6442947387695312,0.006896159145981073,0.055737417191267014,0.7627123594284058,-0.6446626782417297,0.005865845829248428,0.05349496006965637,0.7625706195831299,-0.6415515542030334,0.00514712231233716,0.0522942915558815,0.7652780413627625,-0.635970413684845,0.007308665197342634,0.0477648600935936,0.770199179649353,-0.6315909624099731,0.012279949150979519,0.03949521481990814,0.7741978168487549,-0.6359462738037109,0.017368538305163383,0.032143302261829376,0.7708680033683777,-0.651681661605835,0.020563939586281776,0.028247924521565437,0.7576874494552612,-0.6711803674697876,0.02159268409013748,0.02803165279328823,0.7404491901397705,-0.6828362941741943,0.022747643291950226,0.026782771572470665,0.7297258973121643,-0.6913858652114868,0.02344476990401745,0.02612052857875824,0.7216326594352722,-0.6933141946792603,0.02785627171397209,0.018711451441049576,0.7198536992073059,-0.6799776554107666,0.03971386328339577,-0.001675515086390078,0.7321546673774719,-0.6510988473892212,0.05197755619883537,-0.02100117690861225,0.7569198608398438,-0.6177079677581787,0.05911006033420563,-0.030557213351130486,0.7835873961448669,-0.5863003134727478,0.06111959367990494,-0.02948051691055298,0.807246744632721,-0.5501101613044739,0.058150697499513626,-0.020274722948670387,0.8328182697296143,-0.5102473497390747,0.052938587963581085,-0.006801906041800976,0.8583698868751526,-0.46614140272140503,0.04450510814785957,0.011232214979827404,0.8835186958312988,-0.41895347833633423,0.03563261777162552,0.03178584948182106,0.9067513346672058,-0.3728315830230713,0.027600811794400215,0.048588529229164124,0.926214873790741,-0.3311300277709961,0.02142510935664177,0.06196737661957741,0.9413043856620789,-0.2913389801979065,0.013945868238806725,0.07527543604373932,0.9535515904426575,-0.26776087284088135,-0.0031148032285273075,0.10232613980770111,0.958031177520752,-0.2656593918800354,-0.030785761773586273,0.14478260278701782,0.9526360034942627,-0.27966564893722534,-0.05690983682870865,0.18455727398395538,0.9404717087745667,-0.3047741949558258,-0.07686660438776016,0.21421433985233307,0.9248331785202026,-0.3359805941581726,-0.0903615951538086,0.23202931880950928,0.9083579778671265,-0.3725857436656952,-0.09702412784099579,0.23987004160881042,0.8911950588226318,-0.405061274766922,-0.09407463669776917,0.23288072645664215,0.8791142702102661,-0.43140825629234314,-0.08159053325653076,0.21190203726291656,0.8731136322021484,-0.4503074884414673,-0.06542346626520157,0.18290722370147705,0.8714860081672668,-0.4727201461791992,-0.04682353511452675,0.15242068469524384,0.8666667342185974,-0.5086374878883362,-0.03050014190375805,0.12459377199411392,0.8513718843460083,-0.5554764866828918,-0.015709737315773964,0.10222619771957397,0.825075089931488,-0.608658492565155,-0.004981857258826494,0.08375412970781326,0.7889837026596069,-0.6618933081626892,0.0019737458787858486,0.07025539129972458,0.7462958693504333,-0.6843101382255554,0.017846690490841866,0.0375695563852787,0.7280039191246033,-0.69056236743927,0.034715283662080765,0.005637359339743853,0.7224172353744507,-0.6941898465156555,0.045981261879205704,-0.013213329017162323,0.7182002067565918,-0.6970438957214355,0.05130011960864067,-0.020947927609086037,0.7148841619491577,-0.7011093497276306,0.04870299622416496,-0.016349270939826965,0.7112007141113281,-0.70326167345047,0.04442945495247841,-0.009019090794026852,0.7094841003417969,-0.7031165957450867,0.040285248309373856,-0.0020644289907068014,0.7099295258522034,-0.7009684443473816,0.03648713603615761,0.004195593297481537,0.7122460603713989,-0.6969828009605408,0.033173274248838425,0.009890743531286716,0.7162518501281738,-0.6912943124771118,0.0303385928273201,0.014911018311977386,0.7217821478843689,-0.6841534376144409,0.028142670169472694,0.01899646408855915,0.7285473942756653,-0.6763358116149902,0.026504794135689735,0.022254493087530136,0.7357800006866455,-0.6683703660964966,0.025432411581277847,0.024613063782453537,0.7429862022399902,-0.660788357257843,0.024898968636989594,0.026036962866783142,0.7497072219848633,-0.6541770100593567,0.024802105501294136,0.02666543982923031,0.7554643154144287,-0.6491503715515137,0.025118034332990646,0.02644260972738266,0.7597852945327759,-0.6451189517974854,0.02403915300965309,0.0266580730676651,0.7632384896278381],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998211860657,1,1,0.9999998807907104,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.0000022019873995304806,13.54739761352539,-0.000002248606506327633,0.000006142527126939967,13.54739761352539,-0.000006291230420174543],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[-0.30788561701774597,0.014501919038593769,0.004691845737397671,0.9513012766838074,-0.30788561701774597,0.014501911588013172,0.004691824782639742,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998807907104,1,1.0000001192092896,1,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,5.4666666984558105],"values":[2.8339996337890625,7.754294395446777,-6.139594078063965,2.8339834213256836,7.754300117492676,-6.139595985412598],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[-0.3320007026195526,-0.09571805596351624,-0.11373621225357056,0.9314922094345093,-0.33200064301490784,-0.09571804106235504,-0.11373621225357056,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,1,1.0000001192092896,0.9999998211860657,0.9999999403953552,0.9999999403953552],"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,5.4666666984558105],"values":[9.527432780487288e-7,10.288599967956543,1.4505384626772866e-7,0.000013171695172786713,10.288603782653809,0.0000010316732641513227],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[-1.3751559002628255e-8,-3.405145720947189e-9,9.909854270517826e-9,1,-1.3984389646282125e-8,-9.022178559803251e-10,9.70612745732069e-9,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,1,0.9999999403953552,1.0000001192092896,1,1],"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,5.4666666984558105],"values":[-2.418855729047209e-7,10.288612365722656,-7.065179374876607e-8,0.00000895751600182848,10.288619041442871,-0.00000302976854982262],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.013359828852117062,-0.015478933230042458,-0.005288835149258375,0.9997769594192505,0.01330641470849514,-0.015500426292419434,-0.005022819619625807,0.999778687953949,0.013084730133414268,-0.015186093747615814,-0.004483753349632025,0.9997890591621399,0.012720532715320587,-0.014583970420062542,-0.0037645769771188498,0.9998056888580322,0.012219220399856567,-0.013736113905906677,-0.002882494358345866,0.9998268485069275,0.011586589738726616,-0.01268973108381033,-0.0018560467287898064,0.9998506307601929,0.01082330010831356,-0.011488298885524273,-0.0007206828449852765,0.9998751878738403,0.009905771352350712,-0.010146040469408035,0.00043847880442626774,0.9998993277549744,0.008804012089967728,-0.008671573363244534,0.0015630638226866722,0.9999223947525024,0.007487796247005463,-0.0070853508077561855,0.0026607224717736244,0.9999433159828186,0.0058188787661492825,-0.005390480626374483,0.0034718429669737816,0.9999625086784363,0.00392505107447505,-0.0035117738880217075,0.0037907662335783243,0.9999789595603943,0.0019263492431491613,-0.0015013142256066203,0.003969763405621052,0.9999891519546509,-0.00022146393894217908,0.0005839504301548004,0.003911925479769707,0.9999921321868896,-0.0022000831086188555,0.0027012841310352087,0.003129924414679408,0.9999890327453613,-0.0033568795770406723,0.004681439604610205,0.001480096485465765,0.9999822974205017,-0.0040151155553758144,0.006620678585022688,-0.0002683847851585597,0.999970018863678,-0.004552132915705442,0.00847677979618311,-0.0021782757248729467,0.9999513626098633,-0.005702775903046131,0.01015089824795723,-0.004142654128372669,0.9999236464500427,-0.00700746662914753,0.0115460604429245,-0.005786961875855923,0.9998920559883118,-0.008077817969024181,0.012508343905210495,-0.007027065847069025,0.9998644590377808,-0.008654193952679634,0.012891746126115322,-0.0076882001012563705,0.9998499155044556,-0.009076775051653385,0.012404057197272778,-0.00799900759011507,0.9998499155044556,-0.00960951205343008,0.011224130168557167,-0.007900148630142212,0.999859631061554,-0.010237093083560467,0.009898788295686245,-0.007926778867840767,0.9998672008514404,-0.011022522114217281,0.008608483709394932,-0.008268440142273903,0.9998679757118225,-0.012051052413880825,0.007608063519001007,-0.008463628590106964,0.9998626708984375,-0.012998513877391815,0.006888710893690586,-0.00846048817038536,0.9998559951782227,-0.013700996525585651,0.006455674767494202,-0.008251551538705826,0.9998512268066406,-0.014143074862658978,0.006297002546489239,-0.008491888642311096,0.9998441338539124,-0.01435883343219757,0.00630895746871829,-0.008918940089643002,0.9998372197151184,-0.014509203843772411,0.0065184300765395164,-0.009218902327120304,0.9998310208320618,-0.014434839598834515,0.006412516348063946,-0.009534912183880806,0.9998297691345215,-0.01431200560182333,0.006535160820931196,-0.009947676211595535,0.9998267889022827,-0.014271171763539314,0.006700220052152872,-0.010428300127387047,0.9998213052749634,-0.01387264858931303,0.007088300306349993,-0.01071761641651392,0.9998211860656738,-0.012615494430065155,0.007975228130817413,-0.011428509838879108,0.9998233318328857,-0.010139405727386475,0.008501851931214333,-0.012882820330560207,0.9998294711112976,-0.005886927712708712,0.008214286528527737,-0.013544281013309956,0.9998571872711182,-0.00027909904019907117,0.008120954036712646,-0.013082152232527733,0.9998814463615417,0.006441256031394005,0.008328303694725037,-0.014837989583611488,0.9998344779014587,0.01248914748430252,0.009191842749714851,-0.017014002427458763,0.9997349977493286,0.01534439716488123,0.012985656969249249,-0.018125340342521667,0.9996336102485657,0.015354625880718231,0.016163639724254608,-0.017318377271294594,0.9996014833450317,0.014883141964673996,0.01857159473001957,-0.015960190445184708,0.9995893836021423,0.014326059259474277,0.017972147092223167,-0.01428334228694439,0.9996337890625,0.013836818747222424,0.01870807632803917,-0.012288239784538746,0.999653697013855,0.013367222622036934,0.021198661997914314,-0.01004534587264061,0.9996354579925537,0.012884153984487057,0.020978152751922607,-0.0075970604084432125,0.9996680617332458,0.012177221477031708,0.013740645721554756,-0.004954969044774771,0.9998191595077515,0.010698088444769382,-0.0011086564045399427,-0.0004320419975556433,0.9999420642852783,0.008416327647864819,-0.01961275190114975,0.0048834942281246185,0.9997603297233582,0.006162635516375303,-0.032880283892154694,0.008900664746761322,0.9994006752967834,0.0037970810662955046,-0.03894836828112602,0.011784212663769722,0.9991645216941833,0.0005029129679314792,-0.04040391743183136,0.014615838415920734,0.9990764260292053,-0.0037872071843594313,-0.041009899228811264,0.017331523820757866,0.9990012645721436,-0.007709173951297998,-0.041494257748126984,0.019290870055556297,0.9989227652549744,-0.011608483269810677,-0.04147593304514885,0.020390009507536888,0.9988639950752258,-0.015177972614765167,-0.040186069905757904,0.022120783105492592,0.9988320469856262,-0.01758306659758091,-0.03818716108798981,0.02196510136127472,0.9988744258880615,-0.019899671897292137,-0.03632081672549248,0.020106274634599686,0.9989397525787354,-0.021790122613310814,-0.035054851323366165,0.017504094168543816,0.9989944696426392,-0.022747276350855827,-0.034105755388736725,0.01467458438128233,0.999051570892334,-0.022554732859134674,-0.032325517386198044,0.01199254859238863,0.9991508722305298,-0.021163515746593475,-0.03193484991788864,0.009221455082297325,0.9992233514785767,-0.01953834854066372,-0.03386973217129707,0.006430966779589653,0.9992145895957947,-0.018158206716179848,-0.034968052059412,0.004684909712523222,0.9992125034332275,-0.016946766525506973,-0.03471485897898674,0.00415103230625391,0.9992449283599854,-0.01562654972076416,-0.03415584936738014,0.003946366254240274,0.9992865324020386,-0.014350409619510174,-0.03380037844181061,0.003237906377762556,0.9993203282356262,-0.01354325469583273,-0.03361211344599724,0.002585139125585556,0.9993398189544678,-0.01307933870702982,-0.03339896351099014,0.0019666345324367285,0.9993546009063721,-0.012421115301549435,-0.033098671585321426,0.000254761369433254,0.9993749260902405,-0.011221740394830704,-0.0331316776573658,-0.0019861143082380295,0.9993860721588135,-0.009367137216031551,-0.033748216927051544,-0.004099250305444002,0.9993780851364136,-0.00687593687325716,-0.034856073558330536,-0.006415607873350382,0.9993481040000916,-0.003318242495879531,-0.03639877960085869,-0.007550494745373726,0.9993033409118652,0.0009884778410196304,-0.037766795605421066,-0.006446389947086573,0.9992653131484985,0.006002980284392834,-0.03949547931551933,-0.005112829152494669,0.9991886019706726,0.011291506700217724,-0.04105498641729355,-0.0028850026428699493,0.9990889430046082,0.01649366319179535,-0.04184599593281746,0.000049439124268246815,0.9989879131317139,0.01998928189277649,-0.041612934321165085,0.0022760764695703983,0.9989312291145325,0.02424371801316738,-0.03876881301403046,0.004599371459335089,0.9989435076713562,0.0278109572827816,-0.03396284952759743,0.0060340953059494495,0.9990178346633911,0.0284692645072937,-0.032912757247686386,0.006374860182404518,0.9990323185920715,0.02848965860903263,-0.03276868909597397,0.006286810152232647,0.9990370869636536,0.028500786051154137,-0.03254224732518196,0.006512243300676346,0.9990427494049072,0.028469575569033623,-0.029591701924800873,0.005385596305131912,0.999142050743103,0.02841836027801037,-0.018902365118265152,0.0039697205647826195,0.9994094967842102,0.028380364179611206,-0.005698053631931543,0.0025821030139923096,0.9995776414871216,0.028489142656326294,0.004507402889430523,0.0010767014464363456,0.9995834231376648,0.027703769505023956,0.011532996781170368,-0.0008847204153425992,0.9995492696762085,0.023770906031131744,0.017979610711336136,-0.0033488345798105,0.9995501041412354,0.018222227692604065,0.02579684928059578,-0.006034905090928078,0.9994829297065735,0.01171206496655941,0.03272327035665512,-0.00889592245221138,0.9993562698364258,0.004954440053552389,0.037832099944353104,-0.010941123589873314,0.9992119669914246,-0.0001690960198175162,0.038938019424676895,-0.011419541202485561,0.9991763830184937,-0.004254542291164398,0.03855285421013832,-0.01087435893714428,0.9991883039474487,-0.007722892798483372,0.03904389962553978,-0.009929045103490353,0.9991583228111267,-0.00989394448697567,0.04094178229570389,-0.009765489026904106,0.9990648627281189,-0.01174546405673027,0.04409858584403992,-0.010117576457560062,0.9989069104194641,-0.013773688115179539,0.04658668488264084,-0.01012125238776207,0.9987679719924927,-0.01549848448485136,0.04822961613535881,-0.01008041761815548,0.9986652135848999,-0.016789628192782402,0.049443766474723816,-0.009470364078879356,0.998590886592865,-0.017799513414502144,0.0495501309633255,-0.008636502549052238,0.9985756874084473,-0.01837984099984169,0.048819925636053085,-0.006804666947573423,0.9986152648925781,-0.018777230754494667,0.047227468341588974,-0.0042192367836833,0.9986987709999084,-0.018675299361348152,0.045096494257450104,-0.0014232133980840445,0.9988070726394653,-0.018488872796297073,0.04196520522236824,0.00008947862806962803,0.9989480376243591,-0.01892675831913948,0.03889040648937225,0.0005768099217675626,0.9990640878677368,-0.018423091620206833,0.03399329259991646,0.0012850075727328658,0.9992514252662659,-0.014529692009091377,0.02705843560397625,0.0026403649244457483,0.9995247721672058,-0.007759776432067156,0.022322572767734528,0.00099084060639143,0.9997202157974243,-0.0010571421589702368,0.017953716218471527,-0.002851020311936736,0.9998341798782349,0.0042282091453671455,0.013488702476024628,-0.005451599135994911,0.9998852610588074,0.0076952772215008736,0.013503696769475937,-0.004852623213082552,0.9998674392700195,0.009926955215632915,0.01762147806584835,-0.0018017510883510113,0.9997938275337219,0.010748572647571564,0.019289594143629074,0.0033957655541598797,0.9997503757476807,0.011043528094887733,0.017395474016666412,0.009286551736295223,0.9997445344924927,0.01137940026819706,0.011225652880966663,0.013663912191987038,0.9997788667678833,0.011200523003935814,-0.0013925818493589759,0.017059557139873505,0.9997907876968384,0.009047094732522964,-0.015263686887919903,0.019776754081249237,0.9996469616889954,0.00533329276368022,-0.02249149978160858,0.02119562402367592,0.9995080828666687,0.0009636392933316529,-0.024784434586763382,0.02156396210193634,0.9994598031044006,-0.0032055650372058153,-0.028199350461363792,0.021372932940721512,0.9993686676025391,-0.006637750659137964,-0.03135763108730316,0.021865710616111755,0.9992470145225525,-0.010299943387508392,-0.03265753388404846,0.022974485531449318,0.9991494417190552,-0.01407657004892826,-0.03286335989832878,0.023175906389951706,0.9990919828414917,-0.01647336781024933,-0.032388847321271896,0.023062532767653465,0.9990734457969666,-0.018276814371347427,-0.03125133365392685,0.02207707054913044,0.9991005659103394,-0.018902989104390144,-0.030271856114268303,0.01973540522158146,0.9991680383682251,-0.01885780319571495,-0.02934861183166504,0.01716241054236889,0.999243974685669,-0.018573762848973274,-0.02839795872569084,0.014838401228189468,0.9993139505386353,-0.01790105551481247,-0.0274623092263937,0.012445884756743908,0.9993850588798523,-0.01726081781089306,-0.026536043733358383,0.01041279174387455,0.9994446039199829,-0.016786299645900726,-0.02564758062362671,0.008849387057125568,0.9994909167289734,-0.01579553820192814,-0.02483142726123333,0.007424949202686548,0.999539315700531,-0.014412406831979752,-0.024060899391770363,0.006158414762467146,0.9995876550674438,-0.013037528842687607,-0.023208176717162132,0.004636798985302448,0.9996349215507507,-0.01166189182549715,-0.022352667525410652,0.002982938662171364,0.9996777176856995,-0.010280088521540165,-0.02155512012541294,0.001190871000289917,0.9997140765190125,-0.008873467333614826,-0.020841725170612335,0.0006087454967200756,0.9997432231903076,-0.007452246267348528,-0.02022668346762657,-0.0003601881326176226,0.9997676014900208,-0.006008564960211515,-0.019722798839211464,-0.0015314648626372218,0.9997862577438354,-0.004535117652267218,-0.01930164359509945,-0.0026133530773222446,0.9998000264167786,-0.002242809161543846,-0.018924862146377563,-0.003561721183359623,0.9998120665550232,0.0007928208215162158,-0.018577270209789276,-0.004337936174124479,0.9998177289962769,0.003657449036836624,-0.018210837617516518,-0.00467620138078928,0.9998165965080261,0.006175356917083263,-0.017797410488128662,-0.004748369567096233,0.9998112916946411,0.00795435719192028,-0.01738022081553936,-0.004590456839650869,0.9998067617416382,0.009556056000292301,-0.01698007807135582,-0.0043714577332139015,0.9998006224632263,0.010923024266958237,-0.01663926988840103,-0.004020086023956537,0.9997938275337219,0.011418023146688938,-0.016380684450268745,-0.0037785908207297325,0.999793529510498,0.0116433622315526,-0.016178425401449203,-0.003676816588267684,0.9997945427894592,0.011880243197083473,-0.016016747802495956,-0.003685845760628581,0.9997943639755249,0.012121315114200115,-0.015886683017015457,-0.0037709844764322042,0.9997932314872742,0.012359974905848503,-0.015780774876475334,-0.003908380400389433,0.9997914433479309,0.01258912868797779,-0.015742046758532524,-0.0040858774445950985,0.9997885227203369,0.012801872566342354,-0.015752464532852173,-0.004272032529115677,0.9997848868370056,0.012991419993340969,-0.015761351212859154,-0.004459948278963566,0.9997814893722534,0.01315086055546999,-0.015769299119710922,-0.0046441564336419106,0.9997783899307251,0.013273081742227077,-0.01577186770737171,-0.004820512142032385,0.9997759461402893,0.013351245783269405,-0.01576903834939003,-0.005003759171813726,0.9997740387916565,0.013350371271371841,-0.015725016593933105,-0.005240507889539003,0.9997735023498535],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,0.9999998807907104,0.9999998807907104,1,0.9999998807907104,0.9999997615814209],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,5.4666666984558105],"values":[4.366400241851807,7.5629048347473145,-0.17680153250694275,4.36640739440918,7.5629072189331055,-0.17679986357688904],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.6865840554237366,0.4868810176849365,-0.2899816036224365,0.45547768473625183,0.6849735379219055,0.4872973561286926,-0.2912711799144745,0.45663297176361084,0.6804377436637878,0.48860442638397217,-0.29488393664360046,0.4596886932849884,0.6762505769729614,0.4851396977901459,-0.29821595549583435,0.46732422709465027,0.6742478609085083,0.475478857755661,-0.29930248856544495,0.47929930686950684,0.6720565557479858,0.46435976028442383,-0.2995917797088623,0.4929044544696808,0.6675388813018799,0.45658284425735474,-0.30075058341026306,0.5054434537887573,0.6587339043617249,0.45693665742874146,-0.30467382073402405,0.5142493844032288,0.6442521214485168,0.4678474962711334,-0.3094344735145569,0.5199117660522461,0.6235338449478149,0.48645633459091187,-0.3128141462802887,0.5260352492332458,0.5968360304832458,0.5101069808006287,-0.31563761830329895,0.5328702330589294,0.5666519403457642,0.5373812317848206,-0.31671518087387085,0.5383480191230774,0.5343395471572876,0.5661393404006958,-0.3160600960254669,0.5422854423522949,0.5012359023094177,0.594252347946167,-0.31402304768562317,0.5449920296669006,0.4690863788127899,0.6199322938919067,-0.31095337867736816,0.5467632412910461,0.44015026092529297,0.6418610215187073,-0.3070351481437683,0.5477331876754761,0.41261836886405945,0.6613505482673645,-0.2951119542121887,0.5525129437446594,0.39046213030815125,0.6772887110710144,-0.28885573148727417,0.5526135563850403,0.36999619007110596,0.6918332576751709,-0.2848566472530365,0.550750732421875,0.35204270482063293,0.7041353583335876,-0.28071996569633484,0.5490497946739197,0.3359711766242981,0.716218113899231,-0.2780417203903198,0.5448374152183533,0.32439762353897095,0.7242188453674316,-0.2743978202342987,0.5431198477745056,0.3168163597583771,0.727344810962677,-0.270216166973114,0.5455090999603271,0.308506578207016,0.7308029532432556,-0.2694944441318512,0.5460068583488464,0.29898375272750854,0.7340490818023682,-0.2713199853897095,0.5460458993911743,0.2911531627178192,0.7371953129768372,-0.2771880328655243,0.5430836081504822,0.28553932905197144,0.7382951974868774,-0.2862747013568878,0.5398465991020203,0.2834615111351013,0.7363111972808838,-0.29741808772087097,0.5376224517822266,0.28398314118385315,0.7343980073928833,-0.3079700171947479,0.5340109467506409,0.28344619274139404,0.7339403629302979,-0.316038578748703,0.5301975607872009,0.28391382098197937,0.733686089515686,-0.32262086868286133,0.5263206958770752,0.2829015254974365,0.7350332736968994,-0.3270023465156555,0.5222664475440979,0.28256314992904663,0.7360882759094238,-0.33097943663597107,0.5184445381164551,0.2830890715122223,0.7368371486663818,-0.3347870409488678,0.5146351456642151,0.28341013193130493,0.7374317049980164,-0.33886969089508057,0.5109213590621948,0.28346237540245056,0.7391366958618164,-0.3426758646965027,0.5058649182319641,0.28821858763694763,0.7401382923126221,-0.3451419770717621,0.5000024437904358,0.299121230840683,0.7396302819252014,-0.34362661838531494,0.4953728914260864,0.3194664418697357,0.7371739745140076,-0.33529722690582275,0.49202799797058105,0.35143229365348816,0.7288230061531067,-0.32360967993736267,0.4904988706111908,0.3947383165359497,0.7160714864730835,-0.3094619810581207,0.48544472455978394,0.43939149379730225,0.7045077681541443,-0.3009255826473236,0.4690925180912018,0.4783739447593689,0.6894536018371582,-0.29992756247520447,0.4537131190299988,0.5097169876098633,0.6709706783294678,-0.30565890669822693,0.44335055351257324,0.5367027521133423,0.655681848526001,-0.3134072422981262,0.4287277162075043,0.5658226013183594,0.6355950236320496,-0.31503257155418396,0.42025983333587646,0.5926639437675476,0.6162509322166443,-0.3133060932159424,0.4133082926273346,0.6160088777542114,0.6017261743545532,-0.3087141215801239,0.40392372012138367,0.6378986835479736,0.5850030183792114,-0.29852521419525146,0.40216848254203796,0.663716733455658,0.5547239184379578,-0.27145716547966003,0.42198649048805237,0.6882908940315247,0.5085313320159912,-0.2200489193201065,0.4682201147079468,0.7074505090713501,0.4435882270336151,-0.14801663160324097,0.5299381017684937,0.7160575985908508,0.3806021213531494,-0.07829815149307251,0.5798906087875366,0.7134422063827515,0.3388301432132721,-0.027982136234641075,0.6127082705497742,0.6977106928825378,0.32860031723976135,-0.0005086649325676262,0.6365700364112854,0.6751887202262878,0.33964329957962036,0.01037887018173933,0.6547175049781799,0.648601770401001,0.3541281521320343,0.0088351396843791,0.6736698746681213,0.6177900433540344,0.3702148199081421,-0.0013840358005836606,0.6937395930290222,0.5835495591163635,0.39087384939193726,-0.015251162461936474,0.7116564512252808,0.5541669726371765,0.40945810079574585,-0.03195124864578247,0.724031925201416,0.5212846398353577,0.4381732940673828,-0.056549180299043655,0.730115532875061,0.47798195481300354,0.47981730103492737,-0.09865210950374603,0.7290928363800049,0.42642998695373535,0.5258774757385254,-0.1493002474308014,0.7206384539604187,0.37037500739097595,0.5732285976409912,-0.2002476304769516,0.702945351600647,0.3233335316181183,0.6097481846809387,-0.2403789460659027,0.6825544834136963,0.30629289150238037,0.6252004504203796,-0.2527541220188141,0.6718812584877014,0.3032897710800171,0.6327062845230103,-0.2576080560684204,0.6643312573432922,0.30532345175743103,0.6376048922538757,-0.2622945308685303,0.6568403244018555,0.3087693452835083,0.641926646232605,-0.27022555470466614,0.6477420926094055,0.3149934709072113,0.6429904699325562,-0.2748963534832001,0.6416964530944824,0.3222987949848175,0.640516996383667,-0.27800512313842773,0.639198362827301,0.3276750147342682,0.6381261944770813,-0.2801710367202759,0.6379092931747437,0.33277761936187744,0.6371641159057617,-0.2803225517272949,0.6361605525016785,0.34145602583885193,0.6350791454315186,-0.27829664945602417,0.6345338821411133,0.354549378156662,0.630405068397522,-0.2724337875843048,0.6345580816268921,0.3738141655921936,0.6216582655906677,-0.2619710862636566,0.6365336775779724,0.40030595660209656,0.6076917052268982,-0.24646684527397156,0.6400937438011169,0.42952534556388855,0.5927559733390808,-0.22948290407657623,0.6414717435836792,0.45725125074386597,0.5789810419082642,-0.2043236792087555,0.6433926820755005,0.4837660491466522,0.5677025318145752,-0.17359015345573425,0.6430790424346924,0.5292702913284302,0.5281400680541992,-0.1148790568113327,0.6540212631225586,0.5757596492767334,0.4500693678855896,-0.04883808270096779,0.6808474659919739,0.6191776394844055,0.37005436420440674,0.007241426967084408,0.6925507187843323,0.6458232402801514,0.3149771988391876,0.03653552010655403,0.6945264339447021,0.653904139995575,0.2982454299926758,0.04298730939626694,0.6939821839332581,0.6560878157615662,0.3139956593513489,0.03333409130573273,0.6854520440101624,0.6611741185188293,0.34357231855392456,0.012548409402370453,0.6668204665184021,0.669121503829956,0.388117253780365,-0.021042542532086372,0.633402407169342,0.6736007928848267,0.45506247878074646,-0.07325095683336258,0.5777668356895447,0.673342227935791,0.5169270634651184,-0.132775217294693,0.5116320848464966,0.6718971133232117,0.5517452359199524,-0.18669654428958893,0.45746681094169617,0.6610834002494812,0.5706700086593628,-0.22843295335769653,0.43025925755500793,0.6422671675682068,0.5815021991729736,-0.2694419324398041,0.420415461063385,0.6222371459007263,0.5863934755325317,-0.30994436144828796,0.4158101975917816,0.6000896096229553,0.5899389386177063,-0.34965676069259644,0.41183099150657654,0.5734879374504089,0.5969536304473877,-0.38583433628082275,0.4072958827018738,0.5487790703773499,0.6038464903831482,-0.4100654125213623,0.4075014889240265,0.5194191336631775,0.6184704303741455,-0.41062065958976746,0.4231889247894287,0.48920586705207825,0.6318398118019104,-0.41383370757102966,0.4361167848110199,0.4619888365268707,0.6417213678359985,-0.42860591411590576,0.43710070848464966,0.43903377652168274,0.6500579118728638,-0.44359341263771057,0.43347325921058655,0.4247826635837555,0.6494913697242737,-0.4626559615135193,0.4285675883293152,0.41605451703071594,0.6446371674537659,-0.4821833372116089,0.4228958189487457,0.4125637710094452,0.6368860006332397,-0.5022592544555664,0.4146119952201843,0.41182824969291687,0.628032922744751,-0.517768919467926,0.40974095463752747,0.4110007584095001,0.6230509877204895,-0.5241345763206482,0.41008397936820984,0.4117296636104584,0.6191619634628296,-0.524506151676178,0.4147416055202484,0.41922837495803833,0.6140264868736267,-0.5215985774993896,0.4185141623020172,0.4344196617603302,0.604203999042511,-0.5174025297164917,0.42250654101371765,0.45558881759643555,0.5867422819137573,-0.520118772983551,0.42148417234420776,0.485901802778244,0.5617692470550537,-0.523345947265625,0.4176408648490906,0.5271710753440857,0.5485105514526367,-0.5036190152168274,0.40938353538513184,0.5672492980957031,0.5480037331581116,-0.47379425168037415,0.39171308279037476,0.5948134660720825,0.548418402671814,-0.44785967469215393,0.38059934973716736,0.6119877696037292,0.5487123727798462,-0.4267878830432892,0.3771444261074066,0.6290397047996521,0.5442653894424438,-0.41013598442077637,0.37399569153785706,0.6689468026161194,0.5160244703292847,-0.3815946578979492,0.37498602271080017,0.7142852544784546,0.4723420739173889,-0.3343574106693268,0.39356666803359985,0.7439388036727905,0.43796971440315247,-0.2794823944568634,0.42027032375335693,0.7545139789581299,0.42446112632751465,-0.2385108470916748,0.44006142020225525,0.7518400549888611,0.4223134219646454,-0.19715286791324615,0.4663890302181244,0.7321789860725403,0.425430566072464,-0.15862944722175598,0.5077002048492432,0.6901554465293884,0.44639015197753906,-0.14510391652584076,0.5507869720458984,0.6338145732879639,0.4782712459564209,-0.1467290073633194,0.5899207592010498,0.5717381238937378,0.5034731030464172,-0.14638295769691467,0.6310328841209412,0.5034261345863342,0.5284392833709717,-0.15053164958953857,0.6668240427970886,0.43795934319496155,0.5579829216003418,-0.15663373470306396,0.6872499585151672,0.3892844021320343,0.5934330821037292,-0.1712794005870819,0.6833434104919434,0.339376300573349,0.637096643447876,-0.19015540182590485,0.6654115319252014,0.2947734594345093,0.6777964234352112,-0.2107655256986618,0.6397488713264465,0.2612406015396118,0.7091895937919617,-0.2297837734222412,0.6131907105445862,0.2383021116256714,0.7318924069404602,-0.2515518367290497,0.5867429375648499,0.23002026975154877,0.7418431639671326,-0.2648363709449768,0.5715078115463257,0.23503653705120087,0.7441726922988892,-0.2739107608795166,0.56208336353302,0.2429914027452469,0.7435976266860962,-0.28170379996299744,0.5555724501609802,0.2536264955997467,0.7405534386634827,-0.2884787917137146,0.5513929724693298,0.265750527381897,0.7372880578041077,-0.2945977747440338,0.5468044281005859,0.27966421842575073,0.734468936920166,-0.30086833238601685,0.5402051210403442,0.3037255108356476,0.7246228456497192,-0.3093127906322479,0.5357221364974976,0.3345230519771576,0.7069693207740784,-0.31325334310531616,0.5386660099029541,0.36327558755874634,0.6878190040588379,-0.3177187442779541,0.5422091484069824,0.3933698236942291,0.6666248440742493,-0.32415011525154114,0.5438733696937561,0.4244796931743622,0.6449033617973328,-0.33030176162719727,0.5429710149765015,0.4542253017425537,0.6241211295127869,-0.3334473967552185,0.5412624478340149,0.4841659963130951,0.6033322215080261,-0.3346318304538727,0.5381404161453247,0.5159656405448914,0.5819817781448364,-0.332584023475647,0.5333521962165833,0.5502800345420837,0.5595855712890625,-0.3249080777168274,0.527722179889679,0.5863907933235168,0.5375909209251404,-0.31453344225883484,0.5178905725479126,0.6182615756988525,0.5201137661933899,-0.3057207465171814,0.5037551522254944,0.6432666182518005,0.5066412687301636,-0.2979813516139984,0.4906422793865204,0.6592115163803101,0.4979328215122223,-0.29236915707588196,0.48168817162513733,0.6689376831054688,0.49292734265327454,-0.29178574681282043,0.47371530532836914,0.6767423748970032,0.48828738927841187,-0.2946522831916809,0.4655913710594177,0.6819244623184204,0.4845973551273346,-0.29718613624572754,0.460244357585907,0.6852949857711792,0.48239055275917053,-0.2983078956604004,0.45681795477867126,0.687792956829071,0.4809437692165375,-0.29891249537467957,0.45418646931648254,0.6895393133163452,0.4801337420940399,-0.2991167902946472,0.4522568881511688,0.6898349523544312,0.480759859085083,-0.2977769076824188,0.4520251154899597,0.6891888976097107,0.48224055767059326,-0.29559823870658875,0.4528624415397644,0.6883660554885864,0.4837753474712372,-0.29355067014694214,0.4538078308105469,0.6876248717308044,0.48508399724960327,-0.29193416237831116,0.4545768201351166,0.6870537996292114,0.4860454499721527,-0.29084768891334534,0.4551093876361847,0.686690092086792,0.4866415560245514,-0.29022911190986633,0.45541614294052124,0.6865713596343994,0.4868444800376892,-0.2900189459323883,0.45551207661628723],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999997615814209,0.9999997019767761,0.9999999403953552,0.9999997615814209,0.9999997615814209,0.9999998211860657],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.0000033622595765336882,17.63079261779785,-0.000010659162398951594,-0.0000031724719065095996,17.630800247192383,0.000012961847460246645],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.44705018401145935,-0.25238683819770813,0.5476879477500916,0.6606701612472534,0.4702353775501251,-0.26250362396240234,0.5582138895988464,0.6311639547348022,0.4817350208759308,-0.2746615707874298,0.5618649125099182,0.6138405799865723,0.47437766194343567,-0.286897212266922,0.556296706199646,0.6190233826637268,0.4488687813282013,-0.29950106143951416,0.5420964956283569,0.6441641449928284,0.4171404540538788,-0.31491395831108093,0.5262686014175415,0.6707193851470947,0.3821814954280853,-0.3367953300476074,0.5189580917358398,0.6864319443702698,0.34703102707862854,-0.3640921115875244,0.5225620269775391,0.6884296536445618,0.3043985962867737,-0.3945557475090027,0.5351095199584961,0.6821474432945251,0.2537183165550232,-0.42483988404273987,0.5446425080299377,0.677128255367279,0.20244015753269196,-0.45165592432022095,0.5435139536857605,0.6779510378837585,0.16133689880371094,-0.4724051356315613,0.5301146507263184,0.6854066848754883,0.13285107910633087,-0.4876365065574646,0.5103099942207336,0.6958052515983582,0.11344438791275024,-0.4992213845252991,0.4907931387424469,0.7050039768218994,0.0966629832983017,-0.5089598894119263,0.4724713861942291,0.7130126357078552,0.08759692311286926,-0.5202372074127197,0.4561060070991516,0.7166919112205505,0.09088034182786942,-0.5349141955375671,0.4350751042366028,0.7185521721839905,0.09801027923822403,-0.5542970299720764,0.4263623356819153,0.7080706357955933,0.10331045091152191,-0.5767310857772827,0.4255955219268799,0.6896207332611084,0.09550029039382935,-0.6013470888137817,0.42662563920021057,0.6687690019607544,0.072077676653862,-0.6264181733131409,0.43589723110198975,0.6421828269958496,0.04539210721850395,-0.6477711796760559,0.44367504119873047,0.6176443099975586,0.01656094565987587,-0.6642909646034241,0.4484294056892395,0.5977912545204163,-0.006714611779898405,-0.6748878359794617,0.4501268267631531,0.5846941471099854,-0.025883669033646584,-0.6793977618217468,0.44757717847824097,0.5808815360069275,-0.048729751259088516,-0.6802977323532104,0.4443013668060303,0.580875813961029,-0.06467000395059586,-0.6805106997489929,0.4454461932182312,0.5781874060630798,-0.07866626232862473,-0.6803757548332214,0.45085635781288147,0.5723890066146851,-0.09527482092380524,-0.680008053779602,0.45667925477027893,0.5656464695930481,-0.1143043264746666,-0.6793705224990845,0.46171650290489197,0.5587558150291443,-0.1383211314678192,-0.6782939434051514,0.46539878845214844,0.551533043384552,-0.15185216069221497,-0.6772774457931519,0.4704958498477936,0.5448578596115112,-0.16281306743621826,-0.6761364936828613,0.4769715666770935,0.537428617477417,-0.1736486256122589,-0.6748270392417908,0.4836518168449402,0.5296561121940613,-0.18325038254261017,-0.6734464764595032,0.49041733145713806,0.5219003558158875,-0.1901828795671463,-0.6725294589996338,0.5005981922149658,0.5108190178871155,-0.19042424857616425,-0.6741144061088562,0.5180032253265381,0.49089837074279785,-0.17519688606262207,-0.6774975657463074,0.5343999266624451,0.4740462303161621,-0.1378251165151596,-0.6824918985366821,0.5438310503959656,0.46846237778663635,-0.08299098908901215,-0.687326967716217,0.5441945195198059,0.4738633334636688,-0.016280146315693855,-0.6894594430923462,0.5374364852905273,0.4853275418281555,0.0667140781879425,-0.6856721639633179,0.5271693468093872,0.49748915433883667,0.16860243678092957,-0.6725291013717651,0.5067268013954163,0.5123533010482788,0.2907158434391022,-0.6440860629081726,0.4654785096645355,0.5328859090805054,0.4120924472808838,-0.5993484258651733,0.4301568865776062,0.5347208976745605,0.496948778629303,-0.556703507900238,0.39134481549263,0.5384907126426697,0.566343367099762,-0.5104618072509766,0.3419916331768036,0.5492956638336182,0.6284366250038147,-0.45508214831352234,0.2787157893180847,0.5659373998641968,0.640032947063446,-0.42622873187065125,0.2287747859954834,0.5969497561454773,0.6318390369415283,-0.40951868891716003,0.19377562403678894,0.6289076805114746,0.6275686621665955,-0.39536792039871216,0.17015305161476135,0.648760199546814,0.628791868686676,-0.3772417902946472,0.14972442388534546,0.6632435917854309,0.6225748062133789,-0.374615877866745,0.14707553386688232,0.6711425185203552,0.628609299659729,-0.38176703453063965,0.1705216020345688,0.6557642221450806,0.6162222623825073,-0.4123650789260864,0.2186611443758011,0.6343598961830139,0.5771750807762146,-0.45619112253189087,0.267901211977005,0.6220831274986267,0.5208257436752319,-0.501250147819519,0.3411417305469513,0.6009252667427063,0.4386742413043976,-0.5435887575149536,0.39511799812316895,0.5966221690177917,0.33329445123672485,-0.5840129256248474,0.4017006754875183,0.621675431728363,0.1929958462715149,-0.6057356595993042,0.38040095567703247,0.6716637015342712,0.0316753052175045,-0.5928773283958435,0.33784160017967224,0.7303124070167542,-0.09255394339561462,-0.5461626648902893,0.2909112870693207,0.780070960521698,-0.1691720187664032,-0.4955201745033264,0.2697557806968689,0.8081289529800415,-0.19630713760852814,-0.471335768699646,0.2737429738044739,0.8150895237922668,-0.1975780725479126,-0.46223095059394836,0.27339762449264526,0.8200970888137817,-0.20538051426410675,-0.4564618766307831,0.2595648765563965,0.8258858919143677,-0.21966111660003662,-0.45647934079170227,0.25794821977615356,0.8227018713951111,-0.2250620722770691,-0.4561537802219391,0.26112082600593567,0.8204185962677002,-0.24744543433189392,-0.4546545743942261,0.25576090812683105,0.8164841532707214,-0.24726547300815582,-0.45179983973503113,0.2535586655139923,0.8188068866729736,-0.2451484501361847,-0.4494988024234772,0.2539733648300171,0.820579469203949,-0.24310308694839478,-0.4492928385734558,0.26071128249168396,0.8191864490509033,-0.2395820915699005,-0.45110297203063965,0.2746809720993042,0.814651370048523,-0.23315343260765076,-0.4552246034145355,0.2992149293422699,0.8055312037467957,-0.22889481484889984,-0.45947960019111633,0.32310351729393005,0.7950407862663269,-0.23814913630485535,-0.46509793400764465,0.34474998712539673,0.7798181772232056,-0.2588459849357605,-0.473254919052124,0.37059342861175537,0.7561011910438538,-0.2761895954608917,-0.48354005813598633,0.4075985252857208,0.7237207889556885,-0.28038322925567627,-0.49245500564575195,0.4503708481788635,0.6899561285972595,-0.27070921659469604,-0.5008582472801208,0.504612922668457,0.6490172147750854,-0.23245839774608612,-0.5071320533752441,0.5646969079971313,0.6081922054290771,-0.11046648770570755,-0.5060861706733704,0.5932148098945618,0.6162549257278442,0.036976661533117294,-0.501416027545929,0.5764281153678894,0.644162654876709,0.197939932346344,-0.4783945381641388,0.5110375285148621,0.6861480474472046,0.3456282317638397,-0.4303165376186371,0.43586230278015137,0.710909903049469,0.44480472803115845,-0.40480175614356995,0.4106658399105072,0.6853013634681702,0.5272412300109863,-0.3985152542591095,0.35287776589393616,0.6623288989067078,0.6031287312507629,-0.3609172999858856,0.2598694860935211,0.6621497869491577,0.6765950322151184,-0.29760703444480896,0.1761142611503601,0.6501023173332214,0.718509316444397,-0.22861747443675995,0.13138186931610107,0.6435971260070801,0.7049135565757751,-0.18425209820270538,0.14684708416461945,0.6690171957015991,0.6201799511909485,-0.13165849447250366,0.1752016544342041,0.7532246112823486,0.5763062834739685,-0.0848483070731163,0.13710394501686096,0.8011706471443176,0.5706934928894043,-0.05981626361608505,0.07344206422567368,0.8156821131706238,0.5201826095581055,-0.05305089056491852,0.06397517025470734,0.8500016927719116,0.4690350890159607,-0.08359099924564362,0.13949425518512726,0.868078351020813,0.3833219110965729,-0.1496516615152359,0.2692462205886841,0.8707325458526611,0.28645962476730347,-0.21624311804771423,0.38102126121520996,0.852057933807373,0.19954517483711243,-0.27251410484313965,0.4671758711338043,0.8171074390411377,0.12263654172420502,-0.31948792934417725,0.5414209365844727,0.767952561378479,0.05890851095318794,-0.35215267539024353,0.5956423282623291,0.7195335626602173,0.021593715995550156,-0.3659072816371918,0.6394045948982239,0.6758753061294556,0.029146045446395874,-0.363704115152359,0.6719794273376465,0.6444482207298279,0.061281442642211914,-0.35060855746269226,0.7008095979690552,0.6182104349136353,0.0910002663731575,-0.3365299701690674,0.7188407778739929,0.6014436483383179,0.11503718793392181,-0.325804203748703,0.7266355156898499,0.5938172936439514,0.12224879115819931,-0.3126280903816223,0.726407527923584,0.599709153175354,0.10338164120912552,-0.29575783014297485,0.7183976173400879,0.6210833787918091,0.08170703798532486,-0.2763923406600952,0.69829922914505,0.6552171111106873,0.08257710188627243,-0.2576533257961273,0.6773531436920166,0.6840968728065491,0.07562092691659927,-0.23625299334526062,0.6616232991218567,0.7076162099838257,0.03769764304161072,-0.21435099840164185,0.6396364569664001,0.7372230291366577,0.014409901574254036,-0.1893932819366455,0.6002970337867737,0.7768951058387756,0.06822104752063751,-0.15739142894744873,0.5484912991523743,0.818371057510376,0.12660977244377136,-0.12268635630607605,0.5049600005149841,0.8449458479881287,0.13957585394382477,-0.10181134194135666,0.5170841813087463,0.8383179903030396,0.14384040236473083,-0.09273938089609146,0.5663003325462341,0.8062340021133423,0.20784597098827362,-0.05811310186982155,0.5733780860900879,0.7903546690940857,0.2783496677875519,-0.02088071219623089,0.5518031120300293,0.7858745455741882,0.3257027268409729,-0.028449153527617455,0.5589799284934998,0.7620038986206055,0.34829846024513245,-0.03813846409320831,0.556975245475769,0.7530021667480469,0.3319917917251587,-0.054388873279094696,0.5390127897262573,0.7721972465515137,0.2801275849342346,-0.10941880196332932,0.5612698793411255,0.7710591554641724,0.24248561263084412,-0.1722812056541443,0.6336929202079773,0.7141101360321045,0.1780632585287094,-0.22834697365760803,0.681599497795105,0.6719920635223389,0.07265256345272064,-0.26259344816207886,0.6776662468910217,0.6830335259437561,-0.03720904886722565,-0.2747325301170349,0.6299418807029724,0.7254728078842163,-0.13489489257335663,-0.2754914462566376,0.5739025473594666,0.7593048214912415,-0.2034817636013031,-0.26977330446243286,0.5324024558067322,0.7761219143867493,-0.2516084909439087,-0.2641904354095459,0.5089284181594849,0.7796720266342163,-0.28980007767677307,-0.2679000794887543,0.518970251083374,0.7582317590713501,-0.31149038672447205,-0.2741982340812683,0.5388039350509644,0.7331299185752869,-0.3083602786064148,-0.2757219076156616,0.5463138222694397,0.7283080816268921,-0.30441343784332275,-0.2788059115409851,0.5406000018119812,0.7330425977706909,-0.2924846410751343,-0.283521831035614,0.5332602858543396,0.7414186000823975,-0.2800331711769104,-0.2918398082256317,0.5272145867347717,0.7472990155220032,-0.26853588223457336,-0.30495917797088623,0.5296961069107056,0.7445202469825745,-0.2523820102214813,-0.32013148069381714,0.5348194241523743,0.7401266098022461,-0.24854502081871033,-0.3399752974510193,0.5411887168884277,0.7278440594673157,-0.24151258170604706,-0.3590896725654602,0.5471558570861816,0.7164822220802307,-0.2176682949066162,-0.3744952976703644,0.5560876131057739,0.7093239426612854,-0.1779472976922989,-0.3850677013397217,0.564619243144989,0.7079991102218628,-0.12892740964889526,-0.39076146483421326,0.5714119672775269,0.7100504040718079,-0.06372787058353424,-0.3880336284637451,0.5722156167030334,0.7196791172027588,0.011182950809597969,-0.37616223096847534,0.5640075206756592,0.735032320022583,0.0829443410038948,-0.35912802815437317,0.5472186207771301,0.7514646649360657,0.14660005271434784,-0.33838537335395813,0.5204328298568726,0.7701645493507385,0.19592562317848206,-0.3174963891506195,0.49186408519744873,0.7866886258125305,0.23065437376499176,-0.29927411675453186,0.47111397981643677,0.7970477938652039,0.25899797677993774,-0.28168225288391113,0.4553462266921997,0.8038874268531799,0.2841204106807709,-0.2643345594406128,0.4442559778690338,0.8074896335601807,0.3103175461292267,-0.24637700617313385,0.4374386966228485,0.8072476983070374,0.3353678286075592,-0.2294992059469223,0.43675705790519714,0.8025595545768738,0.3561866283416748,-0.21554630994796753,0.4428446292877197,0.7940778136253357,0.37298163771629333,-0.20500421524047852,0.45475834608078003,0.7823380827903748,0.38670575618743896,-0.1979033648967743,0.4705902338027954,0.7680090069770813,0.3985593914985657,-0.19416435062885284,0.4882338345050812,0.7517169713973999,0.41020840406417847,-0.19354692101478577,0.5054890513420105,0.7339956164360046,0.4224935472011566,-0.1959577053785324,0.5192575454711914,0.7165692448616028,0.4346151649951935,-0.20394426584243774,0.530007541179657,0.6990053653717041,0.44351398944854736,-0.21775591373443604,0.5395681262016296,0.6817213892936707,0.4484978914260864,-0.2334403693675995,0.5472804307937622,0.6669629216194153,0.45059528946876526,-0.24637869000434875,0.5524905323982239,0.6565178632736206,0.44709622859954834,-0.2523626685142517,0.5476385951042175,0.6606890559196472],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998807907104,1.0000001192092896,0.9999997615814209,1,1.0000001192092896,0.9999997615814209],"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.000004132907179155154,17.827802658081055,-0.0000035211332942708395,-0.000008800871000858024,17.827800750732422,0.00000710032236383995],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.13791504502296448,0.23126427829265594,0.27532875537872314,0.9228706955909729,0.11952286213636398,0.23188169300556183,0.22969329357147217,0.9376492500305176,0.1119416281580925,0.23791418969631195,0.22545990347862244,0.9381011128425598,0.11805357038974762,0.24799102544784546,0.27341634035110474,0.9218499660491943,0.11951670795679092,0.26453375816345215,0.36922013759613037,0.8828443288803101,0.09121546149253845,0.28477269411087036,0.4796189069747925,0.8249545097351074,0.03390347585082054,0.2926989495754242,0.57376629114151,0.7641794085502625,-0.020484160631895065,0.281216561794281,0.6310374140739441,0.7226959466934204,-0.058869943022727966,0.25369569659233093,0.6602020263671875,0.7044899463653564,-0.0820164829492569,0.21591809391975403,0.6694149971008301,0.7060710191726685,-0.09167275577783585,0.17432110011577606,0.6642961502075195,0.7210540771484375,-0.09335128962993622,0.13708971440792084,0.656351625919342,0.7359989881515503,-0.08883348852396011,0.10549042373895645,0.6470057368278503,0.7499093413352966,-0.07947911322116852,0.07725851237773895,0.6329957842826843,0.7661792039871216,-0.0718112364411354,0.04805248603224754,0.615456759929657,0.7834201455116272,-0.06605339795351028,0.01991615630686283,0.6013224124908447,0.7960224151611328,-0.06500937044620514,-0.009315737523138523,0.5984829068183899,0.7984392642974854,-0.06245340779423714,-0.035895057022571564,0.6014270186424255,0.7956737279891968,-0.06118201091885567,-0.0637529268860817,0.6094797253608704,0.787862241268158,-0.06849582493305206,-0.10190688818693161,0.621418297290802,0.7737975120544434,-0.07807967066764832,-0.14539840817451477,0.6191008687019348,0.7677740454673767,-0.0895431861281395,-0.18886518478393555,0.6060329675674438,0.7674868106842041,-0.1022399440407753,-0.22853688895702362,0.5861390233039856,0.7705575227737427,-0.11293964087963104,-0.2570371627807617,0.5736525654792786,0.7694798707962036,-0.12225589156150818,-0.27452823519706726,0.5699595212936401,0.7647444009780884,-0.13104896247386932,-0.28582513332366943,0.5708292126655579,0.7584748864173889,-0.13509519398212433,-0.2899736762046814,0.5749602317810059,0.753050684928894,-0.13535888493061066,-0.28983795642852783,0.5769158005714417,0.7515584230422974,-0.13463276624679565,-0.29116111993789673,0.567592203617096,0.758246898651123,-0.1331903487443924,-0.29498422145843506,0.5445126295089722,0.7737897634506226,-0.13177870213985443,-0.3018718361854553,0.5114572048187256,0.7936745285987854,-0.12814182043075562,-0.3034093677997589,0.487204909324646,0.8087977766990662,-0.1233549416065216,-0.30283087491989136,0.46404018998146057,0.8232519030570984,-0.11820638924837112,-0.30201831459999084,0.4395352900028229,0.837628185749054,-0.1129646897315979,-0.30064618587493896,0.41603684425354004,0.850743293762207,-0.10602294653654099,-0.29618552327156067,0.39131802320480347,0.8648141026496887,-0.096422478556633,-0.28579241037368774,0.3652064800262451,0.8807097673416138,-0.08359307050704956,-0.263539582490921,0.3504151999950409,0.8948565721511841,-0.06331736594438553,-0.2309211790561676,0.35457643866539,0.9038483500480652,-0.03845890611410141,-0.1996898055076599,0.3706497251987457,0.9062359929084778,-0.012509604915976524,-0.17436979711055756,0.391512393951416,0.9034139513969421,0.024574490264058113,-0.1350211501121521,0.3943316638469696,0.908662736415863,0.06955354660749435,-0.07777706533670425,0.4006580412387848,0.9102671146392822,0.12292546778917313,0.013425635173916817,0.43459567427635193,0.8920962810516357,0.16199320554733276,0.1554383933544159,0.36554786562919617,0.9033116102218628,0.16680489480495453,0.19672110676765442,0.2753032445907593,0.9261128902435303,0.1555265486240387,0.2281106561422348,0.22922225296497345,0.9333993196487427,0.18171927332878113,0.2934727966785431,0.2439420223236084,0.9062803983688354,0.21741631627082825,0.33722421526908875,0.2907083332538605,0.8686188459396362,0.2483818233013153,0.3785442113876343,0.3419085741043091,0.8234739303588867,0.26717686653137207,0.40823182463645935,0.3700120151042938,0.7906038165092468,0.2605774998664856,0.4396575689315796,0.3624490797519684,0.7793787717819214,0.22102324664592743,0.446126788854599,0.3212305009365082,0.8055623173713684,0.1737140566110611,0.421861857175827,0.254470556974411,0.8527020215988159,0.17176897823810577,0.3693670630455017,0.2524578273296356,0.8776835799217224,0.21845024824142456,0.3196300268173218,0.34351834654808044,0.8556349873542786,0.21648429334163666,0.2742392122745514,0.3944251835346222,0.8499153852462769,0.1761009395122528,0.2306993007659912,0.4429934024810791,0.8482471108436584,0.09384504705667496,0.20650222897529602,0.5502879023551941,0.8035752773284912,0.0197446271777153,0.1124025285243988,0.622151792049408,0.7745340466499329,-0.014566595666110516,-0.05317027494311333,0.6195869445800781,0.7829896211624146,-0.034542206674814224,-0.20967496931552887,0.5517188906669617,0.8065045475959778,-0.04899830371141434,-0.29054418206214905,0.4631747305393219,0.8358542919158936,-0.0587829165160656,-0.3120417296886444,0.41370460391044617,0.8532426953315735,-0.06609103083610535,-0.32630905508995056,0.42534077167510986,0.841569721698761,-0.09360376000404358,-0.3578900694847107,0.4438616633415222,0.8161739110946655,-0.1345594823360443,-0.38336172699928284,0.43977633118629456,0.800952136516571,-0.180592879652977,-0.40492311120033264,0.4588836133480072,0.7699670791625977,-0.218623086810112,-0.4228981137275696,0.4533035457134247,0.7535761594772339,-0.24532786011695862,-0.43672704696655273,0.4608671963214874,0.732587993144989,-0.2661901116371155,-0.44834497570991516,0.46689572930336,0.7142394185066223,-0.2828938663005829,-0.4598674476146698,0.47596997022628784,0.6942230463027954,-0.291885644197464,-0.46903955936431885,0.48995524644851685,0.6743505001068115,-0.29037022590637207,-0.47380349040031433,0.5067445039749146,0.6590943336486816,-0.28805065155029297,-0.47655120491981506,0.5184224843978882,0.6489714980125427,-0.2834204137325287,-0.47160905599594116,0.514849066734314,0.657410204410553,-0.2675550878047943,-0.45833030343055725,0.49967527389526367,0.6845963597297668,-0.23936131596565247,-0.42356473207473755,0.47756218910217285,0.7315964698791504,-0.20459763705730438,-0.3622727394104004,0.45278915762901306,0.7885938882827759,-0.1584622859954834,-0.2646760642528534,0.4212431013584137,0.8528719544410706,-0.10393767058849335,-0.15016788244247437,0.37894782423973083,0.907218337059021,-0.07725008577108383,-0.05591991916298866,0.38949906826019287,0.9160763621330261,-0.06044298782944679,-0.036216285079717636,0.37872985005378723,0.9228211045265198,-0.05687786638736725,-0.05144590139389038,0.3960226774215698,0.9150323867797852,-0.04140805825591087,-0.018200263381004333,0.39995962381362915,0.9154160022735596,0.01606004126369953,0.0027994124684482813,0.26838958263397217,0.9631724953651428,0.03460417687892914,0.013218212872743607,0.30561164021492004,0.9514354467391968,0.004556617233902216,0.09203758090734482,0.4856760799884796,0.8692681193351746,-0.022204559296369553,0.17439432442188263,0.5848430395126343,0.7918663024902344,-0.008146255277097225,0.2131734937429428,0.5640366077423096,0.7977176308631897,0.04417211562395096,0.19900746643543243,0.4630846381187439,0.8625528812408447,0.1353234201669693,0.1763259470462799,0.4784221053123474,0.8495346307754517,0.06463532149791718,0.2391573190689087,0.5974819660186768,0.762654185295105,-0.09493423998355865,0.26177525520324707,0.6981046795845032,0.6596295237541199,-0.20908206701278687,0.1939241737127304,0.7587628364562988,0.585625410079956,-0.29328110814094543,0.08236611634492874,0.7415891885757446,0.5977018475532532,-0.32837721705436707,-0.04511205852031708,0.6712086796760559,0.663032591342926,-0.32949554920196533,-0.14049839973449707,0.5665181279182434,0.7421254515647888,-0.3260587453842163,-0.17902570962905884,0.44613271951675415,0.8140031695365906,-0.2991524636745453,-0.1544249802827835,0.32372933626174927,0.884228527545929,-0.2547900378704071,-0.09966855496168137,0.23184692859649658,0.9334855079650879,-0.229658305644989,-0.0260965283960104,0.16963613033294678,0.9580186009407043,-0.24735453724861145,0.05238809064030647,0.16626498103141785,0.9531145095825195,-0.29093170166015625,0.12570369243621826,0.20087486505508423,0.926934003829956,-0.32497668266296387,0.17895106971263885,0.2457718700170517,0.8955237865447998,-0.34386929869651794,0.21485789120197296,0.28923511505126953,0.8671407699584961,-0.3566855788230896,0.2093004286289215,0.3215668201446533,0.8518001437187195,-0.3683098554611206,0.16271847486495972,0.332068532705307,0.8529953956604004,-0.3808337450027466,0.09747139364480972,0.340944766998291,0.8539447784423828,-0.3795674443244934,0.04902183264493942,0.3595898151397705,0.8510115146636963,-0.36955565214157104,-0.005780594889074564,0.3674132227897644,0.8534651398658752,-0.36367130279541016,-0.09154493361711502,0.3567348122596741,0.8556301593780518,-0.3647286593914032,-0.180022731423378,0.36419862508773804,0.837809145450592,-0.359180748462677,-0.2167086899280548,0.42415717244148254,0.8025691509246826,-0.2970675826072693,-0.20946432650089264,0.4619722366333008,0.8089852929115295,-0.1972135603427887,-0.1991232931613922,0.4074023962020874,0.8691835403442383,-0.11850137263536453,-0.20221342146396637,0.31489297747612,0.919733464717865,-0.07542146742343903,-0.183023601770401,0.30947113037109375,0.9300761222839355,-0.046023573726415634,-0.15900582075119019,0.3207128942012787,0.9325997233390808,-0.04029823839664459,-0.13553082942962646,0.26536649465560913,0.9537233114242554,-0.05450740456581116,-0.11336227506399155,0.3477807343006134,0.9290998578071594,-0.07842081040143967,-0.10927952826023102,0.46626588702201843,0.8743593692779541,-0.07368217408657074,-0.11540441960096359,0.4429284334182739,0.8860401511192322,-0.0512055940926075,-0.09613440185785294,0.3729378879070282,0.9214410185813904,-0.04350309446454048,-0.08615453541278839,0.3224889636039734,0.9416399598121643,-0.058523133397102356,-0.14680533111095428,0.34644174575805664,0.9246628284454346,-0.10502652823925018,-0.27342700958251953,0.3875754475593567,0.8740665912628174,-0.1702071577310562,-0.41613754630088806,0.3672621548175812,0.8142343759536743,-0.23364652693271637,-0.5227319002151489,0.3132053315639496,0.7576695084571838,-0.28123998641967773,-0.5844725370407104,0.2599138915538788,0.7153605222702026,-0.3034283220767975,-0.5994828343391418,0.22861164808273315,0.7044773697853088,-0.3256290853023529,-0.591468334197998,0.22826847434043884,0.7014445662498474,-0.3603135049343109,-0.5800319314002991,0.2507089376449585,0.6862084865570068,-0.3785230219364166,-0.5785848498344421,0.28249746561050415,0.6649473905563354,-0.3831903040409088,-0.5805986523628235,0.31991127133369446,0.6432163119316101,-0.3788366913795471,-0.5818013548851013,0.3458685278892517,0.631161630153656,-0.3712719976902008,-0.5777065753936768,0.3728941082954407,0.6239889860153198,-0.34581485390663147,-0.5658988356590271,0.4013121724128723,0.6317588090896606,-0.30493226647377014,-0.5345840454101562,0.4043133556842804,0.676584780216217,-0.2607738673686981,-0.4898359179496765,0.40228039026260376,0.728167712688446,-0.22048763930797577,-0.43258175253868103,0.41314277052879333,0.7704357504844666,-0.18986506760120392,-0.36799535155296326,0.44548299908638,0.7937729954719543,-0.16779854893684387,-0.2988573908805847,0.49269455671310425,0.7998625040054321,-0.1522589921951294,-0.22816336154937744,0.5560337901115417,0.78459233045578,-0.14132918417453766,-0.16231238842010498,0.6238513588905334,0.7513256669044495,-0.13521821796894073,-0.10109449177980423,0.6788044571876526,0.7146470546722412,-0.13977642357349396,-0.050456590950489044,0.7183363437652588,0.6796393394470215,-0.1569058895111084,-0.012987574562430382,0.7391794323921204,0.6548478007316589,-0.18153317272663116,0.014905625022947788,0.7430453896522522,0.6439775824546814,-0.20662827789783478,0.038570336997509,0.7360565662384033,0.6434577107429504,-0.22551590204238892,0.06147998571395874,0.7181324362754822,0.6554758548736572,-0.23155462741851807,0.08839050680398941,0.6923404335975647,0.6776683330535889,-0.22347824275493622,0.11795434355735779,0.6549066305160522,0.7122089862823486,-0.20421786606311798,0.14591465890407562,0.6052341461181641,0.7554439902305603,-0.17756536602973938,0.16992297768592834,0.5490654110908508,0.7988265752792358,-0.14655616879463196,0.18872033059597015,0.4908832013607025,0.8378183841705322,-0.11424963176250458,0.2024940401315689,0.43597087264060974,0.8694093227386475,-0.0835924968123436,0.21339693665504456,0.39223092794418335,0.89085853099823,-0.05561515688896179,0.22433996200561523,0.3673885464668274,0.9008908271789551,-0.017659714445471764,0.23478654026985168,0.34642815589904785,0.9080479145050049,0.03205907344818115,0.2399531453847885,0.3210742175579071,0.9155905842781067,0.08147966116666794,0.2375851422548294,0.2929290533065796,0.9225545525550842,0.11788438260555267,0.23128968477249146,0.2690015435218811,0.9274947643280029,0.13794179260730743,0.23126555979251862,0.2753158509731293,0.9228702783584595],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999997019767761,0.9999999403953552,0.9999998807907104,0.9999997615814209,1,0.9999998807907104],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.000002295968897669809,18.7510986328125,-0.0000013646113075083122,0.000002962013240903616,18.751115798950195,0.0000018519967852625996],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[0.055037710815668106,0.05196232721209526,-0.013945172540843487,0.9970337748527527,0.05503770336508751,0.05196232348680496,-0.013945167884230614,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999999403953552,1.0000001192092896,1,0.9999999403953552,1.0000001192092896,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,5.4666666984558105],"values":[-4.513197898864746,8.24639892578125,-0.08130094408988953,-4.513198375701904,8.246407508850098,-0.08130082488059998],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.5453315377235413,-0.641999363899231,0.31685760617256165,0.4359491467475891,0.5453392267227173,-0.6407395005226135,0.31727102398872375,0.43748968839645386,0.5456733703613281,-0.6369839310646057,0.3182488977909088,0.4418255388736725,0.5463989973068237,-0.6310265064239502,0.31891530752182007,0.4489395320415497,0.5479568243026733,-0.6227999925613403,0.3189815878868103,0.45838218927383423,0.5507832169532776,-0.6122075319290161,0.3181612193584442,0.4696948826313019,0.5542433261871338,-0.5999317169189453,0.31486445665359497,0.4834839105606079,0.5592706203460693,-0.5856954455375671,0.3090532720088959,0.49866148829460144,0.5684822201728821,-0.5662981867790222,0.29719939827919006,0.5175006985664368,0.5794818997383118,-0.5411470532417297,0.2786090672016144,0.5419757962226868,0.5886822938919067,-0.514471173286438,0.258084774017334,0.5675956606864929,0.5919733047485352,-0.49363213777542114,0.23654238879680634,0.5915595293045044,0.586201012134552,-0.48387521505355835,0.21920724213123322,0.611703634262085,0.5746956467628479,-0.48259758949279785,0.20672984421253204,0.6277636289596558,0.5602077841758728,-0.4845280647277832,0.19418513774871826,0.6431888937950134,0.5471291542053223,-0.48679226636886597,0.18253517150878906,0.656021237373352,0.5342332720756531,-0.4903722405433655,0.1736287772655487,0.6663204431533813,0.519572377204895,-0.49634766578674316,0.16586865484714508,0.6754044890403748,0.504688560962677,-0.5032886862754822,0.16123135387897491,0.6826378107070923,0.48752573132514954,-0.5130004286766052,0.1592656373977661,0.6883195042610168,0.4666016101837158,-0.5295087099075317,0.16343934834003448,0.6893410682678223,0.44178736209869385,-0.553335428237915,0.17271237075328827,0.6847001910209656,0.4155373275279999,-0.581392228603363,0.1854916661977768,0.6744661927223206,0.3890058100223541,-0.6097692251205444,0.20292028784751892,0.6600601673126221,0.36499086022377014,-0.6338685750961304,0.2227376252412796,0.6445000171661377,0.3445384204387665,-0.6523231863975525,0.241788849234581,0.6303220987319946,0.3273756206035614,-0.6654378175735474,0.26054853200912476,0.6181685328483582,0.31419485807418823,-0.6743130087852478,0.27555471658706665,0.6088129878044128,0.30640527606010437,-0.6789835691452026,0.2854151129722595,0.6030218601226807,0.30224981904029846,-0.6805124282836914,0.28894710540771484,0.6017121076583862,0.2999308109283447,-0.6809991598129272,0.28999021649360657,0.6018201112747192,0.2986912727355957,-0.6805139780044556,0.2900005280971527,0.6029792428016663,0.2982492446899414,-0.6805667877197266,0.2892002463340759,0.603522539138794,0.29740309715270996,-0.6806356310844421,0.2870177626609802,0.6049027442932129,0.29670512676239014,-0.6807816624641418,0.2885792553424835,0.6043380498886108,0.3019286096096039,-0.6781281232833862,0.2901844084262848,0.6039655804634094,0.3134274482727051,-0.672646701335907,0.28293508291244507,0.6076655983924866,0.3265799880027771,-0.669231116771698,0.26714518666267395,0.6116442084312439,0.3503347933292389,-0.661312460899353,0.2349870353937149,0.6202518939971924,0.37403979897499084,-0.6524491906166077,0.19242562353610992,0.6303781270980835,0.4068712592124939,-0.6374335885047913,0.14483489096164703,0.6380886435508728,0.4491487741470337,-0.6090550422668457,0.09680524468421936,0.6464874744415283,0.5004857778549194,-0.5716273188591003,0.0725201964378357,0.6461400985717773,0.5517638921737671,-0.5277590155601501,0.07101354002952576,0.6418600082397461,0.6011117100715637,-0.4752834737300873,0.06845910847187042,0.638814389705658,0.6342244148254395,-0.43733370304107666,0.06741919368505478,0.6339978575706482,0.6549714803695679,-0.42486894130706787,0.07676531374454498,0.620166003704071,0.6750316619873047,-0.41796478629112244,0.0819762721657753,0.6024264097213745,0.6826021671295166,-0.4135220944881439,0.08921492844820023,0.5958980321884155,0.6796286702156067,-0.4255457818508148,0.11443036049604416,0.5864481329917908,0.6702202558517456,-0.45239025354385376,0.16406512260437012,0.5650048851966858,0.6488705277442932,-0.4911390244960785,0.22968588769435883,0.533848226070404,0.6188843250274658,-0.5303070545196533,0.2876461148262024,0.5030073523521423,0.586724579334259,-0.561398983001709,0.3303144574165344,0.4811214804649353,0.5530343055725098,-0.582343339920044,0.3665400445461273,0.46976345777511597,0.5202680826187134,-0.5993866324424744,0.3983950912952423,0.45971518754959106,0.4921013414859772,-0.6103649139404297,0.42352208495140076,0.4537840783596039,0.4747524857521057,-0.6132661700248718,0.4295976161956787,0.46255865693092346,0.46391424536705017,-0.611471951007843,0.42335939407348633,0.48130303621292114,0.4441370368003845,-0.6220636963844299,0.4105031192302704,0.4972588121891022,0.3892182409763336,-0.6646725535392761,0.37840375304222107,0.5133519768714905,0.32430893182754517,-0.7137675881385803,0.3419914245605469,0.5180746912956238,0.27770084142684937,-0.7469784617424011,0.31303468346595764,0.516637921333313,0.24398109316825867,-0.7704213857650757,0.2993491291999817,0.5072615146636963,0.2331739366054535,-0.7776522636413574,0.2989603579044342,0.5015073418617249,0.22771359980106354,-0.7838125824928284,0.2982557415962219,0.4948008954524994,0.22859066724777222,-0.7837752103805542,0.2936403751373291,0.4972103238105774,0.23284344375133514,-0.7818167209625244,0.2979237735271454,0.495770126581192,0.23595306277275085,-0.7819936871528625,0.30418458580970764,0.49018746614456177,0.2367049753665924,-0.7843776345252991,0.3045109808444977,0.4857937693595886,0.23594442009925842,-0.787321150302887,0.3006337583065033,0.48381295800209045,0.23521217703819275,-0.7894002199172974,0.29948270320892334,0.48149001598358154,0.23540160059928894,-0.7905070781707764,0.2973000705242157,0.4809337854385376,0.2359464466571808,-0.7921761274337769,0.2923831641674042,0.48093488812446594,0.23669961094856262,-0.7960894703865051,0.28744351863861084,0.4770650863647461,0.23713497817516327,-0.8023151755332947,0.2796432673931122,0.4710170328617096,0.2410459667444229,-0.8093314170837402,0.2727740705013275,0.4609488546848297,0.253225713968277,-0.8134024739265442,0.27017828822135925,0.4486165940761566,0.2757001221179962,-0.8119390606880188,0.278405100107193,0.4327066242694855,0.30926212668418884,-0.8042527437210083,0.2887937128543854,0.41729211807250977,0.3497482240200043,-0.7947537302970886,0.30645594000816345,0.3900352120399475,0.38114044070243835,-0.7871695160865784,0.3225845992565155,0.3619880676269531,0.41638752818107605,-0.7759193778038025,0.3329758942127228,0.33719080686569214,0.45483359694480896,-0.7559167742729187,0.3460010290145874,0.3193736970424652,0.506553590297699,-0.7174212336540222,0.37319087982177734,0.2990632951259613,0.5791251063346863,-0.6502128839492798,0.38725942373275757,0.3030964732170105,0.6345840692520142,-0.5971935987472534,0.3477083742618561,0.34606611728668213,0.6735904216766357,-0.5579546689987183,0.28729382157325745,0.3904162347316742,0.6985105276107788,-0.5275603532791138,0.21761229634284973,0.43175002932548523,0.71498042345047,-0.4956406056880951,0.16851308941841125,0.4634076952934265,0.7144877314567566,-0.4916956424713135,0.17754262685775757,0.4649961292743683,0.7075169086456299,-0.49696052074432373,0.19738711416721344,0.46204811334609985,0.7023769617080688,-0.48382142186164856,0.18854938447475433,0.4868599474430084,0.6858484148979187,-0.46888497471809387,0.15945158898830414,0.5332298874855042,0.657831609249115,-0.45930594205856323,0.13382472097873688,0.5817101001739502,0.6256790161132812,-0.4558353126049042,0.1259586364030838,0.6203824877738953,0.5965332388877869,-0.4562711715698242,0.13202689588069916,0.6469417214393616,0.5723111033439636,-0.46150800585746765,0.13959655165672302,0.6633123755455017,0.5426842570304871,-0.4744126498699188,0.15620650351047516,0.6752969622612,0.5134884119033813,-0.48279479146003723,0.17182640731334686,0.6882692575454712,0.4845206141471863,-0.49183693528175354,0.1875627636909485,0.6986819505691528,0.4559439420700073,-0.5039548873901367,0.2074093222618103,0.7036519050598145,0.42774900794029236,-0.5178499817848206,0.22561831772327423,0.7056618332862854,0.40422704815864563,-0.5303728580474854,0.2426959127187729,0.704559326171875,0.388290137052536,-0.5415922999382019,0.26112666726112366,0.6983705759048462,0.3808605372905731,-0.549893319606781,0.27847257256507874,0.689213752746582,0.38669782876968384,-0.5528505444526672,0.2924852669239044,0.6776972413063049,0.44593989849090576,-0.530402660369873,0.2848377823829651,0.662327766418457,0.5226024389266968,-0.4865204095840454,0.2595442235469818,0.6502471566200256,0.5722491145133972,-0.44655346870422363,0.23820802569389343,0.6452735066413879,0.5861585140228271,-0.4407113194465637,0.23565302789211273,0.6376985907554626,0.5999413132667542,-0.4483129382133484,0.23721247911453247,0.6187214851379395,0.6166257858276367,-0.4595358669757843,0.22810058295726776,0.5971344709396362,0.6292359828948975,-0.473145991563797,0.21646305918693542,0.5773549675941467,0.6383243799209595,-0.4843413233757019,0.2071850448846817,0.5612752437591553,0.6494625806808472,-0.48207470774650574,0.19459207355976105,0.5549201369285583,0.6621002554893494,-0.4702843129634857,0.17844340205192566,0.5555303692817688,0.6718736886978149,-0.46101418137550354,0.1764848679304123,0.5521818399429321,0.6848487257957458,-0.45126935839653015,0.1917198896408081,0.5390563011169434,0.6973745822906494,-0.44708532094955444,0.23056437075138092,0.5105130672454834,0.6870037317276001,-0.4665822982788086,0.285331130027771,0.4784485697746277,0.6591740250587463,-0.49631941318511963,0.34919363260269165,0.4440951347351074,0.5984324812889099,-0.5622076988220215,0.4123688042163849,0.39465567469596863,0.576546847820282,-0.5745558738708496,0.4406728744506836,0.37853243947029114,0.5515279173851013,-0.5864033699035645,0.4585212469100952,0.37643900513648987,0.5204523205757141,-0.5995075702667236,0.45826488733291626,0.3996415436267853,0.4889933168888092,-0.6110806465148926,0.4408760368824005,0.439424991607666,0.4631902277469635,-0.6159417629241943,0.4154234230518341,0.4832122027873993,0.4440664052963257,-0.617516279220581,0.38492879271507263,0.5227891802787781,0.4203793406486511,-0.6242597103118896,0.3463377356529236,0.5600278377532959,0.39688000082969666,-0.6376195549964905,0.30692365765571594,0.5845728516578674,0.37999284267425537,-0.6509340405464172,0.27809616923332214,0.5954433679580688,0.36711812019348145,-0.6623188257217407,0.25699111819267273,0.6004278659820557,0.3585830628871918,-0.6693722009658813,0.2446993589401245,0.602894127368927,0.35343676805496216,-0.6734722852706909,0.24318858981132507,0.6019774079322815,0.3508338928222656,-0.6762031316757202,0.24540017545223236,0.5995362401008606,0.3499477207660675,-0.6768632531166077,0.2471434623003006,0.5985924005508423,0.3506649434566498,-0.6765930652618408,0.2459486424922943,0.5989701151847839,0.3491825461387634,-0.6779978275299072,0.23949500918388367,0.6008599400520325,0.3443858027458191,-0.681800901889801,0.2266595959663391,0.6042941808700562,0.3383557200431824,-0.6865667104721069,0.21024839580059052,0.608224630355835,0.3334313631057739,-0.6906589865684509,0.19447605311870575,0.6115494966506958,0.3342118561267853,-0.6934866309165955,0.18459853529930115,0.6109845638275146,0.3551527261734009,-0.690341055393219,0.19643452763557434,0.5989234447479248,0.3880000412464142,-0.6819002628326416,0.22211328148841858,0.5789073705673218,0.4251267611980438,-0.6706351637840271,0.25058168172836304,0.5538272261619568,0.46000996232032776,-0.6589236259460449,0.27491772174835205,0.5278548002243042,0.4844054579734802,-0.6525782346725464,0.28539618849754333,0.5079783201217651,0.5004304647445679,-0.6512718796730042,0.2845924198627472,0.49439001083374023,0.5134339928627014,-0.649918258190155,0.2827960252761841,0.48375436663627625,0.5236082077026367,-0.6482762694358826,0.28111532330513,0.4759691059589386,0.5312556624412537,-0.6462381482124329,0.28042370080947876,0.4706445038318634,0.5367569327354431,-0.6437699794769287,0.2814250886440277,0.4671746790409088,0.5412129163742065,-0.6412792801856995,0.2838146984577179,0.4640029966831207,0.545201301574707,-0.6388488411903381,0.2879481911659241,0.4601233899593353,0.5480751991271973,-0.6370539665222168,0.2936134934425354,0.4555952250957489,0.5489397644996643,-0.6368938684463501,0.2980855703353882,0.4518587589263916,0.548920214176178,-0.6374276280403137,0.3020924925804138,0.44845595955848694,0.5484756827354431,-0.6382542252540588,0.3059346377849579,0.44520774483680725,0.5477610230445862,-0.6392369270324707,0.3094417154788971,0.44224411249160767,0.5469318628311157,-0.6402382850646973,0.31244906783103943,0.43970000743865967,0.5461446642875671,-0.6411223411560059,0.3147956430912018,0.437712162733078,0.5455566644668579,-0.6417526602745056,0.3163220286369324,0.43641942739486694,0.5453329086303711,-0.6419817805290222,0.3168604075908661,0.4359712302684784],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,0.9999998807907104,0.9999998807907104,0.9999998807907104,0.9999998807907104,0.9999999403953552],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,5.4666666984558105],"values":[8.435781637672335e-7,17.921396255493164,-0.000005041616987000452,0.000010265859600622207,17.921390533447266,-0.0000037204242744337535],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.448344886302948,0.16147242486476898,-0.3070830702781677,0.8237800598144531,0.4440325200557709,0.15797336399555206,-0.2921483814716339,0.8321831822395325,0.4338403046131134,0.15384256839752197,-0.2772926986217499,0.8433408737182617,0.4167773127555847,0.15020303428173065,-0.26383277773857117,0.8568127155303955,0.3914247453212738,0.14878466725349426,-0.2549145221710205,0.8715896010398865,0.35676756501197815,0.1515517234802246,-0.25519493222236633,0.8857903480529785,0.31055599451065063,0.16111931204795837,-0.270638108253479,0.8968559503555298,0.2562013268470764,0.17631645500659943,-0.30000436305999756,0.9018152952194214,0.19308353960514069,0.19449655711650848,-0.3307412266731262,0.9030504822731018,0.12808571755886078,0.21214352548122406,-0.35493332147598267,0.9014496803283691,0.0669582188129425,0.22892813384532928,-0.3756222128868103,0.895553708076477,0.020398467779159546,0.24230574071407318,-0.391926646232605,0.887279748916626,-0.011032490991055965,0.2536088228225708,-0.40777894854545593,0.877084493637085,-0.033845871686935425,0.2648393511772156,-0.4229336977005005,0.8659340143203735,-0.05479637533426285,0.276991605758667,-0.4332827627658844,0.8558849692344666,-0.07217872142791748,0.29665833711624146,-0.44112032651901245,0.8439176082611084,-0.08079104125499725,0.32681599259376526,-0.447939395904541,0.8282598853111267,-0.08112414926290512,0.362659215927124,-0.45065292716026306,0.8116706013679504,-0.0811617448925972,0.40206339955329895,-0.44933027029037476,0.7936372756958008,-0.0951932966709137,0.44341209530830383,-0.4409216642379761,0.7745398879051208,-0.11678612977266312,0.4821658432483673,-0.4264931082725525,0.7562941312789917,-0.13181784749031067,0.5149175524711609,-0.4130215644836426,0.7395249605178833,-0.138866126537323,0.5400637984275818,-0.40058043599128723,0.7270369529724121,-0.13613499701023102,0.5564364194869995,-0.39247947931289673,0.7195871472358704,-0.12922093272209167,0.5622583627700806,-0.3857465386390686,0.7199771404266357,-0.12485197186470032,0.5612896084785461,-0.37670695781707764,0.7262629270553589,-0.1168297529220581,0.5603920221328735,-0.3732019066810608,0.7300903797149658,-0.10910336673259735,0.5596830248832703,-0.37160950899124146,0.7326375246047974,-0.10541197657585144,0.5594266057014465,-0.3716949224472046,0.7333301901817322,-0.10108910501003265,0.5590208172798157,-0.37093350291252136,0.734632670879364,-0.09811781346797943,0.5586497187614441,-0.3696541488170624,0.7359614968299866,-0.09498777240514755,0.5582262277603149,-0.36808326840400696,0.7374791502952576,-0.0928330346941948,0.5579132437705994,-0.36685284972190857,0.7386026382446289,-0.09163052588701248,0.5576010942459106,-0.36514905095100403,0.739831805229187,-0.0920037031173706,0.5576843023300171,-0.3654578924179077,0.7395702004432678,-0.09563560038805008,0.5581979155540466,-0.3673955798149109,0.7377597093582153,-0.1023728996515274,0.5588028430938721,-0.36816301941871643,0.7360130548477173,-0.10614326596260071,0.5571821331977844,-0.37199872732162476,0.7347779273986816,-0.11466283351182938,0.5382373929023743,-0.3733350336551666,0.7468426823616028,-0.12208374589681625,0.5005577206611633,-0.3744759261608124,0.7709119915962219,-0.13456137478351593,0.4499365985393524,-0.3842022120952606,0.7948830127716064,-0.13340653479099274,0.39070191979408264,-0.4133870601654053,0.8115823268890381,-0.11974610388278961,0.3287855088710785,-0.4611593186855316,0.815409779548645,-0.07842778414487839,0.2656830847263336,-0.5146240592002869,0.8114330768585205,0.015413282439112663,0.19816593825817108,-0.5518084764480591,0.809938371181488,0.09913387894630432,0.16869311034679413,-0.5691495537757874,0.7986137866973877,0.14543664455413818,0.16329450905323029,-0.5953055620193481,0.7731716632843018,0.20055601000785828,0.10861586034297943,-0.5604244470596313,0.7961811423301697,0.2862269878387451,0.08972997218370438,-0.487535297870636,0.8199585676193237,0.39791885018348694,0.09616420418024063,-0.43244579434394836,0.8033702373504639,0.46555471420288086,0.084310382604599,-0.4108660817146301,0.7793200016021729,0.5017992258071899,0.06330277770757675,-0.40483421087265015,0.7617740035057068,0.5331922173500061,0.06503897160291672,-0.4016198217868805,0.7417395710945129,0.6184181571006775,0.03633229434490204,-0.32585084438323975,0.7141849994659424,0.6802206039428711,0.01722610741853714,-0.26743587851524353,0.6822618842124939,0.6792058944702148,0.016755234450101852,-0.2647876739501953,0.6843143701553345,0.6242751479148865,0.04600793123245239,-0.3206500709056854,0.7108778953552246,0.5096029043197632,0.0924023762345314,-0.398269385099411,0.7570655345916748,0.3041122257709503,0.12565401196479797,-0.46282854676246643,0.8231139779090881,0.05064348503947258,0.18692930042743683,-0.45706814527511597,0.8680906891822815,-0.1597040444612503,0.21936334669589996,-0.37817665934562683,0.8850744366645813,-0.2779962718486786,0.22518648207187653,-0.27357766032218933,0.8928406834602356,-0.310733437538147,0.21650417149066925,-0.19773699343204498,0.9041408896446228,-0.2993435859680176,0.20741520822048187,-0.16507218778133392,0.9165825247764587,-0.28399550914764404,0.1954496204853058,-0.1324036866426468,0.929309070110321,-0.25543859601020813,0.1793782114982605,-0.09870103001594543,0.9448983073234558,-0.21887513995170593,0.1638164222240448,-0.07413124293088913,0.959042489528656,-0.18836548924446106,0.1532435566186905,-0.06128116697072983,0.968131959438324,-0.16724878549575806,0.14671024680137634,-0.05329805985093117,0.9734799861907959,-0.16121332347393036,0.14990843832492828,-0.06527547538280487,0.9732815027236938,-0.1552259773015976,0.1544884443283081,-0.08215736597776413,0.9722594022750854,-0.14713838696479797,0.15784475207328796,-0.09698174893856049,0.9716119766235352,-0.1360452026128769,0.15875039994716644,-0.10650624334812164,0.972083568572998,-0.12317776679992676,0.15725405514240265,-0.11129195988178253,0.9735052585601807,-0.11386331170797348,0.15802034735679626,-0.12554796040058136,0.9727808237075806,-0.0965428426861763,0.1592465341091156,-0.14842945337295532,0.9712305665016174,-0.07482755929231644,0.15908050537109375,-0.17305749654769897,0.9690951108932495,-0.058337729424238205,0.15763893723487854,-0.19235408306121826,0.9668229818344116,-0.055003970861434937,0.1565987467765808,-0.20837417244911194,0.9638628959655762,-0.05177188664674759,0.15826019644737244,-0.2258046418428421,0.9598362445831299,-0.03910660371184349,0.17421922087669373,-0.276986300945282,0.94413822889328,0.0042482903227210045,0.20059509575366974,-0.34508216381073,0.9168761372566223,0.09372478723526001,0.23482775688171387,-0.47601962089538574,0.842304527759552,0.19889266788959503,0.2139066606760025,-0.5765684843063354,0.7630559802055359,0.27977150678634644,0.1529838591814041,-0.6109042763710022,0.7246515154838562,0.3234088122844696,0.12400542199611664,-0.5654307007789612,0.7485436201095581,0.342149555683136,0.15182356536388397,-0.48443955183029175,0.790696918964386,0.30702266097068787,0.16798439621925354,-0.4116813540458679,0.8414492607116699,0.30985045433044434,0.16458559036254883,-0.40565988421440125,0.8440049290657043,0.3619067072868347,0.17014044523239136,-0.4191078245639801,0.8151223063468933,0.4034751355648041,0.18751947581768036,-0.5018081068992615,0.7417768239974976,0.40480443835258484,0.18291634321212769,-0.5787988901138306,0.6838616728782654,0.38895630836486816,0.1883050948381424,-0.6028338670730591,0.670705258846283,0.3531169593334198,0.2035176157951355,-0.6231014728546143,0.6675578951835632,0.2812940180301666,0.23152557015419006,-0.6598215103149414,0.6571949124336243,0.19398356974124908,0.25601062178611755,-0.6929831504821777,0.6454481482505798,0.09315755218267441,0.26553940773010254,-0.7134715914726257,0.6416921615600586,0.01848592422902584,0.27640780806541443,-0.7203534841537476,0.6358835101127625,-0.02076459303498268,0.28716540336608887,-0.7284854650497437,0.6216219067573547,-0.035523876547813416,0.293565034866333,-0.7249005436897278,0.6221550107002258,-0.03516238182783127,0.3012252151966095,-0.7181602120399475,0.6263169646263123,-0.0382155142724514,0.2988705337047577,-0.7188291549682617,0.6264987587928772,-0.0402541309595108,0.29347437620162964,-0.720237672328949,0.6273038387298584,-0.04111579433083534,0.2834224998950958,-0.7176130414009094,0.6348328590393066,-0.0430312417447567,0.2733052372932434,-0.7190988063812256,0.6374555826187134,-0.03419912979006767,0.26961392164230347,-0.7225667834281921,0.6356382966041565,-0.022624848410487175,0.27160245180130005,-0.7279708981513977,0.6291093826293945,-0.018490251153707504,0.27941450476646423,-0.7316809892654419,0.6214728951454163,-0.004408467561006546,0.28824084997177124,-0.7271792888641357,0.6229832172393799,0.013950341381132603,0.2918531894683838,-0.7177916765213013,0.6319827437400818,0.005365061108022928,0.28688913583755493,-0.717643141746521,0.6345503926277161,-0.008784515783190727,0.29103177785873413,-0.7290351986885071,0.6194602251052856,-0.018123503774404526,0.29616087675094604,-0.746680498123169,0.5953390002250671,0.004576058592647314,0.2934492826461792,-0.7708956003189087,0.565319836139679,0.07034873217344284,0.28832751512527466,-0.7904735207557678,0.5357890129089355,0.1407698094844818,0.27005189657211304,-0.7860978841781616,0.5378716588020325,0.20489700138568878,0.23276858031749725,-0.735359251499176,0.6025635004043579,0.294501394033432,0.19631314277648926,-0.6269182562828064,0.6940487027168274,0.3308984041213989,0.17834511399269104,-0.5157374739646912,0.7698793411254883,0.30899158120155334,0.18219119310379028,-0.4913543164730072,0.7936633825302124,0.31778258085250854,0.17297397553920746,-0.45447030663490295,0.8139723539352417,0.35489460825920105,0.1498541682958603,-0.4443495273590088,0.8087936043739319,0.3801029324531555,0.13362500071525574,-0.5323798656463623,0.7444714903831482,0.3659438490867615,0.11142978072166443,-0.5552001595497131,0.738526463508606,0.3369804322719574,0.1324886977672577,-0.5830572843551636,0.7272793054580688,0.276640921831131,0.16896656155586243,-0.6142796874046326,0.7194307446479797,0.16911457479000092,0.2088204026222229,-0.6252912282943726,0.7326699495315552,0.015292040072381496,0.24627096951007843,-0.5941612124443054,0.7655646800994873,-0.14309169352054596,0.26273998618125916,-0.4984324276447296,0.8136692643165588,-0.25308430194854736,0.24418272078037262,-0.3489663004875183,0.8686458468437195,-0.2574976980686188,0.20281736552715302,-0.21172596514225006,0.9207237362861633,-0.2333565354347229,0.17371852695941925,-0.13846616446971893,0.9466751217842102,-0.2246549278497696,0.1602383702993393,-0.10502618551254272,0.9554178714752197,-0.22679877281188965,0.15536095201969147,-0.08532435446977615,0.957676887512207,-0.24548891186714172,0.15573103725910187,-0.08081826567649841,0.9533894658088684,-0.2713663876056671,0.15861527621746063,-0.08575963228940964,0.9454347491264343,-0.28426894545555115,0.16030465066432953,-0.09758832305669785,0.9401968717575073,-0.288847416639328,0.1661204695701599,-0.1277446746826172,0.9341587424278259,-0.275408536195755,0.17301994562149048,-0.16443516314029694,0.9312225580215454,-0.2459537386894226,0.18298473954200745,-0.20475225150585175,0.9295697212219238,-0.2082957625389099,0.19770781695842743,-0.25230899453163147,0.9240480661392212,-0.16859924793243408,0.21452976763248444,-0.30812254548072815,0.9113790392875671,-0.12794913351535797,0.2301141768693924,-0.3710377514362335,0.8905096650123596,-0.10793763399124146,0.23917359113693237,-0.4462200105190277,0.8555893898010254,-0.09538861364126205,0.22744672000408173,-0.5232065320014954,0.8157352209091187,-0.07769248634576797,0.2006015032529831,-0.5856806039810181,0.7814737558364868,-0.04997529089450836,0.17054937779903412,-0.6283100247383118,0.7573915123939514,-0.010364534333348274,0.14650636911392212,-0.6482069492340088,0.7471654415130615,0.03835244104266167,0.13787859678268433,-0.6513321995735168,0.7451744675636292,0.0865667387843132,0.13630478084087372,-0.6446322798728943,0.7472459077835083,0.13628791272640228,0.13817940652370453,-0.6317341923713684,0.7504958510398865,0.1945655643939972,0.14219599962234497,-0.6133069396018982,0.7521829009056091,0.25345513224601746,0.144495889544487,-0.5886054635047913,0.753939688205719,0.3050078749656677,0.14723967015743256,-0.5612754821777344,0.7551559805870056,0.346894770860672,0.15022438764572144,-0.5339097380638123,0.7563313245773315,0.3798864781856537,0.15311799943447113,-0.5077329277992249,0.7579237818717957,0.40688833594322205,0.15717153251171112,-0.4809666574001312,0.760532796382904,0.42641764879226685,0.16077181696891785,-0.4509947597980499,0.7674139142036438,0.4378611743450165,0.16320212185382843,-0.41664955019950867,0.779772937297821,0.4449117183685303,0.1637926548719406,-0.3809736967086792,0.793778657913208,0.4482911229133606,0.16330209374427795,-0.34804829955101013,0.8069881200790405,0.4491913318634033,0.16272135078907013,-0.3229205906391144,0.8169891238212585,0.4488152265548706,0.16238927841186523,-0.3075546622276306,0.8231675624847412,0.44835492968559265,0.16146551072597504,-0.307068407535553,0.823781430721283],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,1,0.9999998807907104,0.9999998211860657,0.9999999403953552,0.9999997615814209],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.0000024798632694000844,17.203704833984375,-0.000006206651960383169,0.000006590003067685757,17.20370864868164,0.000003415607352508232],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.03998570889234543,-0.09525351226329803,-0.6258751153945923,0.7730512619018555,0.0352909080684185,-0.09765776246786118,-0.6359954476356506,0.7646746635437012,0.021194208413362503,-0.09989109635353088,-0.6615681648254395,0.7428998351097107,-0.003167122835293412,-0.09625070542097092,-0.6990793943405151,0.7085292935371399,-0.03842100128531456,-0.07947101444005966,-0.7420092225074768,0.6645529270172119,-0.08454231172800064,-0.0452880784869194,-0.7822828888893127,0.6154958605766296,-0.13973990082740784,0.005268338602036238,-0.8107550740242004,0.5684375166893005,-0.1876946985721588,0.061919357627630234,-0.8195309042930603,0.5378714799880981,-0.22883374989032745,0.12231256812810898,-0.8123069405555725,0.5223333835601807,-0.26055780053138733,0.18313731253147125,-0.7927576899528503,0.5197169780731201,-0.2791343033313751,0.2398151457309723,-0.7629473805427551,0.531492292881012,-0.2876366674900055,0.2850572168827057,-0.7311487793922424,0.5490255951881409,-0.29002949595451355,0.3164955675601959,-0.6999281644821167,0.5708012580871582,-0.2927899658679962,0.3372853696346283,-0.6673040390014648,0.5960016250610352,-0.30160006880760193,0.3524401783943176,-0.6327676773071289,0.6200229525566101,-0.3102458417415619,0.35933127999305725,-0.6050174236297607,0.6392046213150024,-0.31414589285850525,0.3571430444717407,-0.5908756852149963,0.651634156703949,-0.31603336334228516,0.3523649275302887,-0.5947558283805847,0.6497902870178223,-0.3168628215789795,0.34881076216697693,-0.6089037656784058,0.6380950212478638,-0.3225437104701996,0.35191288590431213,-0.6202895045280457,0.6223856806755066,-0.33312463760375977,0.3583036959171295,-0.6192132830619812,0.6141835451126099,-0.341891884803772,0.36130768060684204,-0.6112529039382935,0.6155782341957092,-0.34925535321235657,0.36342376470565796,-0.6029160022735596,0.6184142231941223,-0.35160765051841736,0.3652815520763397,-0.6084880232810974,0.6104782819747925,-0.3524424731731415,0.37038829922676086,-0.6245582699775696,0.590359091758728,-0.35548916459083557,0.3793400526046753,-0.6445151567459106,0.5606502890586853,-0.35238197445869446,0.3821592330932617,-0.6695998907089233,0.5304878354072571,-0.3470471501350403,0.3817027509212494,-0.6940520405769348,0.5021485090255737,-0.34247833490371704,0.37980419397354126,-0.7088598012924194,0.4857727587223053,-0.3411184549331665,0.37926164269447327,-0.7152411341667175,0.4777330756187439,-0.342100590467453,0.37996870279312134,-0.7158719897270203,0.47551897168159485,-0.3441515564918518,0.38129547238349915,-0.7139753103256226,0.47582852840423584,-0.3458609879016876,0.3823954463005066,-0.7121478915214539,0.47644439339637756,-0.348346084356308,0.38399538397789,-0.7093176245689392,0.4775678813457489,-0.34924185276031494,0.3844127953052521,-0.7051838040351868,0.4826725423336029,-0.34930816292762756,0.38396474719047546,-0.6986856460571289,0.4923344850540161,-0.3510986566543579,0.38444554805755615,-0.6902959942817688,0.5024170875549316,-0.34920769929885864,0.38198280334472656,-0.682716429233551,0.5157920718193054,-0.3471018373966217,0.38032421469688416,-0.6731829047203064,0.530752956867218,-0.3453836739063263,0.38095009326934814,-0.6589282155036926,0.5489997863769531,-0.3410305380821228,0.38243231177330017,-0.6377812623977661,0.575046718120575,-0.3252440094947815,0.3770282566547394,-0.6093530058860779,0.6170534491539001,-0.29831790924072266,0.36349669098854065,-0.575425922870636,0.6691498756408691,-0.2635473310947418,0.3416750729084015,-0.5409602522850037,0.7219161987304688,-0.21552380919456482,0.30158722400665283,-0.47628989815711975,0.7973346710205078,-0.15535472333431244,0.2538520097732544,-0.37113136053085327,0.8795939683914185,-0.08477285504341125,0.20866674184799194,-0.2061130851507187,0.9522547721862793,-0.03840479627251625,0.14819057285785675,-0.17512625455856323,0.9725715517997742,0.0009266336564905941,0.032819438725709915,-0.2041163593530655,0.9783958792686462,0.006615767255425453,0.04593135043978691,-0.1742248833179474,0.9836118221282959,0.0025257442612200975,0.09310377389192581,-0.11761318147182465,0.988682210445404,-0.003929866943508387,0.11560174077749252,-0.09861711412668228,0.9883802533149719,-0.022931396961212158,0.18886804580688477,-0.09761769324541092,0.9768694043159485,-0.11525096744298935,0.2557336091995239,-0.31430524587631226,0.9069342613220215,-0.19361521303653717,0.28012385964393616,-0.5193524360656738,0.7837836742401123,-0.2454894632101059,0.2667456269264221,-0.6542322635650635,0.6637483835220337,-0.299314945936203,0.24995532631874084,-0.7144683003425598,0.5809199810028076,-0.32057592272758484,0.24606242775917053,-0.7359946966171265,0.5431354641914368,-0.30486685037612915,0.26924893260002136,-0.7587317824363708,0.5088096857070923,-0.26918262243270874,0.2875590920448303,-0.7548347115516663,0.5244759917259216,-0.2825791835784912,0.34037747979164124,-0.6911023855209351,0.5715502500534058,-0.3156348466873169,0.3851530849933624,-0.5883157849311829,0.6371155977249146,-0.3390040695667267,0.42007333040237427,-0.5067101716995239,0.6722049117088318,-0.36738651990890503,0.4548531174659729,-0.47137752175331116,0.6602568030357361,-0.3993525803089142,0.4729059338569641,-0.4766099750995636,0.6242759227752686,-0.4442518949508667,0.47564902901649475,-0.48843973875045776,0.5812271237373352,-0.496010422706604,0.4655826985836029,-0.4938458800315857,0.5415928363800049,-0.5442724823951721,0.45079493522644043,-0.5064899921417236,0.4939831495285034,-0.5993375182151794,0.42704400420188904,-0.4947820007801056,0.4621890187263489,-0.639644205570221,0.39194539189338684,-0.4807879328727722,0.4539571702480316,-0.6678326725959778,0.3630492687225342,-0.4666752815246582,0.4521161615848541,-0.684300422668457,0.3452621102333069,-0.45791661739349365,0.4503769874572754,-0.6894140839576721,0.3380909860134125,-0.4589338004589081,0.4469703733921051,-0.675815224647522,0.3466101884841919,-0.4828243851661682,0.4359079599380493,-0.6654114723205566,0.3478670120239258,-0.49567049741744995,0.436493843793869,-0.6606189012527466,0.3514975309371948,-0.4981823265552521,0.4380028545856476,-0.65572589635849,0.362367182970047,-0.4963403046131134,0.43858861923217773,-0.6427633166313171,0.3745317757129669,-0.49785295128822327,0.44578447937965393,-0.6174775958061218,0.38128697872161865,-0.506908655166626,0.46517235040664673,-0.5807759165763855,0.38235506415367126,-0.5295496582984924,0.48588186502456665,-0.5324247479438782,0.37452343106269836,-0.5664503574371338,0.5053613185882568,-0.46984952688217163,0.3357619643211365,-0.6221346855163574,0.5286338329315186,-0.40809929370880127,0.32513517141342163,-0.628370463848114,0.5769685506820679,-0.36498183012008667,0.3387031853199005,-0.6029146313667297,0.6233477592468262,-0.3289222717285156,0.37599509954452515,-0.540204644203186,0.6772124767303467,-0.2749735116958618,0.40591591596603394,-0.4231164753437042,0.7619673609733582,-0.20169664919376373,0.3360428810119629,-0.335040420293808,0.8568206429481506,-0.05293254181742668,0.09843620657920837,-0.31483179330825806,0.942544162273407,0.0066530280746519566,-0.06239932402968407,-0.3106463551521301,0.9484518766403198,0.03399732708930969,-0.06825640052556992,-0.2895401418209076,0.9541235566139221,0.08244435489177704,-0.051049936562776566,-0.17988787591457367,0.978895902633667,0.120366171002388,-0.08523260056972504,-0.1576298326253891,0.9764221906661987,0.1401170939207077,-0.15828610956668854,-0.27971675992012024,0.9365208148956299,0.11345160007476807,-0.18992654979228973,-0.41648298501968384,0.8818155527114868,0.062116965651512146,-0.13810700178146362,-0.5104756355285645,0.8464529514312744,0.0071478639729321,-0.0390712171792984,-0.5367914438247681,0.8427795171737671,-0.06686212867498398,0.07354268431663513,-0.53533536195755,0.8387711048126221,-0.13372856378555298,0.16180375218391418,-0.5163299441337585,0.8302648067474365,-0.1842212975025177,0.21456731855869293,-0.4939556121826172,0.8222112059593201,-0.22219398617744446,0.22781075537204742,-0.4992804229259491,0.8058853149414062,-0.2482748031616211,0.22222431004047394,-0.5218238830566406,0.785287082195282,-0.26917165517807007,0.2156689465045929,-0.53687584400177,0.7699337005615234,-0.2851363718509674,0.2094329595565796,-0.5563565492630005,0.7518659830093384,-0.2995227575302124,0.2060416042804718,-0.5904234051704407,0.7205783724784851,-0.3068678081035614,0.1996004581451416,-0.6216572523117065,0.6924840211868286,-0.29737481474876404,0.18853645026683807,-0.6536592245101929,0.6698894500732422,-0.27624428272247314,0.18239837884902954,-0.677930474281311,0.6563766002655029,-0.24312962591648102,0.18222928047180176,-0.6898593306541443,0.6570956110954285,-0.2028285562992096,0.18437249958515167,-0.6943314075469971,0.6654106378555298,-0.16517095267772675,0.18955571949481964,-0.687009871006012,0.6817659139633179,-0.1475028544664383,0.20532041788101196,-0.6518694162368774,0.7149494886398315,-0.12737999856472015,0.20688843727111816,-0.5896095037460327,0.7702805995941162,-0.1012561023235321,0.18837113678455353,-0.5139457583427429,0.8307366371154785,-0.06199412792921066,0.1507900506258011,-0.44485917687416077,0.8806357979774475,-0.019223660230636597,0.10017574578523636,-0.36871445178985596,0.9239290952682495,0.007374733220785856,0.0870036631822586,-0.29754379391670227,0.950706958770752,0.0011266934452578425,0.10289741307497025,-0.3000427484512329,0.9483592510223389,-0.032732002437114716,0.055606909096241,-0.35597339272499084,0.9322657585144043,-0.0199604295194149,0.006025432609021664,-0.32765814661979675,0.9445663094520569,0.030435480177402496,0.03334950655698776,-0.12730562686920166,0.9908353686332703,0.015390905551612377,0.07041926681995392,-0.11407602578401566,0.9908536672592163,-0.05210098624229431,0.14747273921966553,-0.2046220898628235,0.966264545917511,-0.1234322041273117,0.25433892011642456,-0.2479572743177414,0.9266031384468079,-0.21732932329177856,0.35840117931365967,-0.359710693359375,0.8336215019226074,-0.27517399191856384,0.40859317779541016,-0.44191405177116394,0.7496951818466187,-0.30303773283958435,0.4403146207332611,-0.5123603940010071,0.67214435338974,-0.3183916211128235,0.4737187623977661,-0.5519222021102905,0.6079466938972473,-0.3441418707370758,0.49565160274505615,-0.5252085328102112,0.6000431776046753,-0.36981043219566345,0.5056130886077881,-0.44752153754234314,0.6382163763046265,-0.36746013164520264,0.5282418131828308,-0.3720180094242096,0.6689814925193787,-0.37165307998657227,0.561862587928772,-0.367329865694046,0.6412903070449829,-0.3961406648159027,0.5956662893295288,-0.38123416900634766,0.5855892300605774,-0.4277174472808838,0.6112452745437622,-0.37604042887687683,0.5495731234550476,-0.4451926350593567,0.6213701963424683,-0.36900627613067627,0.5287125706672668,-0.4464384615421295,0.6262654066085815,-0.3587137758731842,0.5289695262908936,-0.43716341257095337,0.6309553384780884,-0.34182366728782654,0.5421624779701233,-0.4345030188560486,0.6361303925514221,-0.33860957622528076,0.5402674078941345,-0.4368095099925995,0.6315333247184753,-0.3410375416278839,0.5422698259353638,-0.4411773085594177,0.6184859871864319,-0.34494921565055847,0.551223874092102,-0.44712409377098083,0.6001774668693542,-0.3539910316467285,0.5608541369438171,-0.45415666699409485,0.5771945714950562,-0.37368908524513245,0.5665197968482971,-0.4591869115829468,0.5568669438362122,-0.410822331905365,0.5570203065872192,-0.4446897804737091,0.5517473220825195,-0.47597745060920715,0.5208371877670288,-0.417866051197052,0.5281549692153931,-0.5373231768608093,0.5076652765274048,-0.3914280831813812,0.4888388216495514,-0.587878942489624,0.5120733380317688,-0.364713579416275,0.44110897183418274,-0.629067063331604,0.526005208492279,-0.3367224335670471,0.3875297009944916,-0.6612843871116638,0.5469384789466858,-0.31025272607803345,0.33428218960762024,-0.6859869956970215,0.5669395923614502,-0.2815689742565155,0.2824022173881531,-0.7054088115692139,0.5859746932983398,-0.251036673784256,0.23478403687477112,-0.7155531644821167,0.6081452965736389,-0.21801771223545074,0.19222788512706757,-0.7186494469642639,0.6317117214202881,-0.1812550574541092,0.14826177060604095,-0.7141349911689758,0.6596789360046387,-0.1456378698348999,0.10609287768602371,-0.697578489780426,0.6934826970100403,-0.11269330233335495,0.0698898509144783,-0.6691845059394836,0.731168806552887,-0.08196299523115158,0.040698226541280746,-0.6362876892089844,0.7660050392150879,-0.05348280072212219,0.018519870936870575,-0.6042659282684326,0.7947700023651123,-0.02801131270825863,0.0014858127105981112,-0.5793845653533936,0.8145714998245239,-0.007405146025121212,-0.01374826580286026,-0.5699530243873596,0.8215289115905762,0.007129954174160957,-0.030986549332737923,-0.5795032382011414,0.8143494129180908,0.01852673478424549,-0.04972967877984047,-0.5940337777137756,0.8026877641677856,0.02742997370660305,-0.0675101950764656,-0.6075966358184814,0.7908958792686462,0.03405183181166649,-0.08215472102165222,-0.617889404296875,0.7812194228172302,0.03819066286087036,-0.09191640466451645,-0.6243240237236023,0.7747983336448669,0.04000253230333328,-0.09524798393249512,-0.625845730304718,0.7730749249458313],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998807907104,1,1,0.9999999403953552,0.9999999403953552,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.000001975103941731504,19.450502395629883,4.17614501202479e-7,0.00001104455986933317,19.45050048828125,-0.0000072228003773489036],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[0.14937162399291992,-0.14748121798038483,-0.019200144335627556,0.9775319695472717,0.14937163889408112,-0.14748120307922363,-0.019200200214982033,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999998211860657,0.9999999403953552,0.9999998211860657,0.9999998807907104,0.9999998807907104,0.9999997019767761],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,5.4666666984558105],"values":[0.1469007134437561,6.40949821472168,0.25809967517852783,0.14691346883773804,6.4095001220703125,0.25810033082962036],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[0.020259661599993706,0.012515285983681679,-0.011196224018931389,0.999653697013855,0.020259661599993706,0.012515289708971977,-0.011196217499673367,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999999403953552,1.000000238418579,1.0000001192092896,1.0000001192092896,1.000000238418579,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,5.4666666984558105],"values":[3.7910305650257214e-7,6.815906524658203,-5.23526580309408e-7,0.000009751487596076913,6.815896034240723,0.0000024017745090532117],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379,4.599999904632568,4.633333206176758,4.666666507720947,4.699999809265137,4.733333110809326,4.766666889190674,4.800000190734863,4.833333492279053,4.866666793823242,4.900000095367432,4.933333396911621,4.9666666984558105,5,5.0333333015441895,5.066666603088379,5.099999904632568,5.133333206176758,5.166666507720947,5.199999809265137,5.233333110809326,5.266666889190674,5.300000190734863,5.333333492279053,5.366666793823242,5.400000095367432,5.433333396911621,5.4666666984558105],"values":[0.2577109932899475,0.4488808512687683,-0.04082868993282318,0.8546484708786011,0.26494747400283813,0.4310341775417328,-0.036429423838853836,0.8617919087409973,0.27058807015419006,0.4144650399684906,-0.0330008901655674,0.8682809472084045,0.27482298016548157,0.3990602493286133,-0.03052162565290928,0.8742377758026123,0.2778591215610504,0.3847045302391052,-0.028952613472938538,0.8797491192817688,0.2798997759819031,0.3712806701660156,-0.028244532644748688,0.8848779797554016,0.2811472415924072,0.35867464542388916,-0.02833554334938526,0.8896661400794983,0.28181493282318115,0.34677261114120483,-0.0291419867426157,0.894136369228363,0.28211039304733276,0.3354644775390625,-0.030565880239009857,0.8983001112937927,0.28222620487213135,0.324640691280365,-0.032499153167009354,0.9021643996238708,0.28243589401245117,0.3142009973526001,-0.03478531539440155,0.9057028889656067,0.28275591135025024,0.30402418971061707,-0.03735029324889183,0.9089683294296265,0.28318092226982117,0.2939853370189667,-0.040110427886247635,0.9120155572891235,0.28383389115333557,0.2839682400226593,-0.042922765016555786,0.914854109287262,0.2842162251472473,0.2745368182659149,-0.046042971312999725,0.9174588322639465,0.2839677333831787,0.2651424705982208,-0.049328792840242386,0.9201241135597229,0.28328603506088257,0.25573205947875977,-0.05229579284787178,0.9228300452232361,0.28252652287483215,0.24607712030410767,-0.05496115982532501,0.9255290627479553,0.2822929322719574,0.2360377460718155,-0.056978803128004074,0.9280896186828613,0.2809029519557953,0.2280001938343048,-0.05955590680241585,0.9303561449050903,0.2789648473262787,0.21877732872962952,-0.061692774295806885,0.9330108165740967,0.27576109766960144,0.20875544846057892,-0.06353135406970978,0.936130702495575,0.2706807255744934,0.19933673739433289,-0.06514640152454376,0.9395492672920227,0.26409077644348145,0.1907682865858078,-0.06669022887945175,0.9430885314941406,0.25593435764312744,0.18317677080631256,-0.06779833883047104,0.9467561841011047,0.246744766831398,0.17680490016937256,-0.06845825910568237,0.9503528475761414,0.23792225122451782,0.17083734273910522,-0.06904774159193039,0.953645646572113,0.23170624673366547,0.16570444405078888,-0.06608323007822037,0.9562882781028748,0.22612863779067993,0.16252489387989044,-0.06041937321424484,0.9585410952568054,0.22089660167694092,0.16116099059581757,-0.05246351286768913,0.9604579210281372,0.2147374302148819,0.16021224856376648,-0.04346173629164696,0.9624608755111694,0.2077893316745758,0.15955086052417755,-0.034509725868701935,0.9644564390182495,0.20077358186244965,0.15750034153461456,-0.027178732678294182,0.9665117263793945,0.19708341360092163,0.15494459867477417,-0.021530596539378166,0.9678257703781128,0.19906441867351532,0.1522206813097,-0.01681799814105034,0.9679459929466248,0.20424531400203705,0.15162335336208344,-0.01162415649741888,0.967036247253418,0.2093210071325302,0.15320304036140442,-0.0058235228061676025,0.965753436088562,0.21221092343330383,0.15627609193325043,-0.0007586656138300896,0.9646469354629517,0.2145569920539856,0.16142214834690094,-0.0063058724626898766,0.9632592797279358,0.21603022515773773,0.16441640257835388,-0.029231509193778038,0.9619998931884766,0.21415241062641144,0.1638975441455841,-0.05726470425724983,0.9612476825714111,0.20956142246723175,0.1601507067680359,-0.08190065622329712,0.9611077308654785,0.20210565626621246,0.15844005346298218,-0.0945955291390419,0.9618220925331116,0.19142374396324158,0.1587502509355545,-0.1025858223438263,0.9631362557411194,0.18178367614746094,0.16010157763957977,-0.1091313362121582,0.9640604853630066,0.17255769670009613,0.1661015748977661,-0.11586301028728485,0.9639553427696228,0.1644183099269867,0.1740662008523941,-0.11981191486120224,0.9634898900985718,0.1582852154970169,0.1817067414522171,-0.11842069029808044,0.9632782936096191,0.15662781894207,0.19088830053806305,-0.11121383309364319,0.9626322984695435,0.1563139259815216,0.20605359971523285,-0.09475278109312057,0.961316704750061,0.15442247688770294,0.2273535132408142,-0.07551173865795135,0.958520770072937,0.15057477355003357,0.2598019242286682,-0.056462861597537994,0.9521775841712952,0.14734448492527008,0.294116348028183,-0.0270554069429636,0.9439561367034912,0.14845359325408936,0.30826976895332336,0.013617905788123608,0.9395455718040466,0.15772345662117004,0.29411324858665466,0.05524419620633125,0.9410466551780701,0.17352381348609924,0.25180357694625854,0.09133418649435043,0.9477038383483887,0.1930800825357437,0.1955883949995041,0.11488417536020279,0.9546030759811401,0.21415194869041443,0.14356176555156708,0.12296031415462494,0.9583369493484497,0.23082251846790314,0.10344766080379486,0.12153611332178116,0.9598169326782227,0.2405678629875183,0.073701873421669,0.118428073823452,0.9605571031570435,0.24757839739322662,0.04611249268054962,0.11845644563436508,0.9604929089546204,0.25509533286094666,0.02264014072716236,0.11811095476150513,0.9594079256057739,0.2636382281780243,0.01557858195155859,0.11423557251691818,0.957706868648529,0.2747895419597626,0.032001882791519165,0.10254377871751785,0.9554848670959473,0.28677114844322205,0.05872492119669914,0.08374190330505371,0.9525234699249268,0.2974154055118561,0.08586211502552032,0.061909016221761703,0.9488619565963745,0.3040693402290344,0.11452479660511017,0.040790997445583344,0.9448608160018921,0.3014895021915436,0.1399625986814499,0.028344077989459038,0.9427148103713989,0.2911446690559387,0.15731225907802582,0.025731727480888367,0.9433056712150574,0.2832391560077667,0.16458314657211304,0.02720322273671627,0.9444299936294556,0.27765408158302307,0.16271330416202545,0.0313277468085289,0.9462828636169434,0.2724480628967285,0.15370933711528778,0.03829927742481232,0.949040949344635,0.267594575881958,0.14030560851097107,0.04591164365410805,0.9521552324295044,0.2631235420703888,0.12318677455186844,0.052402667701244354,0.9554292559623718,0.2580684721469879,0.10134892910718918,0.0585421659052372,0.959010899066925,0.25198015570640564,0.07404812425374985,0.06551402807235718,0.9626685976982117,0.24462850391864777,0.03985435515642166,0.07397713512182236,0.965968906879425,0.23559974133968353,-0.003170506563037634,0.08301182091236115,0.9682931900024414,0.22409150004386902,-0.05818016082048416,0.09108300507068634,0.9685566425323486,0.21093925833702087,-0.11723480373620987,0.09658455103635788,0.9656252264976501,0.1979224532842636,-0.17172753810882568,0.09861122071743011,0.9600064158439636,0.18796087801456451,-0.21560712158679962,0.09831199795007706,0.953162670135498,0.18108612298965454,-0.2330511063337326,0.09304886311292648,0.9509137272834778,0.17829397320747375,-0.2379360944032669,0.08470926433801651,0.951011061668396,0.17838157713413239,-0.2519392669200897,0.07677963376045227,0.948056697845459,0.18062032759189606,-0.27348995208740234,0.07099927961826324,0.9420927166938782,0.18659862875938416,-0.2862080931663513,0.06106523424386978,0.9378362894058228,0.1958855837583542,-0.2862675189971924,0.04678867384791374,0.9367446303367615,0.20482291281223297,-0.2880428731441498,0.033382199704647064,0.9348607063293457,0.21195591986179352,-0.3039141893386841,0.026074575260281563,0.9284562468528748,0.22062720358371735,-0.32751330733299255,0.02289506047964096,0.9184413552284241,0.23192515969276428,-0.3388294577598572,0.021681047976017,0.9115564823150635,0.23757225275039673,-0.3373565375804901,0.019142890349030495,0.9107049703598022,0.22931890189647675,-0.3337242901325226,0.013162385672330856,0.9142580032348633,0.2122783213853836,-0.32978883385658264,0.005938236135989428,0.919859766960144,0.19129297137260437,-0.3266548812389374,-0.0012275545159354806,0.9255820512771606,0.17012736201286316,-0.32385995984077454,-0.007013917434960604,0.930656909942627,0.14952269196510315,-0.32659903168678284,-0.008009822107851505,0.9332266449928284,0.13083723187446594,-0.32593151926994324,-0.00641571544110775,0.9362741112709045,0.11937805265188217,-0.32034412026405334,-0.0018272585002705455,0.9397473931312561,0.10861906409263611,-0.3100469708442688,-0.000037075569707667455,0.9444960355758667,0.09869368374347687,-0.3001920282840729,0.00005071350824437104,0.9487593770027161,0.09342560917139053,-0.2936386466026306,0.0037849433720111847,0.9513325691223145,0.0898967906832695,-0.2911404073238373,0.0065875789150595665,0.9524245262145996,0.08377164602279663,-0.2892172634601593,0.011984873563051224,0.9535156488418579,0.08001480996608734,-0.2825782001018524,0.017604103311896324,0.9557391405105591,0.08179623633623123,-0.2681463062763214,0.02679380774497986,0.959525465965271,0.09030036628246307,-0.2519506514072418,0.03733862191438675,0.9627941250801086,0.11110828816890717,-0.22846682369709015,0.04448044300079346,0.9661673903465271,0.13206574320793152,-0.19467486441135406,0.04405776783823967,0.9709373116493225,0.14041289687156677,-0.16434311866760254,0.04191114753484726,0.9754583835601807,0.1493576467037201,-0.14382043480873108,0.03911205753684044,0.9774856567382812,0.16996265947818756,-0.12898853421211243,0.03941912576556206,0.9761766791343689,0.1860523819923401,-0.11620312929153442,0.031401198357343674,0.9751386046409607,0.2041054666042328,-0.10710293054580688,0.012475899420678616,0.972992479801178,0.2229580134153366,-0.09692072123289108,-0.007678133901208639,0.9699676036834717,0.2408340573310852,-0.08929427713155746,-0.019000254571437836,0.9662631750106812,0.26848819851875305,-0.08885364234447479,-0.009703364223241806,0.9591271877288818,0.2902871370315552,-0.08176518231630325,0.026732100173830986,0.9530651569366455,0.3013734519481659,-0.07047940790653229,0.06318645179271698,0.9487961530685425,0.3004515469074249,-0.05416298285126686,0.097112275660038,0.947293221950531,0.2938896715641022,-0.029628843069076538,0.12704668939113617,0.9468950033187866,0.2907024025917053,0.004879763815551996,0.1474173665046692,0.9453763365745544,0.2919081747531891,0.03323236107826233,0.16246244311332703,0.9419613480567932,0.2951311469078064,0.04304373264312744,0.17604520916938782,0.9381113648414612,0.29765912890434265,0.05535184592008591,0.17795038223266602,0.9363059997558594,0.3046088218688965,0.07664573937654495,0.17053620517253876,0.9339466691017151,0.3131921887397766,0.09699655324220657,0.158906951546669,0.9312630891799927,0.32122960686683655,0.11386659741401672,0.14455969631671906,0.9289501905441284,0.3284232020378113,0.12617456912994385,0.1301935762166977,0.9269670248031616,0.33330196142196655,0.13893090188503265,0.12083663046360016,0.924665629863739,0.33714109659194946,0.1530335247516632,0.10958772897720337,0.9224463105201721,0.3387799561023712,0.1679074913263321,0.0971272885799408,0.9206528067588806,0.33677154779434204,0.18293215334415436,0.08480185270309448,0.9197442531585693,0.33009013533592224,0.19815152883529663,0.07275013625621796,0.9200456142425537,0.3207795023918152,0.2141609489917755,0.06070764362812042,0.9206249117851257,0.3110065460205078,0.22989322245121002,0.04909232258796692,0.9208767414093018,0.3010408580303192,0.24394990503787994,0.038427770137786865,0.9210788011550903,0.29193565249443054,0.25651779770851135,0.02921437658369541,0.9209336638450623,0.2842042148113251,0.2686617076396942,0.021450523287057877,0.9201025366783142,0.2777576148509979,0.28090551495552063,0.015967143699526787,0.9185248017311096,0.273648738861084,0.293109655380249,0.01216537319123745,0.9159995317459106,0.27128055691719055,0.30567750334739685,0.009607373736798763,0.9126203060150146,0.26893317699432373,0.3183926045894623,0.0067648510448634624,0.9089859127998352,0.26672297716140747,0.3310469090938568,0.003657745663076639,0.9051262140274048,0.2640857994556427,0.34297704696655273,0.00029797531897202134,0.9014573693275452,0.2610582411289215,0.3538391888141632,-0.0033149579539895058,0.898128867149353,0.2584122121334076,0.3638206124305725,-0.007031709887087345,0.8948789238929749,0.2563420534133911,0.3730680048465729,-0.010763285681605339,0.8916239142417908,0.25527530908584595,0.3814883828163147,-0.014293644577264786,0.8883112072944641,0.2546927332878113,0.3890025019645691,-0.017556075006723404,0.8851556181907654,0.25456395745277405,0.3959529399871826,-0.020584283396601677,0.8820400834083557,0.2552945017814636,0.4026218056678772,-0.0231816116720438,0.8787394165992737,0.2562829554080963,0.4088795483112335,-0.025524217635393143,0.8754913210868835,0.2572622001171112,0.4146077036857605,-0.027705935761332512,0.8724385499954224,0.2581968605518341,0.4198041558265686,-0.029705796390771866,0.8696070313453674,0.25901368260383606,0.4245021641254425,-0.03154471144080162,0.8670148849487305,0.25968241691589355,0.4287363290786743,-0.033250242471694946,0.8646644949913025,0.2601739764213562,0.43257206678390503,-0.034829363226890564,0.8625414967536926,0.26045045256614685,0.435996413230896,-0.036268848925828934,0.8606725931167603,0.26046180725097656,0.43896088004112244,-0.03755679354071617,0.85910564661026,0.2601477801799774,0.44156280159950256,-0.03873424604535103,0.8578140735626221,0.25947514176368713,0.44381359219551086,-0.03979796916246414,0.8568070530891418,0.25770851969718933,0.44907188415527344,-0.04089486226439476,0.8545457124710083],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1,1,0.9999999403953552,1.0000001192092896,1,1],"interpolation":2300,"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,5.4666666984558105],"values":[0.5746020674705505,51.8169059753418,-20.52269744873047,0.5745956897735596,51.8168830871582,-20.52271270751953],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[-0.18743079900741577,8.082884050963912e-8,-0.005247792229056358,0.9822638034820557,-0.18743082880973816,1.0814709128226241e-7,-0.005247784778475761,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,5.4666666984558105],"values":[1.0000001192092896,1.0000001192092896,1.0000001192092896,1.0000001192092896,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,5.4666666984558105],"values":[-0.574603259563446,4.330286026000977,20.522716522216797,-0.5746024250984192,4.330305099487305,20.52269744873047],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[0.6296831369400024,-8.573925924793002e-8,0.01763101853430271,0.7766519784927368,0.6296830773353577,-6.835156796114461e-8,0.017631007358431816,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.9999999403953552,1.0000003576278687,1.000000238418579,1,1.000000238418579,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,5.4666666984558105],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,5.4666666984558105],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,5.4666666984558105],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"d8053648-f3cb-43d5-8f97-353ac7d0028c","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/insword-and-shield-slash.json b/public/anims/baked/sword_shield/insword-and-shield-slash.json new file mode 100644 index 0000000..d918a53 --- /dev/null +++ b/public/anims/baked/sword_shield/insword-and-shield-slash.json @@ -0,0 +1 @@ +{"name":"sword_shield/insword-and-shield-slash","duration":1.7000000476837158,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.5806028842926025,56.27493667602539,0.03721565008163452,-0.42805778980255127,56.00941467285156,0.5324831008911133,-0.24404489994049072,55.72565460205078,1.0960255861282349,-0.03865236043930054,55.57026672363281,1.8817975521087646,0.2062172293663025,55.50332260131836,2.8395192623138428,0.49125516414642334,55.53767776489258,4.043420314788818,0.7923306822776794,55.60991668701172,5.351734161376953,1.1100603342056274,55.70604705810547,6.7549848556518555,1.5081768035888672,56.1225700378418,8.768156051635742,1.7371443510055542,56.655540466308594,10.79256820678711,1.7324281930923462,57.34444046020508,12.794827461242676,1.7461631298065186,58.01448059082031,14.84062385559082,1.7427587509155273,58.4493408203125,17.151874542236328,1.468902349472046,58.51986312866211,19.415058135986328,0.9324515461921692,58.36884689331055,21.611162185668945,0.01853889226913452,57.89317321777344,23.943626403808594,-0.9734368324279785,57.28437805175781,26.176496505737305,-1.952386736869812,57.000205993652344,27.97093391418457,-3.2111611366271973,57.1023063659668,29.413616180419922,-4.5356550216674805,57.632694244384766,30.49863052368164,-5.774686813354492,58.860374450683594,31.546812057495117,-6.810389041900635,60.115421295166016,32.50301742553711,-7.432973384857178,61.01994705200195,33.026790618896484,-7.686971664428711,61.511146545410156,33.1785888671875,-7.865643501281738,61.898353576660156,33.266780853271484,-8.013152122497559,62.248451232910156,33.321388244628906,-8.053926467895508,62.49006271362305,33.245304107666016,-8.00370979309082,62.67317581176758,33.01950454711914,-7.796690940856934,62.91081237792969,32.57255172729492,-7.5354719161987305,63.15272903442383,32.00617599487305,-7.184747695922852,63.55788803100586,31.1369686126709,-6.4993391036987305,64.18955993652344,29.381275177001953,-5.469149112701416,64.6544418334961,27.01319122314453,-4.4845170974731445,64.64556884765625,24.77488136291504,-3.694042205810547,64.13977813720703,22.661598205566406,-2.9671058654785156,63.43641662597656,20.5398006439209,-2.291182041168213,62.56769561767578,18.308063507080078,-1.7324881553649902,61.53300476074219,15.974343299865723,-1.451542615890503,59.94472122192383,13.191312789916992,-1.306430697441101,58.10435485839844,10.251790046691895,-1.1089725494384766,56.53977966308594,7.562553405761719,-1.0881385803222656,55.43568420410156,5.116037368774414,-1.0174825191497803,55.21246337890625,3.983900308609009,-0.9540262222290039,55.120121002197266,3.062114715576172,-0.8861302733421326,55.09471893310547,2.2475059032440186,-0.8204087018966675,55.09291076660156,1.4750196933746338,-0.7538955211639404,55.190208435058594,0.8844753503799438,-0.7004525661468506,55.37925338745117,0.48918282985687256,-0.6560940742492676,55.627296447753906,0.22463014721870422,-0.6178328990936279,55.943824768066406,0.11195409297943115,-0.58060622215271,56.274932861328125,0.03721803426742554],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.28925591707229614,-0.3346841633319855,0.2664211690425873,0.8563511371612549,0.29235851764678955,-0.3273513913154602,0.26347440481185913,0.8590394854545593,0.301119863986969,-0.31425249576568604,0.25741222500801086,0.8627347350120544,0.3089034855365753,-0.2885976731777191,0.25097060203552246,0.8708064556121826,0.3152083158493042,-0.2563663721084595,0.24350307881832123,0.8806964755058289,0.3211263120174408,-0.22147834300994873,0.23431147634983063,0.8904625177383423,0.3262802064418793,-0.1845235973596573,0.22416190803050995,0.8995797634124756,0.3291991651058197,-0.14195138216018677,0.21278031170368195,0.9089567065238953,0.32343775033950806,-0.07489831745624542,0.19575999677181244,0.9227439165115356,0.3205041289329529,0.005920276511460543,0.1817251443862915,0.9296332597732544,0.3325899839401245,0.08098399639129639,0.15899325907230377,0.9260381460189819,0.35712122917175293,0.15474019944667816,0.12151604890823364,0.9131011962890625,0.373192697763443,0.22338683903217316,0.08547072857618332,0.8963929414749146,0.37726354598999023,0.2716447412967682,0.05236094817519188,0.8838210701942444,0.3808908462524414,0.31922653317451477,0.02263352833688259,0.8674700260162354,0.38936612010002136,0.37896472215652466,-0.0055704438127577305,0.8394932150840759,0.39986351132392883,0.44651222229003906,-0.03207940235733986,0.7998168468475342,0.411450058221817,0.4930124580860138,-0.05231123045086861,0.7647948265075684,0.4136049747467041,0.4960971176624298,-0.05840981751680374,0.7611878514289856,0.40285494923591614,0.46767109632492065,-0.06141911819577217,0.7843592166900635,0.3729417622089386,0.3916921019554138,-0.05654621124267578,0.8392224311828613,0.33457183837890625,0.30387046933174133,-0.04865027964115143,0.8907062411308289,0.2980605661869049,0.23516453802585602,-0.0368744395673275,0.9243904948234558,0.2824588119983673,0.20100854337215424,-0.026145583018660545,0.9376187920570374,0.273677796125412,0.1751958131790161,-0.016113493591547012,0.9455935955047607,0.26648345589637756,0.15101231634616852,-0.006233370862901211,0.9519155025482178,0.26779836416244507,0.13544943928718567,0.003076026914641261,0.9539015293121338,0.274332731962204,0.1232757717370987,0.012871671468019485,0.9536136388778687,0.2826102375984192,0.10713884234428406,0.02329857274889946,0.9529480338096619,0.2918989360332489,0.08973143994808197,0.034243810921907425,0.9516147375106812,0.29964587092399597,0.06486105918884277,0.04604634642601013,0.950728714466095,0.29846033453941345,0.009220571257174015,0.06671015173196793,0.9520431756973267,0.28991585969924927,-0.05978880822658539,0.0946211889386177,0.9504846334457397,0.28357234597206116,-0.11352699249982834,0.11994875967502594,0.9446219801902771,0.28100988268852234,-0.15256911516189575,0.1410098522901535,0.9369484782218933,0.2781703472137451,-0.18880483508110046,0.16231736540794373,0.9276999235153198,0.2749726176261902,-0.22255660593509674,0.18363872170448303,0.9171344041824341,0.27324363589286804,-0.2501692473888397,0.2028271108865738,0.9064295291900635,0.2757648527622223,-0.2642940580844879,0.21780230104923248,0.8981450796127319,0.28236639499664307,-0.27112072706222534,0.22821931540966034,0.8914475440979004,0.2888340950012207,-0.2841484546661377,0.23831798136234283,0.8826318979263306,0.29106998443603516,-0.30774253606796265,0.2498856633901596,0.8707066178321838,0.2906310260295868,-0.31920501589775085,0.25644853711128235,0.8647981882095337,0.289189875125885,-0.32705098390579224,0.26199501752853394,0.8606773614883423,0.2872169017791748,-0.3329509496688843,0.26703697443008423,0.8575205206871033,0.2854592204093933,-0.3381424844264984,0.27148357033729553,0.8546750545501709,0.28372302651405334,-0.3407139778137207,0.27449744939804077,0.8532680869102478,0.2834787666797638,-0.34095320105552673,0.27475857734680176,0.8531696796417236,0.28443658351898193,-0.33984532952308655,0.27320560812950134,0.8537914156913757,0.2866881489753723,-0.3374280631542206,0.27002161741256714,0.855009138584137,0.2892559766769409,-0.3346841335296631,0.2664211094379425,0.8563511967658997],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,0.9999997615814209,0.9999997615814209,0.9999998807907104,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[8.305602073669434,-6.711593151092529,5.719297409057617,8.305603981018066,-6.711597919464111,5.719293594360352],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.6466915607452393,-0.06025046482682228,-0.44836875796318054,0.6141053438186646,0.6417709589004517,-0.061733368784189224,-0.4402817487716675,0.624876856803894,0.6299023032188416,-0.06600397080183029,-0.4339978098869324,0.6407124996185303,0.6236813068389893,-0.07847126573324203,-0.4179028570652008,0.655912458896637,0.619573712348938,-0.09696368873119354,-0.40355148911476135,0.6662377119064331,0.6175748109817505,-0.1242644339799881,-0.3975348472595215,0.6671774983406067,0.6278656721115112,-0.1489068865776062,-0.3645661175251007,0.6713441610336304,0.6457183361053467,-0.17431394755840302,-0.311791330575943,0.6748693585395813,0.6666441559791565,-0.23455873131752014,-0.28332021832466125,0.6483035087585449,0.6975921392440796,-0.27247267961502075,-0.2230055034160614,0.6240133047103882,0.717384397983551,-0.2790997326374054,-0.16404497623443604,0.6168893575668335,0.7209242582321167,-0.2765810489654541,-0.10700707882642746,0.6263552308082581,0.7237850427627563,-0.26430168747901917,-0.03954988345503807,0.6361727118492126,0.7240720391273499,-0.24094688892364502,0.008055128157138824,0.6462193727493286,0.718824565410614,-0.21847185492515564,0.05255894362926483,0.657874584197998,0.7034744024276733,-0.20342515408992767,0.0942678153514862,0.6744297742843628,0.681032657623291,-0.19397559762001038,0.13589094579219818,0.6928936839103699,0.663406491279602,-0.1831367462873459,0.15399408340454102,0.7089700102806091,0.6678176522254944,-0.1609831303358078,0.12990935146808624,0.7150017619132996,0.6901262998580933,-0.12164901196956635,0.07894185930490494,0.709010124206543,0.7397623062133789,-0.05158410966396332,0.008514064364135265,0.6708340644836426,0.785835862159729,0.01487583015114069,-0.0968291163444519,0.6106265187263489,0.8132516145706177,0.05898354575037956,-0.1716121882200241,0.5528942346572876,0.8244950771331787,0.07203428447246552,-0.19282452762126923,0.527103066444397,0.8349951505661011,0.07611814141273499,-0.1944636106491089,0.5090903639793396,0.8433080911636353,0.0760011374950409,-0.2103901505470276,0.48866280913352966,0.846778154373169,0.06820831447839737,-0.2316974699497223,0.47395211458206177,0.8613415956497192,0.05600891262292862,-0.2177475243806839,0.4555652141571045,0.869415283203125,0.04300941899418831,-0.23521463572978973,0.43236714601516724,0.871135950088501,0.02619403414428234,-0.2619766592979431,0.41449275612831116,0.8647927641868591,0.008298774249851704,-0.2911653220653534,0.40900781750679016,0.8606566786766052,-0.019644271582365036,-0.31950265169143677,0.39598265290260315,0.8607558012008667,-0.03599445894360542,-0.33809876441955566,0.3788048028945923,0.8629150390625,-0.044405534863471985,-0.3266419768333435,0.38302847743034363,0.8443660140037537,-0.05727866664528847,-0.34706082940101624,0.4041212201118469,0.8211144804954529,-0.0726940706372261,-0.3714459240436554,0.4272170960903168,0.8024358153343201,-0.08561412245035172,-0.37843969464302063,0.4533766210079193,0.7847788333892822,-0.09396766126155853,-0.37310391664505005,0.48588648438453674,0.7556763887405396,-0.09304032474756241,-0.3685905933380127,0.5333269238471985,0.7166336178779602,-0.08731760829687119,-0.372244268655777,0.5833061337471008,0.6780975461006165,-0.0787169486284256,-0.38674336671829224,0.6200137138366699,0.6479260921478271,-0.064064159989357,-0.4120543599128723,0.6374157071113586,0.6336401104927063,-0.06225930154323578,-0.43721070885658264,0.6351935267448425,0.6244519948959351,-0.06268958747386932,-0.4569084942340851,0.6303684711456299,0.6218583583831787,-0.06259683519601822,-0.4659107029438019,0.6263393759727478,0.6228551268577576,-0.06119593232870102,-0.46825575828552246,0.6237332224845886,0.6274338960647583,-0.06075672805309296,-0.46619921922683716,0.6207202672958374,0.632938027381897,-0.06011822447180748,-0.46150830388069153,0.6186965107917786,0.6384124159812927,-0.059436067938804626,-0.455893337726593,0.6172991394996643,0.6431431174278259,-0.05940760672092438,-0.45093390345573425,0.6160327196121216,0.6466918587684631,-0.06025047227740288,-0.44836869835853577,0.6141050457954407],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999998807907104,1,0.9999994039535522,0.9999998807907104,1,0.9999990463256836],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000001175460965896491,25.81270980834961,-0.0000028554120490298374,-0.0000017708764517010422,25.812707901000977,-0.000004281139354134211],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.40776705741882324,0.21367254853248596,0.06294765323400497,0.8854985237121582,0.4241519570350647,0.2230546772480011,0.06451603770256042,0.8753167390823364,0.4362923204898834,0.2488136738538742,0.07164950668811798,0.8617465496063232,0.46752187609672546,0.19820183515548706,0.04718800261616707,0.8601818084716797,0.507066011428833,0.1314064860343933,0.009503670036792755,0.8517782092094421,0.5270468592643738,0.08509115129709244,-0.019491469487547874,0.8453409075737,0.5320841073989868,0.007729473523795605,-0.0665799081325531,0.8440343141555786,0.5227251052856445,-0.08407420665025711,-0.1226639524102211,0.8394305109977722,0.4984068274497986,-0.07934732735157013,-0.1635560393333435,0.847669780254364,0.4786711037158966,-0.06507816165685654,-0.15734529495239258,0.8613252639770508,0.4646359086036682,-0.05028495192527771,-0.12165815383195877,0.8756622076034546,0.45376476645469666,-0.023896483704447746,-0.09509914368391037,0.8857102990150452,0.45229649543762207,-0.012102203443646431,-0.08320023864507675,0.887895941734314,0.4723488688468933,-0.004229831043630838,-0.08161935210227966,0.8776143789291382,0.49356701970100403,0.004698972683399916,-0.08145520091056824,0.8658722043037415,0.5135558247566223,0.0290206465870142,-0.07396432012319565,0.8543696403503418,0.524937629699707,0.06175406277179718,-0.05622602626681328,0.8470334410667419,0.5215153694152832,0.09531629830598831,-0.030645163729786873,0.8473473191261292,0.5172802805900574,0.12095975130796432,-0.006628531962633133,0.8471989035606384,0.5153272151947021,0.13893111050128937,0.013005811721086502,0.8455571532249451,0.5122763514518738,0.1337576061487198,0.016586599871516228,0.848178505897522,0.502774715423584,0.15098936855793,0.03440973535180092,0.8504326939582825,0.4944876432418823,0.1629704087972641,0.043230731040239334,0.8526744842529297,0.49158358573913574,0.15790563821792603,0.03637201339006424,0.8556216955184937,0.487737774848938,0.1426226943731308,0.019580576568841934,0.8610384464263916,0.4778144955635071,0.1391638219356537,0.009207366034388542,0.867318868637085,0.46259182691574097,0.14231950044631958,0.008081287145614624,0.875036358833313,0.4360128343105316,0.11379817128181458,-0.0051138149574398994,0.8927018642425537,0.3985357880592346,0.10384666174650192,0.0037863366305828094,0.9112468361854553,0.36398589611053467,0.10394059121608734,0.013937966898083687,0.9254816770553589,0.3469804525375366,0.10448896139860153,0.016495920717716217,0.9318876266479492,0.3502008318901062,0.10476038604974747,0.012589084915816784,0.9307126998901367,0.33848056197166443,0.12149876356124878,0.025828015059232712,0.9327389597892761,0.3370666801929474,0.13317610323429108,0.03570546954870224,0.9313298463821411,0.35420510172843933,0.18253526091575623,0.052051663398742676,0.9157020449638367,0.36360234022140503,0.24474523961544037,0.07429654896259308,0.8957528471946716,0.37296435236930847,0.28027552366256714,0.08615878224372864,0.8802953362464905,0.41124141216278076,0.25288668274879456,0.07270500063896179,0.8727214932441711,0.45506158471107483,0.22985883057117462,0.05845513939857483,0.8582929968833923,0.49362918734550476,0.22077122330665588,0.05172671377658844,0.8395919799804688,0.516306459903717,0.21702562272548676,0.04961974918842316,0.8269615769386292,0.5219892859458923,0.21597322821617126,0.050307709723711014,0.8236212134361267,0.5133308172225952,0.22480781376361847,0.058377139270305634,0.8261628746986389,0.5019983053207397,0.23224160075187683,0.06541784107685089,0.8305311799049377,0.48958128690719604,0.23271314799785614,0.06766019761562347,0.8376018404960632,0.4767707586288452,0.2285233736038208,0.06676376610994339,0.8461733460426331,0.4629229009151459,0.22306060791015625,0.0649418756365776,0.8554115891456604,0.4491659700870514,0.21814779937267303,0.06327232718467712,0.8640937805175781,0.4354860484600067,0.21427294611930847,0.0619741752743721,0.8721228241920471,0.4218702018260956,0.2126859724521637,0.061789970844984055,0.8791884183883667,0.40776583552360535,0.21367256343364716,0.06294761598110199,0.885499119758606],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,1.0000001192092896,0.9999999403953552,0.9999999403953552,1.0000001192092896],"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000001958887651198893,22.74271011352539,-0.000005931609393883264,0.0000029277714475028915,22.74271011352539,-0.0000014023285075381864],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.4524131119251251,-0.04905669391155243,0.04749445244669914,0.889190673828125,-0.46276965737342834,-0.048721376806497574,0.04497905075550079,0.8839951157569885,-0.468750536441803,-0.046047233045101166,0.03819528594613075,0.8813022971153259,-0.4885430335998535,-0.034728653728961945,0.0250829067081213,0.8714874982833862,-0.5366249680519104,-0.028040997684001923,0.016234587877988815,0.8431985378265381,-0.5614665746688843,-0.022864557802677155,0.004291537683457136,0.8271723389625549,-0.5654977560043335,-0.026141861453652382,0.002365949796512723,0.824332058429718,-0.5593727827072144,-0.035459939390420914,0.010702681727707386,0.8280882835388184,-0.5537953972816467,-0.039760977029800415,0.008045493625104427,0.8316640257835388,-0.557641327381134,-0.035899609327316284,-0.004331393167376518,0.8292940258979797,-0.5573923587799072,-0.02487722970545292,-0.025007260963320732,0.8294996023178101,-0.553877055644989,-0.02063518762588501,-0.04211651161313057,0.8312764763832092,-0.5559660792350769,-0.022295158356428146,-0.04194069653749466,0.8298467397689819,-0.5706496238708496,-0.026602853089571,-0.02900209277868271,0.8202500939369202,-0.5841237306594849,-0.03057539463043213,-0.011952911503612995,0.811000406742096,-0.5932627320289612,-0.03376816585659981,0.0025576066691428423,0.8042963147163391,-0.5947413444519043,-0.035433802753686905,0.011920513585209846,0.8030473589897156,-0.5895165205001831,-0.035524871200323105,0.015261405147612095,0.8068304061889648,-0.5905081033706665,-0.03697383031249046,0.017740678042173386,0.8059890866279602,-0.5978039503097534,-0.03834475204348564,0.020512258633971214,0.800462007522583,-0.619246780872345,-0.04807007312774658,0.03606078401207924,0.7828935980796814,-0.630199134349823,-0.051762666553258896,0.04010309278964996,0.7736675143241882,-0.6219239830970764,-0.057643841952085495,0.04666327312588692,0.7795577049255371,-0.6050601005554199,-0.06637918204069138,0.05803648754954338,0.7912824153900146,-0.5889100432395935,-0.07742151618003845,0.07575120031833649,0.8009074330329895,-0.5780107378959656,-0.08412172645330429,0.08592689782381058,0.8071206212043762,-0.5780724883079529,-0.08444852381944656,0.08535496145486832,0.8071029782295227,-0.5923905968666077,-0.09027282893657684,0.0947277769446373,0.7949534058570862,-0.6083462834358215,-0.08487454801797867,0.0856345146894455,0.7844602465629578,-0.615332305431366,-0.07818147540092468,0.07420141994953156,0.7808636426925659,-0.6058875918388367,-0.07338254898786545,0.06662249565124512,0.7893519997596741,-0.6465392708778381,-0.06718362122774124,0.05153457075357437,0.7581672072410583,-0.66748046875,-0.04893093183636665,0.03671291470527649,0.7421103715896606,-0.6733818650245667,-0.03443246707320213,0.029018832370638847,0.737922191619873,-0.6632712483406067,-0.022114403545856476,0.008869784884154797,0.7479997277259827,-0.625298023223877,-0.011905022896826267,-0.015423886477947235,0.7801427841186523,-0.5859824419021606,-0.009048876352608204,-0.025999896228313446,0.8098559379577637,-0.5925716161727905,-0.014537554234266281,-0.019539333879947662,0.8051495552062988,-0.5924906730651855,-0.02463965304195881,-0.005140574183315039,0.805184006690979,-0.5837851166725159,-0.03174556419253349,0.006613307166844606,0.8112604022026062,-0.5688828825950623,-0.036999303847551346,0.016502320766448975,0.8214200735092163,-0.5507224202156067,-0.04332709312438965,0.028517263010144234,0.8330752849578857,-0.5356059670448303,-0.041611723601818085,0.02724672108888626,0.8430019617080688,-0.5211480855941772,-0.039802227169275284,0.026026954874396324,0.8521403074264526,-0.5084437131881714,-0.04043317213654518,0.02882515825331211,0.8596622943878174,-0.49675676226615906,-0.04288044571876526,0.034389689564704895,0.8661473989486694,-0.4862513244152069,-0.0452883243560791,0.03966307267546654,0.8717427849769592,-0.4769490957260132,-0.04718177393078804,0.04373432695865631,0.8765733242034912,-0.46837764978408813,-0.04867330193519592,0.04670336842536926,0.8809495568275452,-0.4605638384819031,-0.04924744367599487,0.047811977565288544,0.8849686980247498,-0.4524124264717102,-0.04905689135193825,0.047494761645793915,0.8891910314559937],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999998211860657,0.9999998211860657,0.9999998211860657,0.9999998211860657,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.0000010550772913120454,13.415114402770996,0.000004097688815818401,-2.5525787350488827e-7,13.415109634399414,9.854056770564057e-7],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.3069814443588257,-0.00566264521330595,-0.0018291984451934695,0.9516969323158264,-0.3069814443588257,-0.005662641022354364,-0.001829207525588572,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1,1.0000001192092896,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[-11.139494895935059,-4.020092964172363,0.42029789090156555,-11.139496803283691,-4.020102500915527,0.42029672861099243],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.706893265247345,-0.2815976142883301,0.46786463260650635,0.4495636224746704,0.7031827569007874,-0.281584769487381,0.4797726273536682,0.4427892863750458,0.7110507488250732,-0.2866947650909424,0.48185572028160095,0.4242970645427704,0.7250892519950867,-0.29658976197242737,0.48720261454582214,0.38589349389076233,0.7357468605041504,-0.2933819591999054,0.503734827041626,0.34475329518318176,0.7435830235481262,-0.29213017225265503,0.5091729164123535,0.3201361894607544,0.7462989687919617,-0.2923000454902649,0.5160142183303833,0.3022049367427826,0.7340795993804932,-0.2921345829963684,0.5449073910713196,0.28082114458084106,0.7874880433082581,-0.30908870697021484,0.47106683254241943,0.24984541535377502,0.8548504114151001,-0.30636000633239746,0.3597615659236908,0.21435008943080902,0.9060297608375549,-0.24955250322818756,0.28353893756866455,0.19089078903198242,0.8639914989471436,-0.19470804929733276,0.3364017903804779,0.32006457448005676,0.778418242931366,-0.15191949903964996,0.39176350831985474,0.46637633442878723,0.6843113303184509,-0.17074738442897797,0.37238529324531555,0.6032350659370422,0.6273206472396851,-0.20114019513130188,0.3727252185344696,0.6535192131996155,0.586701512336731,-0.2706693708896637,0.39855706691741943,0.6509007811546326,0.5879880785942078,-0.32616060972213745,0.4520706832408905,0.5861069560050964,0.5895338654518127,-0.30108749866485596,0.5105115175247192,0.548793375492096,0.5963411927223206,-0.2836501896381378,0.5043245553970337,0.5563960075378418,0.6337848901748657,-0.24994787573814392,0.4632861018180847,0.5667529702186584,0.6928530335426331,-0.18066608905792236,0.4082682728767395,0.5662434101104736,0.7744209170341492,-0.12017983198165894,0.3027621805667877,0.54236900806427,0.8254724144935608,-0.07515839487314224,0.23972752690315247,0.5054476261138916,0.8363884687423706,-0.04788520187139511,0.2480068951845169,0.4864709973335266,0.8426846265792847,-0.04577089473605156,0.2565034031867981,0.4711620807647705,0.8574345707893372,-0.060500726103782654,0.23501434922218323,0.45377737283706665,0.8652694821357727,-0.08137660473585129,0.21934758126735687,0.44336575269699097,0.8683505058288574,-0.09633079916238785,0.2132355123758316,0.43728524446487427,0.8472703099250793,-0.09791619330644608,0.20843324065208435,0.47864505648612976,0.8176848292350769,-0.09978631138801575,0.19398687779903412,0.5327319502830505,0.8066613674163818,-0.10687439888715744,0.17505182325839996,0.5542852878570557,0.8109596371650696,-0.11167392134666443,0.15557041764259338,0.5528755187988281,0.8063710331916809,-0.11323340982198715,0.17089934647083282,0.5547407865524292,0.8059206008911133,-0.13906553387641907,0.16132359206676483,0.5523834824562073,0.8181670308113098,-0.18181835114955902,0.14380957186222076,0.5261783003807068,0.8378053307533264,-0.22227393090724945,0.1286659985780716,0.4817900061607361,0.8482232689857483,-0.2601524293422699,0.18210765719413757,0.4238807260990143,0.840337872505188,-0.2929532825946808,0.24431845545768738,0.38512226939201355,0.8213282227516174,-0.32633668184280396,0.33783289790153503,0.32371777296066284,0.7892025709152222,-0.3337991237640381,0.4126811921596527,0.3089202344417572,0.7894800305366516,-0.32444509863853455,0.4234526455402374,0.30355319380760193,0.7941168546676636,-0.3117547333240509,0.4257220923900604,0.3015761077404022,0.7776656746864319,-0.3122963011264801,0.4182349145412445,0.35040923953056335,0.7612480521202087,-0.31413331627845764,0.41391828656196594,0.38793468475341797,0.7472469806671143,-0.315345823764801,0.41190028190612793,0.41535183787345886,0.7337311506271362,-0.3152502179145813,0.4120878577232361,0.4386793375015259,0.7201960682868958,-0.3139960467815399,0.4207842946052551,0.45350271463394165,0.7095845341682434,-0.3090876042842865,0.43501898646354675,0.4601230323314667,0.7024116516113281,-0.30132290720939636,0.4503832757472992,0.46149471402168274,0.7035919427871704,-0.2918103337287903,0.45986390113830566,0.45643216371536255,0.7068933248519897,-0.28159770369529724,0.4678645133972168,0.44956356287002563],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,1.000000238418579,1.000000238418579,1.0000001192092896,1.0000003576278687],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000001355268864244863,25.527101516723633,-0.0000011704943290169467,0.0000025574659048288595,25.52710723876953,9.408555570189492e-7],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.348246306180954,-0.09891270846128464,-0.005920770578086376,0.9321511387825012,0.3428623080253601,-0.10174769908189774,-0.005006594117730856,0.9338456988334656,0.3313550353050232,-0.08208130300045013,-0.016114216297864914,0.9397908449172974,0.3025062084197998,-0.04826885089278221,-0.03238585963845253,0.9513733386993408,0.262849897146225,-0.022420432418584824,-0.029946651309728622,0.9641112089157104,0.2601119577884674,-0.00026337351300753653,-0.01634945720434189,0.965440034866333,0.27359187602996826,0.014533181674778461,-0.005516841541975737,0.9617202877998352,0.2828464210033417,0.011851772665977478,-0.008310557343065739,0.95905601978302,0.32561829686164856,0.1051190122961998,0.030635932460427284,0.9391401410102844,0.3383248746395111,0.20592711865901947,0.06928499042987823,0.9156035780906677,0.3584786057472229,0.2254665642976761,0.09524434804916382,0.9008809328079224,0.5245801210403442,0.03906211256980896,0.05468044430017471,0.8487048149108887,0.6530225872993469,-0.08152098208665848,-0.03870847821235657,0.75194251537323,0.7008978128433228,-0.11054825782775879,-0.09722443670034409,0.6979030966758728,0.6875154376029968,-0.09073743969202042,-0.07221942394971848,0.7168498039245605,0.5905498266220093,-0.08700329810380936,-0.09136788547039032,0.7970779538154602,0.44681957364082336,-0.09073778241872787,-0.12243231385946274,0.8815493583679199,0.4539288580417633,-0.09643986076116562,-0.11436901241540909,0.8783892393112183,0.4802742302417755,-0.08749683946371078,-0.10482282936573029,0.8664255142211914,0.4794779121875763,-0.07022503018379211,-0.08948414772748947,0.8701505661010742,0.4327932596206665,-0.0836244449019432,-0.0727619007229805,0.8946523070335388,0.36537081003189087,-0.06442620605230331,-0.02923927642405033,0.9281694293022156,0.29963427782058716,-0.07323656976222992,0.005950446240603924,0.9512204527854919,0.26069045066833496,-0.11764013022184372,0.01589534431695938,0.9580963850021362,0.22876925766468048,-0.14760413765907288,-0.008275269530713558,0.962189793586731,0.19191423058509827,-0.14161238074302673,-0.03669196367263794,0.9704475998878479,0.1552809774875641,-0.1320808082818985,-0.0693923681974411,0.9765384197235107,0.11319711059331894,-0.13154102861881256,-0.09127023071050644,0.9805881977081299,0.13744184374809265,-0.1497480571269989,-0.10367143154144287,0.9736208319664001,0.1961175501346588,-0.14808498322963715,-0.10833845287561417,0.9632608890533447,0.2467367947101593,-0.11003191024065018,-0.1090942844748497,0.9566150903701782,0.29584038257598877,-0.07148944586515427,-0.10018554329872131,0.9472754001617432,0.3774159848690033,-0.06762335449457169,-0.07470708340406418,0.9205450415611267,0.4721914529800415,-0.029892096295952797,-0.01634393446147442,0.8808374404907227,0.5220556855201721,0.02205657958984375,0.03734424710273743,0.8518079519271851,0.5196996331214905,0.06761535257101059,0.08204304426908493,0.8477083444595337,0.4691406786441803,0.07556718587875366,0.0870683342218399,0.8755659461021423,0.39423200488090515,0.0656149685382843,0.07380437850952148,0.9136896133422852,0.27898144721984863,0.03652912750840187,0.015424789860844612,0.9594774842262268,0.2543429136276245,0.03392184153199196,-0.007746926974505186,0.9664880037307739,0.23113107681274414,0.02699369192123413,-0.00036198936868458986,0.9725480079650879,0.17941203713417053,0.02838856354355812,0.0004980614176020026,0.9833642244338989,0.25070205330848694,0.00817677192389965,0.013735531829297543,0.9679323434829712,0.3009016215801239,-0.015505176968872547,0.019914045929908752,0.9533211588859558,0.3354988396167755,-0.034211333841085434,0.020591329783201218,0.9411939978599548,0.36318695545196533,-0.05215190723538399,0.018008960410952568,0.9300812482833862,0.3783379793167114,-0.07015980035066605,0.009960295632481575,0.9229511618614197,0.38160187005996704,-0.08621500432491302,0.0007515655597671866,0.9202969074249268,0.3772171139717102,-0.09873059391975403,-0.00624352041631937,0.9208260178565979,0.3641504645347595,-0.10041598230600357,-0.007044950034469366,0.9258841276168823,0.3482460081577301,-0.09891287237405777,-0.005920592229813337,0.932151198387146],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,0.9999999403953552,0.9999998211860657,1,0.9999998211860657,0.9999999403953552],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000002871927563319332,23.109195709228516,-6.065718025638489e-7,0.000003533056997184758,23.109201431274414,-0.0000023242030238179723],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.5572507381439209,0.15959540009498596,-0.20703673362731934,0.788122296333313,-0.553617537021637,0.1612894982099533,-0.21326212584972382,0.7886778116226196,-0.5485606789588928,0.1639280766248703,-0.2177942544221878,0.7904267311096191,-0.5371207594871521,0.16961702704429626,-0.22423309087753296,0.7952678203582764,-0.5178089737892151,0.1764107644557953,-0.233154758810997,0.803985059261322,-0.5162700414657593,0.1692388504743576,-0.21436013281345367,0.8117100596427917,-0.5238315463066101,0.15112900733947754,-0.1754816174507141,0.8197358250617981,-0.5232238173484802,0.12307074666023254,-0.12131483852863312,0.8344897627830505,-0.47406473755836487,0.09920921921730042,-0.05567064881324768,0.8731099367141724,-0.3282271921634674,0.05136051028966904,0.010234728455543518,0.9431459307670593,-0.3948962092399597,0.09563182294368744,-0.08126877248287201,0.9101136922836304,-0.5868803262710571,0.09349377453327179,-0.08296061307191849,0.7999674677848816,-0.6606878042221069,0.03426104038953781,0.016698531806468964,0.7496926188468933,-0.6374971866607666,0.011328439228236675,0.06188742443919182,0.7678795456886292,-0.6444305777549744,0.006017102859914303,0.07110516726970673,0.7613258957862854,-0.5492671132087708,0.03014373406767845,0.026431173086166382,0.8346846103668213,-0.4729693531990051,0.08755858987569809,-0.04001650959253311,0.8758037686347961,-0.5718486905097961,0.10294661670923233,-0.07136232405900955,0.8107395172119141,-0.6010680198669434,0.099615179002285,-0.06829717010259628,0.7900187373161316,-0.6077594757080078,0.09150441735982895,-0.057663124054670334,0.7867213487625122,-0.5855779051780701,0.07185225188732147,-0.03314252197742462,0.8067449331283569,-0.5511397123336792,0.06758635491132736,-0.03527035936713219,0.8309230804443359,-0.5149125456809998,0.06498385965824127,-0.03905852511525154,0.8538832664489746,-0.4924304187297821,0.05904301628470421,-0.03475147485733032,0.8676511645317078,-0.4726896584033966,0.04939405620098114,-0.018587233498692513,0.8796471953392029,-0.450588196516037,0.04098498448729515,-0.006129838526248932,0.8917695879936218,-0.4286007881164551,0.033569466322660446,0.006885922513902187,0.9028438925743103,-0.4033021926879883,0.029964562505483627,0.014262648299336433,0.9144648909568787,-0.3459714353084564,0.017731081694364548,0.047781601548194885,0.9368597865104675,-0.29581859707832336,0.005500229075551033,0.07402102649211884,0.9523560404777527,-0.29254889488220215,-0.002578896936029196,0.09203069657087326,0.9518082141876221,-0.33361494541168213,0.001433761091902852,0.09571877866983414,0.9378363490104675,-0.41137397289276123,0.017197705805301666,0.0756327360868454,0.9081604480743408,-0.5023289918899536,0.055998049676418304,0.0032415406312793493,0.8628553748130798,-0.5400574207305908,0.1052723228931427,-0.08639004826545715,0.8305374979972839,-0.5265421271324158,0.14277653396129608,-0.15527381002902985,0.8235644102096558,-0.49272090196609497,0.1350158303976059,-0.13285428285598755,0.8493212461471558,-0.44366875290870667,0.12731318175792694,-0.10779089480638504,0.8805285692214966,-0.3793765902519226,0.1431671530008316,-0.13505393266677856,0.9040669202804565,-0.40046051144599915,0.14776359498500824,-0.1108330637216568,0.8975039124488831,-0.45298710465431213,0.17985230684280396,-0.17366576194763184,0.8557429909706116,-0.4614815413951874,0.1987057328224182,-0.26534608006477356,0.82288658618927,-0.5084545016288757,0.19178254902362823,-0.26647865772247314,0.7960418462753296,-0.5305593013763428,0.18878433108329773,-0.2619163393974304,0.7837519645690918,-0.5463096499443054,0.18552523851394653,-0.2542959749698639,0.7761828303337097,-0.5594429969787598,0.18196791410446167,-0.24432514607906342,0.7708544135093689,-0.5673881769180298,0.1763233095407486,-0.2322922646999359,0.7700785994529724,-0.5702413320541382,0.17036186158657074,-0.22070655226707458,0.772716224193573,-0.5693888068199158,0.16492384672164917,-0.21154624223709106,0.7770745158195496,-0.5640853047370911,0.162114217877388,-0.20832684636116028,0.7823852896690369,-0.5572505593299866,0.15959544479846954,-0.2070367932319641,0.7881224155426025],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999998807907104,1.0000001192092896,1.0000001192092896,0.9999998807907104,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.0000034142626645916607,13.547398567199707,6.825279683653207e-7,-0.000003735066229637596,13.54739761352539,-6.556514904332289e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.30788561701774597,0.014501913450658321,0.004691826179623604,0.9513012766838074,-0.3078855574131012,0.014501913450658321,0.004691829904913902,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,1.0000001192092896,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,1.7000000476837158],"values":[2.834000825881958,7.754306316375732,-6.139601707458496,2.8340001106262207,7.754297256469727,-6.13960075378418],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.3320007026195526,-0.09571807086467743,-0.11373621225357056,0.9314922094345093,-0.3320007026195526,-0.09571804851293564,-0.11373622715473175,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1,0.9999999403953552,0.9999998807907104,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,1.7000000476837158],"values":[1.1082781981031076e-7,10.288592338562012,5.064072752247739e-7,3.492462212761893e-7,10.288599967956543,-4.472674106636987e-7],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-1.654552761465311e-8,-8.44009306888438e-10,1.0346411727368832e-8,1,-1.654552761465311e-8,2.8230742188384284e-9,9.880750440061092e-9,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999999403953552,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,1.7000000476837158],"values":[-9.227898800645562e-8,10.28859806060791,1.169440366766139e-7,-1.6826524174007318e-8,10.288605690002441,-0.0000012683876775554381],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.0436820425093174,0.029420556500554085,-0.019534049555659294,0.9984211325645447,0.04581921175122261,0.021200548857450485,-0.01311154942959547,0.9986386895179749,0.04619516059756279,0.013804350979626179,-0.002943543018773198,0.9988327026367188,0.04635752737522125,0.014430359937250614,0.001442129840143025,0.9988196492195129,0.047730453312397,0.01828496903181076,0.003410700475797057,0.9986870884895325,0.04976551979780197,0.023310448974370956,0.005675774998962879,0.9984726905822754,0.05256510153412819,0.028291406109929085,0.007822534069418907,0.9981859922409058,0.05852857232093811,0.031063038855791092,0.008724958635866642,0.9977642297744751,0.07895440608263016,0.022882355377078056,0.004756774753332138,0.9966042041778564,0.10509958118200302,0.010024037212133408,-0.008960711769759655,0.9943708181381226,0.11939791589975357,-0.008710386231541634,-0.01588471792638302,0.9926812052726746,0.11962317675352097,-0.031093521043658257,-0.005830751731991768,0.9923152327537537,0.11440384387969971,-0.03023384138941765,0.005226621404290199,0.9929603934288025,0.11176200956106186,0.001553896814584732,0.017210055142641068,0.9935847520828247,0.10777458548545837,0.03671620413661003,0.02104506455361843,0.9932742118835449,0.10012240707874298,0.06516268104314804,0.015907946974039078,0.99271160364151,0.09289278090000153,0.09982389956712723,0.0064362213015556335,0.9906385540962219,0.08585499972105026,0.1510189026594162,-0.013328415341675282,0.9847053289413452,0.08968541026115417,0.19209879636764526,-0.035011157393455505,0.9766415953636169,0.09513932466506958,0.19249452650547028,-0.04057835415005684,0.9758318662643433,0.10237598419189453,0.1850634664297104,-0.041325926780700684,0.97650545835495,0.11009468883275986,0.17546942830085754,-0.04001794755458832,0.9774907827377319,0.11822257936000824,0.15766610205173492,-0.03520134463906288,0.9797579646110535,0.11890432238578796,0.13372547924518585,-0.03306465968489647,0.9833036661148071,0.11668980121612549,0.10887300968170166,-0.03255997225642204,0.9866458773612976,0.11384691298007965,0.08394773304462433,-0.03213191777467728,0.9894236922264099,0.10767888277769089,0.05875261127948761,-0.03268605098128319,0.9919098019599915,0.09987945109605789,0.034375790506601334,-0.03327753767371178,0.9938485622406006,0.09254401177167892,0.01275140605866909,-0.03131561353802681,0.9951343536376953,0.08539532124996185,-0.007721180561929941,-0.02845260314643383,0.9959108829498291,0.08012522011995316,-0.024959417060017586,-0.02283419854938984,0.9962106347084045,0.08366135507822037,-0.02888042852282524,-0.012161404825747013,0.9960014224052429,0.09277508407831192,-0.02082681655883789,-0.0035428558476269245,0.995462954044342,0.09645124524831772,-0.01274975948035717,0.0007651714258827269,0.9952557682991028,0.09438960254192352,-0.008196334354579449,0.003244930412620306,0.9954962730407715,0.09160070866346359,-0.004178721457719803,0.004167591687291861,0.9957783222198486,0.08865471184253693,-0.0003860106226056814,0.0036524254828691483,0.9960556626319885,0.08571775257587433,0.0026187882758677006,0.0018317130161449313,0.9963143467903137,0.08358155936002731,0.00262679741717875,-0.002999883843585849,0.9964929819107056,0.08184529840946198,0.0009368388564325869,-0.00803848635405302,0.9966121912002563,0.07928358763456345,0.0006534955464303493,-0.01211174950003624,0.9967783093452454,0.0751294419169426,0.004187627229839563,-0.01734744943678379,0.9970141053199768,0.07166755944490433,0.007907700724899769,-0.018631847575306892,0.9972231388092041,0.06840048730373383,0.011714059859514236,-0.019397560507059097,0.9974005818367004,0.06523123383522034,0.015524771995842457,-0.019995147362351418,0.9975490570068359,0.06201684847474098,0.01912764273583889,-0.020585961639881134,0.9976794719696045,0.05880027264356613,0.022383414208889008,-0.020823316648602486,0.9978015422821045,0.05529513582587242,0.02460257150232792,-0.020763985812664032,0.9979509115219116,0.051573656499385834,0.0262790285050869,-0.02051982469856739,0.9981124997138977,0.04765380173921585,0.027856143191456795,-0.02005276270210743,0.9982740879058838,0.0436820462346077,0.029420524835586548,-0.01953401230275631,0.9984211325645447],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999997615814209,0.9999997615814209,0.9999999403953552,0.9999998807907104,0.9999997615814209],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,1.7000000476837158],"values":[4.366399765014648,7.562901496887207,-0.17679868638515472,4.366399765014648,7.56290340423584,-0.1767987459897995],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.5298227071762085,0.5134139060974121,-0.4447128474712372,0.5078627467155457,0.534051239490509,0.5084609985351562,-0.45456114411354065,0.49963074922561646,0.5362854599952698,0.5043455958366394,-0.4646063446998596,0.49211207032203674,0.5351476669311523,0.5039048194885254,-0.47473958134651184,0.48406535387039185,0.5279479622840881,0.5099490284919739,-0.484237402677536,0.4761691689491272,0.5212697386741638,0.5148343443870544,-0.4986671805381775,0.46320024132728577,0.5140466690063477,0.5200307369232178,-0.501984715461731,0.46188268065452576,0.5254513621330261,0.5099513530731201,-0.4826865792274475,0.4804832637310028,0.5460657477378845,0.4944593906402588,-0.434013307094574,0.5186082720756531,0.5685389637947083,0.47419118881225586,-0.386029452085495,0.550352156162262,0.5971379280090332,0.44169846177101135,-0.34279027581214905,0.5751726627349854,0.6241691708564758,0.4017977714538574,-0.3002529740333557,0.5990155339241028,0.6385180950164795,0.38110053539276123,-0.2706056833267212,0.6114161014556885,0.6395227909088135,0.3879859447479248,-0.26265665888786316,0.6094990372657776,0.632735550403595,0.4081948697566986,-0.27712514996528625,0.5968453884124756,0.6181347370147705,0.44262129068374634,-0.3027687072753906,0.5747408866882324,0.612506628036499,0.4561419188976288,-0.3057100772857666,0.5686049461364746,0.6065524816513062,0.4693564474582672,-0.31560006737709045,0.5587444305419922,0.6034037470817566,0.4836532771587372,-0.3407147526741028,0.5346933603286743,0.5990428924560547,0.4967442750930786,-0.4087221920490265,0.4768006205558777,0.5879791975021362,0.5067858695983887,-0.49973005056381226,0.38434144854545593,0.5732464790344238,0.5123972296714783,-0.5613647699356079,0.3061162233352661,0.5663658976554871,0.5213313698768616,-0.5643051862716675,0.29833367466926575,0.5667058229446411,0.5215414762496948,-0.5549213886260986,0.3144857585430145,0.5693621635437012,0.5181694626808167,-0.5430188775062561,0.33534714579582214,0.5716963410377502,0.5144014954566956,-0.5278935432434082,0.3603925406932831,0.5703356266021729,0.5121278166770935,-0.5095928311347961,0.39084210991859436,0.560563325881958,0.5159439444541931,-0.4840724766254425,0.43040040135383606,0.5498334169387817,0.518187403678894,-0.4591072201728821,0.4673173129558563,0.5460173487663269,0.5120571255683899,-0.4519437253475189,0.48519009351730347,0.5392882823944092,0.5096511244773865,-0.4535611867904663,0.49366605281829834,0.5054263472557068,0.5349950194358826,-0.4326745867729187,0.5206892490386963,0.45929205417633057,0.567241907119751,-0.3892197012901306,0.5619568824768066,0.4415350556373596,0.5778186321258545,-0.3722720146179199,0.5767027139663696,0.4359217882156372,0.5809141397476196,-0.3683314919471741,0.5803816914558411,0.43595126271247864,0.5814582109451294,-0.373937726020813,0.5762147903442383,0.43901392817497253,0.5795858502388,-0.3773419260978699,0.5735504627227783,0.45488792657852173,0.5696709156036377,-0.39125776290893555,0.56166672706604,0.4792117178440094,0.5516435503959656,-0.39747563004493713,0.5550304651260376,0.5012088418006897,0.5350027680397034,-0.3986848294734955,0.551010251045227,0.5077697038650513,0.5329919457435608,-0.4214962124824524,0.529556930065155,0.5149974822998047,0.5275538563728333,-0.4413878917694092,0.5115089416503906,0.516454815864563,0.5281496644020081,-0.4553018808364868,0.4970238208770752,0.5148429870605469,0.530607283115387,-0.45466485619544983,0.49666154384613037,0.5149310231208801,0.5308013558387756,-0.45258665084838867,0.4982583522796631,0.5166337490081787,0.5289242267608643,-0.4503802955150604,0.5004861950874329,0.5211777091026306,0.5243239998817444,-0.44881677627563477,0.5020177364349365,0.5262773633003235,0.5188645720481873,-0.44817906618118286,0.5029386878013611,0.5282174348831177,0.5162435173988342,-0.4473051428794861,0.5043779015541077,0.5289570689201355,0.5148676037788391,-0.44605425000190735,0.5061139464378357,0.5298224091529846,0.5134141445159912,-0.44471275806427,0.5078628659248352],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999997615814209,0.9999998807907104,0.9999998211860657,0.9999998807907104,0.9999997615814209,0.9999999403953552],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000003174408675477025,17.630796432495117,-0.000011906789040949661,0.000003702555659401696,17.63079833984375,-0.000012033097846142482],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.38447335362434387,0.15783919394016266,0.22161303460597992,0.8821308016777039,0.38487508893013,0.11716759204864502,0.1934039145708084,0.8948395848274231,0.38872087001800537,0.07660845667123795,0.16856417059898376,0.9025593400001526,0.3901370167732239,0.036783382296562195,0.14898893237113953,0.9078779816627502,0.3865906894207001,0.0014315890148282051,0.13684670627117157,0.9120408892631531,0.36465930938720703,-0.025446107611060143,0.13391239941120148,0.9211099743843079,0.3371424973011017,-0.04245779290795326,0.14641113579273224,0.9290295839309692,0.3036918640136719,-0.03694961220026016,0.15926064550876617,0.9386383891105652,0.27174484729766846,-0.006811272352933884,0.17538651823997498,0.9462282657623291,0.22971998155117035,0.025962956249713898,0.19802699983119965,0.9525439739227295,0.1798640638589859,0.04240579158067703,0.20087279379367828,0.9620295166969299,0.1389259248971939,0.035586751997470856,0.1965240240097046,0.9699543714523315,0.1740657091140747,-0.02287687174975872,0.21488095819950104,0.9607309699058533,0.2883547842502594,-0.0971570685505867,0.21302030980587006,0.9284580945968628,0.4086785912513733,-0.10646915435791016,0.19793887436389923,0.8845712542533875,0.4686164855957031,0.013576243072748184,0.21788038313388824,0.8560037612915039,0.5048332810401917,0.20200416445732117,0.18305906653404236,0.8190403580665588,0.49635276198387146,0.32889503240585327,0.21931684017181396,0.7728920578956604,0.45043495297431946,0.44602033495903015,0.2388623058795929,0.735607922077179,0.3992610573768616,0.5389226675033569,0.2167290896177292,0.7093528509140015,0.3875836730003357,0.5709420442581177,0.1528763473033905,0.707412838935852,0.45250779390335083,0.4841359555721283,0.0838356614112854,0.7441912889480591,0.4709356725215912,0.3894471526145935,0.048117607831954956,0.7900855541229248,0.4516699016094208,0.31004175543785095,0.009293459355831146,0.8365297913551331,0.4062725007534027,0.20852364599704742,-0.0011097609531134367,0.8896399736404419,0.3632318079471588,0.08707624673843384,0.02522025629878044,0.9272779226303101,0.34093964099884033,-0.017747700214385986,0.040332674980163574,0.9390519261360168,0.3220497965812683,-0.08463030308485031,0.04427488520741463,0.9418924450874329,0.29213613271713257,-0.09572912007570267,0.03491605445742607,0.9509328603744507,0.2429182231426239,-0.10384929925203323,0.019997531548142433,0.9642645716667175,0.18824224174022675,-0.10736740380525589,0.017049184069037437,0.9760873317718506,0.17483137547969818,-0.09825589507818222,0.04450692608952522,0.9786720275878906,0.18924199044704437,-0.0877913236618042,0.05165558308362961,0.9766328930854797,0.19373269379138947,-0.11474486440420151,0.04269988462328911,0.9733847975730896,0.20342355966567993,-0.15151813626289368,0.055444713681936264,0.9657054543495178,0.22498321533203125,-0.17852309346199036,0.1023600772023201,0.9523835778236389,0.23723919689655304,-0.18219849467277527,0.13607649505138397,0.9444599151611328,0.22251494228839874,-0.1684340238571167,0.1800777018070221,0.9432333707809448,0.23183046281337738,-0.18802785873413086,0.2330668717622757,0.9255160689353943,0.26930180191993713,-0.18097932636737823,0.2879177927970886,0.9010140895843506,0.3151748478412628,-0.1328110545873642,0.3316534161567688,0.8792224526405334,0.34457680583000183,-0.09025689959526062,0.32068052887916565,0.8776585459709167,0.3546120822429657,-0.04246613010764122,0.3045633137226105,0.8829994797706604,0.3569272756576538,0.004432540852576494,0.2759966254234314,0.8924175500869751,0.3676411807537079,0.04807252809405327,0.2546623945236206,0.8931270837783813,0.3785586655139923,0.0831693783402443,0.24106372892856598,0.8897553086280823,0.38621142506599426,0.11083272844552994,0.22863149642944336,0.8867268562316895,0.3881085515022278,0.1279856115579605,0.2177894115447998,0.886317789554596,0.3874785304069519,0.13910990953445435,0.21709993481636047,0.8850855827331543,0.38587209582328796,0.14829279482364655,0.2196846455335617,0.8836575150489807,0.3844733238220215,0.15783922374248505,0.22161298990249634,0.8821308016777039],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,0.9999998211860657,1.0000001192092896,1,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[4.21992552901429e-7,17.82779884338379,-1.4927697122857353e-7,0.000001559614702273393,17.827804565429688,-0.000002689106167963473],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.05372462794184685,-0.2845951020717621,0.7204499840736389,0.6301357746124268,0.08034376055002213,-0.2846415042877197,0.6964906454086304,0.6537774205207825,0.1300605833530426,-0.3070954382419586,0.6700318455696106,0.6631998419761658,0.17712484300136566,-0.3343038558959961,0.6439377665519714,0.6649901866912842,0.20772907137870789,-0.3714652955532074,0.6165468096733093,0.662368655204773,0.21571072936058044,-0.4196358323097229,0.5859130620956421,0.6588479280471802,0.20524448156356812,-0.47803881764411926,0.5361462831497192,0.6647562384605408,0.18290019035339355,-0.5323888063430786,0.4690943658351898,0.6804852485656738,0.15578152239322662,-0.569126307964325,0.4049485921859741,0.6984583139419556,0.13653382658958435,-0.5889129638671875,0.3600870966911316,0.7105472683906555,0.12984679639339447,-0.5889893770217896,0.3301900327205658,0.7260894775390625,0.1344892978668213,-0.5679715871810913,0.2749878168106079,0.7640043497085571,0.1161046177148819,-0.5228250622749329,0.250189870595932,0.8065845966339111,0.0927644670009613,-0.47358348965644836,0.2527562379837036,0.8385867476463318,0.09358938783407211,-0.4375936985015869,0.2704690396785736,0.8524079918861389,0.10075928270816803,-0.42445194721221924,0.2570975422859192,0.8623160719871521,0.04018557816743851,-0.4071631133556366,0.361591637134552,0.8377677798271179,-0.03752432391047478,-0.4053416848182678,0.42318838834762573,0.8094452619552612,-0.09739716351032257,-0.34222647547721863,0.563152015209198,0.7458248138427734,-0.1732277125120163,-0.29540523886680603,0.6385541558265686,0.689185380935669,-0.21816527843475342,-0.2844982445240021,0.6733775734901428,0.6465503573417664,-0.16439461708068848,-0.31416040658950806,0.6718042492866516,0.6503512859344482,-0.09982476383447647,-0.3328585624694824,0.6037049889564514,0.7174820899963379,-0.0887388363480568,-0.2846953272819519,0.5353460907936096,0.7902395725250244,-0.0777926966547966,-0.20964466035366058,0.4120880663394928,0.883278489112854,-0.04461018368601799,-0.13025251030921936,0.24586261808872223,0.9594768285751343,0.0023629702627658844,-0.08031504601240158,0.10215986520051956,0.9915176630020142,0.029995493590831757,-0.11106237024068832,0.021059369668364525,0.9931374192237854,0.009861276485025883,-0.16819997131824493,0.0294734425842762,0.9852628111839294,-0.01089543104171753,-0.2469286322593689,0.06279182434082031,0.9669357538223267,-0.01389331929385662,-0.3341768682003021,0.10968489944934845,0.9360032081604004,0.0024288573767989874,-0.3944225013256073,0.16819046437740326,0.9034029841423035,0.04003814980387688,-0.42801231145858765,0.2492688000202179,0.8677945733070374,0.10047901421785355,-0.4451654851436615,0.3545972406864166,0.8160836100578308,0.162966787815094,-0.44438663125038147,0.4427705407142639,0.7615225315093994,0.19236446917057037,-0.434452623128891,0.4857046604156494,0.7337151169776917,0.20040462911128998,-0.41837403178215027,0.5138047337532043,0.7216688394546509,0.20231404900550842,-0.41180887818336487,0.525534987449646,0.7164463996887207,0.19871720671653748,-0.40168917179107666,0.5274553298950195,0.7217674255371094,0.1797434240579605,-0.38678842782974243,0.5196467041969299,0.7403069138526917,0.1528349667787552,-0.3654024302959442,0.5289592146873474,0.7505496144294739,0.146613210439682,-0.34743356704711914,0.5600861310958862,0.7376300692558289,0.15224973857402802,-0.339717298746109,0.5877839922904968,0.7182772159576416,0.15489983558654785,-0.33225488662719727,0.6229274272918701,0.6910674571990967,0.13132308423519135,-0.3178148567676544,0.6544392108917236,0.6733924150466919,0.09732342511415482,-0.30203551054000854,0.6793802976608276,0.6616231203079224,0.06824033707380295,-0.28984853625297546,0.699032723903656,0.6501418352127075,0.05779376998543739,-0.2872496247291565,0.7127847671508789,0.6372482776641846,0.055288512259721756,-0.28641143441200256,0.7171130776405334,0.6329775452613831,0.054698362946510315,-0.2854919731616974,0.7184921503067017,0.6318793296813965,0.05372470244765282,-0.2845953106880188,0.7204499244689941,0.6301357746124268],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999997615814209,0.9999999403953552,0.9999997019767761,0.9999998807907104,1,0.9999998211860657],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000002641373384904,18.751102447509766,0.0000017390580069331918,0.000013946715625934303,18.751100540161133,0.000004474665729503613],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.055037714540958405,0.05196232721209526,-0.013945184648036957,0.9970337748527527,0.0550377257168293,0.05196233093738556,-0.013945169746875763,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.0000001192092896,0.9999998807907104,0.9999999403953552,1.0000001192092896,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,1.7000000476837158],"values":[-4.5131988525390625,8.246386528015137,-0.08130056411027908,-4.513198375701904,8.246395111083984,-0.08129743486642838],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.43135538697242737,-0.5555515289306641,0.6031647324562073,0.3761480748653412,0.4600326716899872,-0.5387779474258423,0.5921521186828613,0.38398462533950806,0.4929754137992859,-0.516934871673584,0.5791006684303284,0.39293262362480164,0.5309114456176758,-0.489018052816391,0.5644128322601318,0.4005403518676758,0.5700528621673584,-0.45694348216056824,0.5507808327674866,0.4035874605178833,0.6048600077629089,-0.4244005084037781,0.5373197197914124,0.40659093856811523,0.626990795135498,-0.40098869800567627,0.5262530446052551,0.411276638507843,0.6370351910591125,-0.389853298664093,0.5171016454696655,0.41809865832328796,0.6394208073616028,-0.3924582004547119,0.5105950832366943,0.42001238465309143,0.6379837393760681,-0.40347737073898315,0.49781322479248047,0.4270419776439667,0.6325011253356934,-0.416525661945343,0.4816109836101532,0.4410211145877838,0.6215456128120422,-0.4312787652015686,0.47308605909347534,0.4515187740325928,0.6296162605285645,-0.42589694261550903,0.43995267152786255,0.4781598448753357,0.6361145973205566,-0.4203132092952728,0.4043254852294922,0.5051890015602112,0.6508477926254272,-0.40292680263519287,0.36939769983291626,0.5268704891204834,0.6615778207778931,-0.39017102122306824,0.33449992537498474,0.5460689663887024,0.6817899346351624,-0.36234456300735474,0.3117782473564148,0.5537718534469604,0.7140308022499084,-0.32318174839019775,0.3032916486263275,0.5419666171073914,0.7276191115379333,-0.3146137297153473,0.2992611229419708,0.5310662984848022,0.7152486443519592,-0.34192323684692383,0.3008299767971039,0.5301030278205872,0.6834824681282043,-0.37823396921157837,0.30080267786979675,0.547091007232666,0.6493543386459351,-0.4129765033721924,0.3255476951599121,0.5493706464767456,0.6169098615646362,-0.44257935881614685,0.35979345440864563,0.5423048734664917,0.6231002807617188,-0.4327850937843323,0.3508681654930115,0.54893958568573,0.6171192526817322,-0.4460521936416626,0.3208993971347809,0.5632272958755493,0.5571163296699524,-0.5098491311073303,0.29459720849990845,0.5855662822723389,0.49521806836128235,-0.5613517165184021,0.28711995482444763,0.5976667404174805,0.4626753032207489,-0.5841208100318909,0.29304447770118713,0.5990487933158875,0.4371128976345062,-0.6006944179534912,0.30252933502197266,0.5971386432647705,0.40759408473968506,-0.6190969944000244,0.312070369720459,0.5943048000335693,0.37789836525917053,-0.6365877985954285,0.32111045718193054,0.5906241536140442,0.35157132148742676,-0.6514971256256104,0.32739976048469543,0.5871614217758179,0.32603174448013306,-0.6634514927864075,0.3297843337059021,0.5871777534484863,0.2999383509159088,-0.6733145713806152,0.3311411440372467,0.5890925526618958,0.27806562185287476,-0.6793670058250427,0.33311381936073303,0.5917560458183289,0.2700138986110687,-0.6749331951141357,0.33692798018455505,0.5983620882034302,0.26546356081962585,-0.668505072593689,0.3423367142677307,0.6045127511024475,0.2637116014957428,-0.6610649228096008,0.34937918186187744,0.609412431716919,0.2709428369998932,-0.6503264904022217,0.3630430996417999,0.6098074913024902,0.30415454506874084,-0.6264166235923767,0.39728137850761414,0.5977120995521545,0.3446205258369446,-0.5981108546257019,0.43417981266975403,0.5787814259529114,0.3743010461330414,-0.5734359622001648,0.467303067445755,0.5591939091682434,0.38650408387184143,-0.5624868273735046,0.4907282292842865,0.5416723489761353,0.39385759830474854,-0.5559503436088562,0.510855495929718,0.5242347121238708,0.39874765276908875,-0.5519276857376099,0.5289244651794434,0.5065719485282898,0.40360158681869507,-0.5486900806427002,0.5458520650863647,0.4879452586174011,0.4067729413509369,-0.5484861731529236,0.560295581817627,0.46879372000694275,0.4116974174976349,-0.5499481558799744,0.5725752115249634,0.44745954871177673,0.4179149568080902,-0.5519112348556519,0.5835191011428833,0.42455461621284485,0.4245910942554474,-0.5537574291229248,0.5937597751617432,0.40065503120422363,0.4313555359840393,-0.5555514693260193,0.603164792060852,0.376147985458374],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999998807907104,0.9999999403953552,1,0.9999998807907104,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.0000013133010270394152,17.92139434814453,0.000007771351192786824,-2.86967576812458e-7,17.921396255493164,-0.000001652859623391123],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.43107542395591736,-0.19461344182491302,-0.10335654020309448,0.8749954700469971,0.42205917835235596,-0.20970244705677032,-0.08659962564706802,0.8777194619178772,0.4038050174713135,-0.20589210093021393,-0.08766240626573563,0.8870542645454407,0.38957875967025757,-0.1961061656475067,-0.0949864387512207,0.8948454260826111,0.3753999173641205,-0.17710362374782562,-0.1095571517944336,0.9031646847724915,0.35149213671684265,-0.15587767958641052,-0.14231795072555542,0.9120861291885376,0.33088263869285583,-0.1342657059431076,-0.18773214519023895,0.9150115251541138,0.31868278980255127,-0.11008204519748688,-0.23866502940654755,0.9106932878494263,0.31866970658302307,-0.06967867910861969,-0.29653218388557434,0.8975874185562134,0.3221886456012726,-0.01971854455769062,-0.37304437160491943,0.869852602481842,0.29012152552604675,0.017836546525359154,-0.4640938341617584,0.8367366790771484,0.24376076459884644,0.05713457986712456,-0.5700661540031433,0.7825222015380859,0.15938259661197662,0.09745340049266815,-0.6235889196395874,0.7591027021408081,0.09467779099941254,0.12778130173683167,-0.6548550128936768,0.7388321757316589,0.0556364543735981,0.13089407980442047,-0.675011157989502,0.7239691019058228,0.029609130695462227,0.1124725267291069,-0.6947287917137146,0.7098064422607422,0.019391141831874847,0.04640259966254234,-0.6883127689361572,0.7236687541007996,-0.029502280056476593,-0.04696813225746155,-0.6313297152519226,0.7735285758972168,-0.09912422299385071,-0.1054903045296669,-0.5710320472717285,0.8080647587776184,-0.07996035367250443,-0.1482468992471695,-0.5919094085693359,0.7882083654403687,-0.0013013913994655013,-0.14746685326099396,-0.663952112197876,0.7330889701843262,0.049560610204935074,-0.13214808702468872,-0.7299715876579285,0.6687466502189636,0.10432212799787521,-0.11269691586494446,-0.7509870529174805,0.642210841178894,0.20265533030033112,-0.018981346860527992,-0.657025933265686,0.7258701920509338,0.19154629111289978,0.12949244678020477,-0.4685577154159546,0.8526402711868286,0.09277807176113129,0.1435505449771881,-0.24363800883293152,0.9546863436698914,0.07866733521223068,0.13226699829101562,-0.10384573042392731,0.9826154112815857,0.08411147445440292,0.12100951373577118,0.01140872947871685,0.9890155792236328,0.09206151217222214,0.11739563941955566,0.08609319478273392,0.9850537776947021,0.09423607587814331,0.12036649882793427,0.11493045836687088,0.981540858745575,0.09490520507097244,0.1252366453409195,0.13195794820785522,0.978721559047699,0.10170984268188477,0.13603396713733673,0.1307399570941925,0.9767584204673767,0.12352467328310013,0.1505926251411438,0.1210612803697586,0.9733487367630005,0.15254564583301544,0.16127820312976837,0.11333785951137543,0.9684388041496277,0.19181028008460999,0.16101354360580444,0.11408506333827972,0.9613886475563049,0.2533800005912781,0.13516473770141602,0.1329447627067566,0.9486067295074463,0.2924415171146393,0.11155220866203308,0.12377236783504486,0.941655158996582,0.31146785616874695,0.09355434775352478,0.08692023158073425,0.9416369795799255,0.3316556513309479,0.060132041573524475,0.06031404063105583,0.9395482540130615,0.3527764081954956,-0.01531579066067934,0.040088310837745667,0.9347230195999146,0.3709234893321991,-0.08007720112800598,0.014979692175984383,0.9250832796096802,0.38910579681396484,-0.1247277706861496,-0.01504579558968544,0.9125860333442688,0.4077197313308716,-0.15294677019119263,-0.03213144838809967,0.89963299036026,0.4256945550441742,-0.17536544799804688,-0.04624171555042267,0.8865059614181519,0.44290387630462646,-0.19418299198150635,-0.059175051748752594,0.8732854127883911,0.45688462257385254,-0.20979270339012146,-0.07211887091398239,0.8614187836647034,0.46649500727653503,-0.2190888375043869,-0.08218257129192352,0.853011429309845,0.46379005908966064,-0.21845488250255585,-0.08861328661441803,0.8540046811103821,0.4527713358402252,-0.21104638278484344,-0.09328880906105042,0.8612518906593323,0.4409559667110443,-0.20232261717319489,-0.09810519218444824,0.8689066767692566,0.4310756325721741,-0.1946135014295578,-0.10335642844438553,0.8749953508377075],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999998807907104,0.9999997615814209,0.9999999403953552,0.9999998807907104,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000004441968485480174,17.20370101928711,-0.000006082174877519719,-0.000008713177521713078,17.203702926635742,0.000008789989806246012],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.08050931990146637,0.14672638475894928,-0.6004754304885864,0.7819327712059021,-0.08966201543807983,0.13503269851207733,-0.64165198802948,0.749673068523407,-0.09497468918561935,0.12940824031829834,-0.6662954092025757,0.7282058596611023,-0.09884209930896759,0.13046392798423767,-0.6901784539222717,0.7048851847648621,-0.09766478836536407,0.14084535837173462,-0.7117235064506531,0.6812297105789185,-0.07613996416330338,0.17746619880199432,-0.716650664806366,0.6701644062995911,-0.06044689193367958,0.20170791447162628,-0.717658281326294,0.6637971997261047,-0.053863197565078735,0.210171639919281,-0.7158050537109375,0.6637392640113831,-0.0542733334004879,0.20975060760974884,-0.7130745053291321,0.6667712330818176,-0.06383706629276276,0.19526974856853485,-0.7039816975593567,0.6798561215400696,-0.05884639173746109,0.2009199857711792,-0.6939041018486023,0.6889596581459045,-0.04449494555592537,0.21299642324447632,-0.6530507802963257,0.7253808379173279,-0.03294188901782036,0.22849515080451965,-0.6140231490135193,0.7547717690467834,-0.02112559974193573,0.2415134161710739,-0.5875688791275024,0.7720024585723877,0.009366997517645359,0.266735315322876,-0.5800157785415649,0.769640326499939,0.022927885875105858,0.2721048593521118,-0.5733835101127625,0.7724407315254211,-0.004357507918030024,0.23350033164024353,-0.5765622854232788,0.7829652428627014,-0.019836541265249252,0.20189300179481506,-0.5590980648994446,0.8038999438285828,-0.04914766177535057,0.1380680501461029,-0.5571916103363037,0.8173488974571228,-0.1184108704328537,-0.0038869765121489763,-0.4562486708164215,0.8819302320480347,-0.1193382665514946,-0.17396052181720734,-0.29595425724983215,0.9316153526306152,-0.044442109763622284,-0.1881844401359558,-0.16552788019180298,0.9670636653900146,-0.010738649405539036,-0.0837668776512146,-0.058776289224624634,0.9946925044059753,-0.09362328052520752,-0.20702841877937317,0.0023983633145689964,0.9738419651985168,-0.10422533750534058,-0.34065479040145874,0.01345740631222725,0.9342966675758362,0.01083306409418583,-0.15224689245224,-0.050057172775268555,0.987014651298523,0.04405704513192177,-0.0025388270150870085,-0.039977382868528366,0.9982256293296814,0.03279547765851021,0.014556076377630234,-0.04061993956565857,0.9985302686691284,0.020762071013450623,0.05143143981695175,-0.0408066026866436,0.9976264834403992,0.02434113249182701,0.16859135031700134,-0.03429368883371353,0.9847885370254517,0.029418854042887688,0.30127063393592834,-0.025512950494885445,0.9527432322502136,0.033762674778699875,0.4262520372867584,-0.03762423247098923,0.9031908512115479,0.04707305133342743,0.5192975401878357,-0.09369292110204697,0.8481367230415344,0.07756335288286209,0.5931423306465149,-0.1658715307712555,0.7839979529380798,0.13094313442707062,0.637363612651825,-0.2586260139942169,0.7139566540718079,0.21122850477695465,0.613383412361145,-0.4006471335887909,0.64701247215271,0.2634293735027313,0.5629276037216187,-0.5035905241966248,0.6000950932502747,0.27797332406044006,0.5033534169197083,-0.5790359973907471,0.5779995918273926,0.2952812612056732,0.44356635212898254,-0.6458603739738464,0.5467379093170166,0.30606481432914734,0.37421077489852905,-0.703040599822998,0.5215597152709961,0.27207857370376587,0.345716655254364,-0.712350070476532,0.5468186736106873,0.20567983388900757,0.3300471007823944,-0.6902587413787842,0.6101701259613037,0.150239035487175,0.30653250217437744,-0.6736977100372314,0.6554368138313293,0.09982822835445404,0.2787805497646332,-0.6562951803207397,0.6939685344696045,0.053247690200805664,0.24779507517814636,-0.6371403932571411,0.727883517742157,0.012144212611019611,0.21620453894138336,-0.6167775988578796,0.7567651867866516,-0.021851733326911926,0.18806931376457214,-0.6001583337783813,0.7771502137184143,-0.04248357191681862,0.17402099072933197,-0.5943372845649719,0.7840121388435364,-0.05599009245634079,0.16633568704128265,-0.5967618227005005,0.7829897999763489,-0.06845516711473465,0.15716339647769928,-0.59989994764328,0.7814944982528687,-0.08050915598869324,0.1467263400554657,-0.6004754900932312,0.7819327712059021],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.0000001192092896,1,0.9999999403953552,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000007552434908575378,19.45049476623535,-0.000007778955477988347,0.0000068158842623233795,19.450496673583984,0.0000010524083791096928],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.14937162399291992,-0.14748123288154602,-0.019200153648853302,0.9775319695472717,0.14937160909175873,-0.14748121798038483,-0.019200151786208153,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1,0.9999998807907104,0.9999998807907104,0.9999999403953552,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.14690029621124268,6.40949821472168,0.2580966651439667,0.14690084755420685,6.409499168395996,0.2580986022949219],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.0202596727758646,0.012515287846326828,-0.011196224950253963,0.999653697013855,0.0202596727758646,0.012515285983681679,-0.011196223087608814,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.000000238418579,1,1.0000001192092896,1.0000003576278687,1],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,1.7000000476837158],"values":[-2.009107902267715e-7,6.8159003257751465,-0.000002612949174363166,-9.266649385608616e-7,6.815901279449463,0.0000032756965993030462],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.06515958160161972,0.3020924925804138,-0.05072396621108055,0.9496954679489136,0.06042582169175148,0.30767393112182617,-0.06238668039441109,0.9475196003913879,0.051941677927970886,0.30676040053367615,-0.07714542001485825,0.9472321271896362,0.04887143522500992,0.2890799939632416,-0.08093788474798203,0.952624499797821,0.048748768866062164,0.26286524534225464,-0.07822997868061066,0.9604194164276123,0.04977590963244438,0.2329869419336319,-0.07246040552854538,0.9684982895851135,0.05131176859140396,0.20093940198421478,-0.0648103654384613,0.9761096835136414,0.053090695291757584,0.1637742817401886,-0.053204912692308426,0.9836302995681763,0.05507253110408783,0.10414966195821762,-0.02608860656619072,0.9926929473876953,0.044037383049726486,0.029088683426380157,-0.00731023121625185,0.9985795617103577,0.02383418008685112,-0.026132483035326004,-0.0018849072512239218,0.9993726015090942,-0.0011566634057089686,-0.07918965816497803,-0.0028684765566140413,0.9968547821044922,-0.01050418522208929,-0.1493055820465088,-0.011761214584112167,0.9886653423309326,-0.025632819160819054,-0.2241702526807785,-0.019853709265589714,0.9740105271339417,-0.0419660285115242,-0.27026301622390747,-0.023579958826303482,0.9615824222564697,-0.04547058790922165,-0.3191923499107361,-0.03187517449259758,0.9460616707801819,-0.03223026916384697,-0.41783618927001953,-0.04244176298379898,0.9069579839706421,-0.03264356777071953,-0.5167422294616699,-0.04441725090146065,0.8543646931648254,-0.030881425365805626,-0.5694420337677002,-0.040811121463775635,0.8204368352890015,-0.008526962250471115,-0.566165030002594,-0.050435520708560944,0.8227033019065857,0.03478292003273964,-0.49828067421913147,-0.0557013675570488,0.8645252585411072,0.08360841870307922,-0.41596341133117676,-0.060348425060510635,0.9035165309906006,0.1199585422873497,-0.33096927404403687,-0.0601147785782814,0.9340533018112183,0.1367986500263214,-0.2697826623916626,-0.05454589053988457,0.9515924453735352,0.14736363291740417,-0.21693894267082214,-0.04669315740466118,0.9638678431510925,0.1545664519071579,-0.16587820649147034,-0.0376393124461174,0.9732301831245422,0.1540369838476181,-0.12575994431972504,-0.028684405609965324,0.9796091914176941,0.1477254331111908,-0.09248031675815582,-0.021578362211585045,0.9844587445259094,0.13725626468658447,-0.060449227690696716,-0.01810024306178093,0.9885236620903015,0.12474630028009415,-0.03002277947962284,-0.017157578840851784,0.991585910320282,0.11094691604375839,0.0037791363429278135,-0.019679568707942963,0.9936243295669556,0.09456595033407211,0.05748431384563446,-0.032956548035144806,0.9933109879493713,0.08194996416568756,0.11429066210985184,-0.052743151783943176,0.988655686378479,0.07612935453653336,0.1545242965221405,-0.06728740781545639,0.9827507138252258,0.07234285771846771,0.18149429559707642,-0.07577665150165558,0.9777956008911133,0.06864029169082642,0.20646680891513824,-0.08361297845840454,0.9724550843238831,0.06443218886852264,0.229739248752594,-0.09076002240180969,0.9668665528297424,0.05815201252698898,0.24824732542037964,-0.09497646987438202,0.9622739553451538,0.04682445526123047,0.25739938020706177,-0.09248635917901993,0.9607285261154175,0.032841019332408905,0.26109111309051514,-0.08504815399646759,0.9609993100166321,0.022287482395768166,0.267963707447052,-0.07879804074764252,0.9599425196647644,0.022666729986667633,0.2802935540676117,-0.07749789953231812,0.9565123319625854,0.025508049875497818,0.2873069941997528,-0.07777901738882065,0.954334557056427,0.029055044054985046,0.29272031784057617,-0.07834980636835098,0.9525397419929504,0.03295592591166496,0.29728391766548157,-0.0789962038397789,0.9509446620941162,0.036952704191207886,0.301327109336853,-0.0789690762758255,0.9495263695716858,0.041392337530851364,0.3039078712463379,-0.07799282670021057,0.9486009478569031,0.04619315266609192,0.30442795157432556,-0.0738176479935646,0.9485467076301575,0.05154850706458092,0.3039182126522064,-0.06747441738843918,0.9489066004753113,0.05817490816116333,0.30303865671157837,-0.05930174142122269,0.9493505954742432,0.06515957415103912,0.30209243297576904,-0.050723955035209656,0.9496954679489136],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999998211860657,1,1,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.5746002793312073,51.81690216064453,-20.522706985473633,0.5745996832847595,51.816890716552734,-20.522695541381836],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.18743081390857697,9.191021632659613e-8,-0.00524778850376606,0.9822638034820557,-0.18743079900741577,9.919903476429681e-8,-0.005247789900749922,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1.0000001192092896,1.000000238418579,1.0000001192092896,1.0000001192092896,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.5746011137962341,4.330300807952881,20.522706985473633,-0.5746014714241028,4.3303070068359375,20.522705078125],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.6296830773353577,-7.674562141346541e-8,0.017631003633141518,0.7766520380973816,0.6296830773353577,-7.434731941202699e-8,0.017631007358431816,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.0000003576278687,1.0000003576278687,1,1.0000003576278687,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,1.7000000476837158],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"c0fa8a57-de91-4a22-b925-59c86f0297d2","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/intoout.json b/public/anims/baked/sword_shield/intoout.json new file mode 100644 index 0000000..766cd87 --- /dev/null +++ b/public/anims/baked/sword_shield/intoout.json @@ -0,0 +1 @@ +{"name":"sword_shield/attack1-intoout","duration":1.7000000476837158,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.5806028842926025,56.27493667602539,0.03721565008163452,-0.42805778980255127,56.00941467285156,0.5324831008911133,-0.24404489994049072,55.72565460205078,1.0960255861282349,-0.03865236043930054,55.57026672363281,1.8817975521087646,0.2062172293663025,55.50332260131836,2.8395192623138428,0.49125516414642334,55.53767776489258,4.043420314788818,0.7923306822776794,55.60991668701172,5.351734161376953,1.1100603342056274,55.70604705810547,6.7549848556518555,1.5081768035888672,56.1225700378418,8.768156051635742,1.7371443510055542,56.655540466308594,10.79256820678711,1.7324281930923462,57.34444046020508,12.794827461242676,1.7461631298065186,58.01448059082031,14.84062385559082,1.7427587509155273,58.4493408203125,17.151874542236328,1.468902349472046,58.51986312866211,19.415058135986328,0.9324515461921692,58.36884689331055,21.611162185668945,0.01853889226913452,57.89317321777344,23.943626403808594,-0.9734368324279785,57.28437805175781,26.176496505737305,-1.952386736869812,57.000205993652344,27.97093391418457,-3.2111611366271973,57.1023063659668,29.413616180419922,-4.5356550216674805,57.632694244384766,30.49863052368164,-5.774686813354492,58.860374450683594,31.546812057495117,-6.810389041900635,60.115421295166016,32.50301742553711,-7.432973384857178,61.01994705200195,33.026790618896484,-7.686971664428711,61.511146545410156,33.1785888671875,-7.865643501281738,61.898353576660156,33.266780853271484,-8.013152122497559,62.248451232910156,33.321388244628906,-8.053926467895508,62.49006271362305,33.245304107666016,-8.00370979309082,62.67317581176758,33.01950454711914,-7.796690940856934,62.91081237792969,32.57255172729492,-7.5354719161987305,63.15272903442383,32.00617599487305,-7.184747695922852,63.55788803100586,31.1369686126709,-6.4993391036987305,64.18955993652344,29.381275177001953,-5.469149112701416,64.6544418334961,27.01319122314453,-4.4845170974731445,64.64556884765625,24.77488136291504,-3.694042205810547,64.13977813720703,22.661598205566406,-2.9671058654785156,63.43641662597656,20.5398006439209,-2.291182041168213,62.56769561767578,18.308063507080078,-1.7324881553649902,61.53300476074219,15.974343299865723,-1.451542615890503,59.94472122192383,13.191312789916992,-1.306430697441101,58.10435485839844,10.251790046691895,-1.1089725494384766,56.53977966308594,7.562553405761719,-1.0881385803222656,55.43568420410156,5.116037368774414,-1.0174825191497803,55.21246337890625,3.983900308609009,-0.9540262222290039,55.120121002197266,3.062114715576172,-0.8861302733421326,55.09471893310547,2.2475059032440186,-0.8204087018966675,55.09291076660156,1.4750196933746338,-0.7538955211639404,55.190208435058594,0.8844753503799438,-0.7004525661468506,55.37925338745117,0.48918282985687256,-0.6560940742492676,55.627296447753906,0.22463014721870422,-0.6178328990936279,55.943824768066406,0.11195409297943115,-0.58060622215271,56.274932861328125,0.03721803426742554],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.28925591707229614,-0.3346841633319855,0.2664211690425873,0.8563511371612549,0.29235851764678955,-0.3273513913154602,0.26347440481185913,0.8590394854545593,0.301119863986969,-0.31425249576568604,0.25741222500801086,0.8627347350120544,0.3089034855365753,-0.2885976731777191,0.25097060203552246,0.8708064556121826,0.3152083158493042,-0.2563663721084595,0.24350307881832123,0.8806964755058289,0.3211263120174408,-0.22147834300994873,0.23431147634983063,0.8904625177383423,0.3262802064418793,-0.1845235973596573,0.22416190803050995,0.8995797634124756,0.3291991651058197,-0.14195138216018677,0.21278031170368195,0.9089567065238953,0.32343775033950806,-0.07489831745624542,0.19575999677181244,0.9227439165115356,0.3205041289329529,0.005920276511460543,0.1817251443862915,0.9296332597732544,0.3325899839401245,0.08098399639129639,0.15899325907230377,0.9260381460189819,0.35712122917175293,0.15474019944667816,0.12151604890823364,0.9131011962890625,0.373192697763443,0.22338683903217316,0.08547072857618332,0.8963929414749146,0.37726354598999023,0.2716447412967682,0.05236094817519188,0.8838210701942444,0.3808908462524414,0.31922653317451477,0.02263352833688259,0.8674700260162354,0.38936612010002136,0.37896472215652466,-0.0055704438127577305,0.8394932150840759,0.39986351132392883,0.44651222229003906,-0.03207940235733986,0.7998168468475342,0.411450058221817,0.4930124580860138,-0.05231123045086861,0.7647948265075684,0.4136049747467041,0.4960971176624298,-0.05840981751680374,0.7611878514289856,0.40285494923591614,0.46767109632492065,-0.06141911819577217,0.7843592166900635,0.3729417622089386,0.3916921019554138,-0.05654621124267578,0.8392224311828613,0.33457183837890625,0.30387046933174133,-0.04865027964115143,0.8907062411308289,0.2980605661869049,0.23516453802585602,-0.0368744395673275,0.9243904948234558,0.2824588119983673,0.20100854337215424,-0.026145583018660545,0.9376187920570374,0.273677796125412,0.1751958131790161,-0.016113493591547012,0.9455935955047607,0.26648345589637756,0.15101231634616852,-0.006233370862901211,0.9519155025482178,0.26779836416244507,0.13544943928718567,0.003076026914641261,0.9539015293121338,0.274332731962204,0.1232757717370987,0.012871671468019485,0.9536136388778687,0.2826102375984192,0.10713884234428406,0.02329857274889946,0.9529480338096619,0.2918989360332489,0.08973143994808197,0.034243810921907425,0.9516147375106812,0.29964587092399597,0.06486105918884277,0.04604634642601013,0.950728714466095,0.29846033453941345,0.009220571257174015,0.06671015173196793,0.9520431756973267,0.28991585969924927,-0.05978880822658539,0.0946211889386177,0.9504846334457397,0.28357234597206116,-0.11352699249982834,0.11994875967502594,0.9446219801902771,0.28100988268852234,-0.15256911516189575,0.1410098522901535,0.9369484782218933,0.2781703472137451,-0.18880483508110046,0.16231736540794373,0.9276999235153198,0.2749726176261902,-0.22255660593509674,0.18363872170448303,0.9171344041824341,0.27324363589286804,-0.2501692473888397,0.2028271108865738,0.9064295291900635,0.2757648527622223,-0.2642940580844879,0.21780230104923248,0.8981450796127319,0.28236639499664307,-0.27112072706222534,0.22821931540966034,0.8914475440979004,0.2888340950012207,-0.2841484546661377,0.23831798136234283,0.8826318979263306,0.29106998443603516,-0.30774253606796265,0.2498856633901596,0.8707066178321838,0.2906310260295868,-0.31920501589775085,0.25644853711128235,0.8647981882095337,0.289189875125885,-0.32705098390579224,0.26199501752853394,0.8606773614883423,0.2872169017791748,-0.3329509496688843,0.26703697443008423,0.8575205206871033,0.2854592204093933,-0.3381424844264984,0.27148357033729553,0.8546750545501709,0.28372302651405334,-0.3407139778137207,0.27449744939804077,0.8532680869102478,0.2834787666797638,-0.34095320105552673,0.27475857734680176,0.8531696796417236,0.28443658351898193,-0.33984532952308655,0.27320560812950134,0.8537914156913757,0.2866881489753723,-0.3374280631542206,0.27002161741256714,0.855009138584137,0.2892559766769409,-0.3346841335296631,0.2664211094379425,0.8563511967658997],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,0.9999997615814209,0.9999997615814209,0.9999998807907104,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[8.305602073669434,-6.711593151092529,5.719297409057617,8.305603981018066,-6.711597919464111,5.719293594360352],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.6466915607452393,-0.06025046482682228,-0.44836875796318054,0.6141053438186646,0.6417709589004517,-0.061733368784189224,-0.4402817487716675,0.624876856803894,0.6299023032188416,-0.06600397080183029,-0.4339978098869324,0.6407124996185303,0.6236813068389893,-0.07847126573324203,-0.4179028570652008,0.655912458896637,0.619573712348938,-0.09696368873119354,-0.40355148911476135,0.6662377119064331,0.6175748109817505,-0.1242644339799881,-0.3975348472595215,0.6671774983406067,0.6278656721115112,-0.1489068865776062,-0.3645661175251007,0.6713441610336304,0.6457183361053467,-0.17431394755840302,-0.311791330575943,0.6748693585395813,0.6666441559791565,-0.23455873131752014,-0.28332021832466125,0.6483035087585449,0.6975921392440796,-0.27247267961502075,-0.2230055034160614,0.6240133047103882,0.717384397983551,-0.2790997326374054,-0.16404497623443604,0.6168893575668335,0.7209242582321167,-0.2765810489654541,-0.10700707882642746,0.6263552308082581,0.7237850427627563,-0.26430168747901917,-0.03954988345503807,0.6361727118492126,0.7240720391273499,-0.24094688892364502,0.008055128157138824,0.6462193727493286,0.718824565410614,-0.21847185492515564,0.05255894362926483,0.657874584197998,0.7034744024276733,-0.20342515408992767,0.0942678153514862,0.6744297742843628,0.681032657623291,-0.19397559762001038,0.13589094579219818,0.6928936839103699,0.663406491279602,-0.1831367462873459,0.15399408340454102,0.7089700102806091,0.6678176522254944,-0.1609831303358078,0.12990935146808624,0.7150017619132996,0.6901262998580933,-0.12164901196956635,0.07894185930490494,0.709010124206543,0.7397623062133789,-0.05158410966396332,0.008514064364135265,0.6708340644836426,0.785835862159729,0.01487583015114069,-0.0968291163444519,0.6106265187263489,0.8132516145706177,0.05898354575037956,-0.1716121882200241,0.5528942346572876,0.8244950771331787,0.07203428447246552,-0.19282452762126923,0.527103066444397,0.8349951505661011,0.07611814141273499,-0.1944636106491089,0.5090903639793396,0.8433080911636353,0.0760011374950409,-0.2103901505470276,0.48866280913352966,0.846778154373169,0.06820831447839737,-0.2316974699497223,0.47395211458206177,0.8613415956497192,0.05600891262292862,-0.2177475243806839,0.4555652141571045,0.869415283203125,0.04300941899418831,-0.23521463572978973,0.43236714601516724,0.871135950088501,0.02619403414428234,-0.2619766592979431,0.41449275612831116,0.8647927641868591,0.008298774249851704,-0.2911653220653534,0.40900781750679016,0.8606566786766052,-0.019644271582365036,-0.31950265169143677,0.39598265290260315,0.8607558012008667,-0.03599445894360542,-0.33809876441955566,0.3788048028945923,0.8629150390625,-0.044405534863471985,-0.3266419768333435,0.38302847743034363,0.8443660140037537,-0.05727866664528847,-0.34706082940101624,0.4041212201118469,0.8211144804954529,-0.0726940706372261,-0.3714459240436554,0.4272170960903168,0.8024358153343201,-0.08561412245035172,-0.37843969464302063,0.4533766210079193,0.7847788333892822,-0.09396766126155853,-0.37310391664505005,0.48588648438453674,0.7556763887405396,-0.09304032474756241,-0.3685905933380127,0.5333269238471985,0.7166336178779602,-0.08731760829687119,-0.372244268655777,0.5833061337471008,0.6780975461006165,-0.0787169486284256,-0.38674336671829224,0.6200137138366699,0.6479260921478271,-0.064064159989357,-0.4120543599128723,0.6374157071113586,0.6336401104927063,-0.06225930154323578,-0.43721070885658264,0.6351935267448425,0.6244519948959351,-0.06268958747386932,-0.4569084942340851,0.6303684711456299,0.6218583583831787,-0.06259683519601822,-0.4659107029438019,0.6263393759727478,0.6228551268577576,-0.06119593232870102,-0.46825575828552246,0.6237332224845886,0.6274338960647583,-0.06075672805309296,-0.46619921922683716,0.6207202672958374,0.632938027381897,-0.06011822447180748,-0.46150830388069153,0.6186965107917786,0.6384124159812927,-0.059436067938804626,-0.455893337726593,0.6172991394996643,0.6431431174278259,-0.05940760672092438,-0.45093390345573425,0.6160327196121216,0.6466918587684631,-0.06025047227740288,-0.44836869835853577,0.6141050457954407],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999998807907104,1,0.9999994039535522,0.9999998807907104,1,0.9999990463256836],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000001175460965896491,25.81270980834961,-0.0000028554120490298374,-0.0000017708764517010422,25.812707901000977,-0.000004281139354134211],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.40776705741882324,0.21367254853248596,0.06294765323400497,0.8854985237121582,0.4241519570350647,0.2230546772480011,0.06451603770256042,0.8753167390823364,0.4362923204898834,0.2488136738538742,0.07164950668811798,0.8617465496063232,0.46752187609672546,0.19820183515548706,0.04718800261616707,0.8601818084716797,0.507066011428833,0.1314064860343933,0.009503670036792755,0.8517782092094421,0.5270468592643738,0.08509115129709244,-0.019491469487547874,0.8453409075737,0.5320841073989868,0.007729473523795605,-0.0665799081325531,0.8440343141555786,0.5227251052856445,-0.08407420665025711,-0.1226639524102211,0.8394305109977722,0.4984068274497986,-0.07934732735157013,-0.1635560393333435,0.847669780254364,0.4786711037158966,-0.06507816165685654,-0.15734529495239258,0.8613252639770508,0.4646359086036682,-0.05028495192527771,-0.12165815383195877,0.8756622076034546,0.45376476645469666,-0.023896483704447746,-0.09509914368391037,0.8857102990150452,0.45229649543762207,-0.012102203443646431,-0.08320023864507675,0.887895941734314,0.4723488688468933,-0.004229831043630838,-0.08161935210227966,0.8776143789291382,0.49356701970100403,0.004698972683399916,-0.08145520091056824,0.8658722043037415,0.5135558247566223,0.0290206465870142,-0.07396432012319565,0.8543696403503418,0.524937629699707,0.06175406277179718,-0.05622602626681328,0.8470334410667419,0.5215153694152832,0.09531629830598831,-0.030645163729786873,0.8473473191261292,0.5172802805900574,0.12095975130796432,-0.006628531962633133,0.8471989035606384,0.5153272151947021,0.13893111050128937,0.013005811721086502,0.8455571532249451,0.5122763514518738,0.1337576061487198,0.016586599871516228,0.848178505897522,0.502774715423584,0.15098936855793,0.03440973535180092,0.8504326939582825,0.4944876432418823,0.1629704087972641,0.043230731040239334,0.8526744842529297,0.49158358573913574,0.15790563821792603,0.03637201339006424,0.8556216955184937,0.487737774848938,0.1426226943731308,0.019580576568841934,0.8610384464263916,0.4778144955635071,0.1391638219356537,0.009207366034388542,0.867318868637085,0.46259182691574097,0.14231950044631958,0.008081287145614624,0.875036358833313,0.4360128343105316,0.11379817128181458,-0.0051138149574398994,0.8927018642425537,0.3985357880592346,0.10384666174650192,0.0037863366305828094,0.9112468361854553,0.36398589611053467,0.10394059121608734,0.013937966898083687,0.9254816770553589,0.3469804525375366,0.10448896139860153,0.016495920717716217,0.9318876266479492,0.3502008318901062,0.10476038604974747,0.012589084915816784,0.9307126998901367,0.33848056197166443,0.12149876356124878,0.025828015059232712,0.9327389597892761,0.3370666801929474,0.13317610323429108,0.03570546954870224,0.9313298463821411,0.35420510172843933,0.18253526091575623,0.052051663398742676,0.9157020449638367,0.36360234022140503,0.24474523961544037,0.07429654896259308,0.8957528471946716,0.37296435236930847,0.28027552366256714,0.08615878224372864,0.8802953362464905,0.41124141216278076,0.25288668274879456,0.07270500063896179,0.8727214932441711,0.45506158471107483,0.22985883057117462,0.05845513939857483,0.8582929968833923,0.49362918734550476,0.22077122330665588,0.05172671377658844,0.8395919799804688,0.516306459903717,0.21702562272548676,0.04961974918842316,0.8269615769386292,0.5219892859458923,0.21597322821617126,0.050307709723711014,0.8236212134361267,0.5133308172225952,0.22480781376361847,0.058377139270305634,0.8261628746986389,0.5019983053207397,0.23224160075187683,0.06541784107685089,0.8305311799049377,0.48958128690719604,0.23271314799785614,0.06766019761562347,0.8376018404960632,0.4767707586288452,0.2285233736038208,0.06676376610994339,0.8461733460426331,0.4629229009151459,0.22306060791015625,0.0649418756365776,0.8554115891456604,0.4491659700870514,0.21814779937267303,0.06327232718467712,0.8640937805175781,0.4354860484600067,0.21427294611930847,0.0619741752743721,0.8721228241920471,0.4218702018260956,0.2126859724521637,0.061789970844984055,0.8791884183883667,0.40776583552360535,0.21367256343364716,0.06294761598110199,0.885499119758606],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,1.0000001192092896,0.9999999403953552,0.9999999403953552,1.0000001192092896],"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000001958887651198893,22.74271011352539,-0.000005931609393883264,0.0000029277714475028915,22.74271011352539,-0.0000014023285075381864],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.4524131119251251,-0.04905669391155243,0.04749445244669914,0.889190673828125,-0.46276965737342834,-0.048721376806497574,0.04497905075550079,0.8839951157569885,-0.468750536441803,-0.046047233045101166,0.03819528594613075,0.8813022971153259,-0.4885430335998535,-0.034728653728961945,0.0250829067081213,0.8714874982833862,-0.5366249680519104,-0.028040997684001923,0.016234587877988815,0.8431985378265381,-0.5614665746688843,-0.022864557802677155,0.004291537683457136,0.8271723389625549,-0.5654977560043335,-0.026141861453652382,0.002365949796512723,0.824332058429718,-0.5593727827072144,-0.035459939390420914,0.010702681727707386,0.8280882835388184,-0.5537953972816467,-0.039760977029800415,0.008045493625104427,0.8316640257835388,-0.557641327381134,-0.035899609327316284,-0.004331393167376518,0.8292940258979797,-0.5573923587799072,-0.02487722970545292,-0.025007260963320732,0.8294996023178101,-0.553877055644989,-0.02063518762588501,-0.04211651161313057,0.8312764763832092,-0.5559660792350769,-0.022295158356428146,-0.04194069653749466,0.8298467397689819,-0.5706496238708496,-0.026602853089571,-0.02900209277868271,0.8202500939369202,-0.5841237306594849,-0.03057539463043213,-0.011952911503612995,0.811000406742096,-0.5932627320289612,-0.03376816585659981,0.0025576066691428423,0.8042963147163391,-0.5947413444519043,-0.035433802753686905,0.011920513585209846,0.8030473589897156,-0.5895165205001831,-0.035524871200323105,0.015261405147612095,0.8068304061889648,-0.5905081033706665,-0.03697383031249046,0.017740678042173386,0.8059890866279602,-0.5978039503097534,-0.03834475204348564,0.020512258633971214,0.800462007522583,-0.619246780872345,-0.04807007312774658,0.03606078401207924,0.7828935980796814,-0.630199134349823,-0.051762666553258896,0.04010309278964996,0.7736675143241882,-0.6219239830970764,-0.057643841952085495,0.04666327312588692,0.7795577049255371,-0.6050601005554199,-0.06637918204069138,0.05803648754954338,0.7912824153900146,-0.5889100432395935,-0.07742151618003845,0.07575120031833649,0.8009074330329895,-0.5780107378959656,-0.08412172645330429,0.08592689782381058,0.8071206212043762,-0.5780724883079529,-0.08444852381944656,0.08535496145486832,0.8071029782295227,-0.5923905968666077,-0.09027282893657684,0.0947277769446373,0.7949534058570862,-0.6083462834358215,-0.08487454801797867,0.0856345146894455,0.7844602465629578,-0.615332305431366,-0.07818147540092468,0.07420141994953156,0.7808636426925659,-0.6058875918388367,-0.07338254898786545,0.06662249565124512,0.7893519997596741,-0.6465392708778381,-0.06718362122774124,0.05153457075357437,0.7581672072410583,-0.66748046875,-0.04893093183636665,0.03671291470527649,0.7421103715896606,-0.6733818650245667,-0.03443246707320213,0.029018832370638847,0.737922191619873,-0.6632712483406067,-0.022114403545856476,0.008869784884154797,0.7479997277259827,-0.625298023223877,-0.011905022896826267,-0.015423886477947235,0.7801427841186523,-0.5859824419021606,-0.009048876352608204,-0.025999896228313446,0.8098559379577637,-0.5925716161727905,-0.014537554234266281,-0.019539333879947662,0.8051495552062988,-0.5924906730651855,-0.02463965304195881,-0.005140574183315039,0.805184006690979,-0.5837851166725159,-0.03174556419253349,0.006613307166844606,0.8112604022026062,-0.5688828825950623,-0.036999303847551346,0.016502320766448975,0.8214200735092163,-0.5507224202156067,-0.04332709312438965,0.028517263010144234,0.8330752849578857,-0.5356059670448303,-0.041611723601818085,0.02724672108888626,0.8430019617080688,-0.5211480855941772,-0.039802227169275284,0.026026954874396324,0.8521403074264526,-0.5084437131881714,-0.04043317213654518,0.02882515825331211,0.8596622943878174,-0.49675676226615906,-0.04288044571876526,0.034389689564704895,0.8661473989486694,-0.4862513244152069,-0.0452883243560791,0.03966307267546654,0.8717427849769592,-0.4769490957260132,-0.04718177393078804,0.04373432695865631,0.8765733242034912,-0.46837764978408813,-0.04867330193519592,0.04670336842536926,0.8809495568275452,-0.4605638384819031,-0.04924744367599487,0.047811977565288544,0.8849686980247498,-0.4524124264717102,-0.04905689135193825,0.047494761645793915,0.8891910314559937],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999998211860657,0.9999998211860657,0.9999998211860657,0.9999998211860657,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.0000010550772913120454,13.415114402770996,0.000004097688815818401,-2.5525787350488827e-7,13.415109634399414,9.854056770564057e-7],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.3069814443588257,-0.00566264521330595,-0.0018291984451934695,0.9516969323158264,-0.3069814443588257,-0.005662641022354364,-0.001829207525588572,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1,1.0000001192092896,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[-11.139494895935059,-4.020092964172363,0.42029789090156555,-11.139496803283691,-4.020102500915527,0.42029672861099243],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.706893265247345,-0.2815976142883301,0.46786463260650635,0.4495636224746704,0.7031827569007874,-0.281584769487381,0.4797726273536682,0.4427892863750458,0.7110507488250732,-0.2866947650909424,0.48185572028160095,0.4242970645427704,0.7250892519950867,-0.29658976197242737,0.48720261454582214,0.38589349389076233,0.7357468605041504,-0.2933819591999054,0.503734827041626,0.34475329518318176,0.7435830235481262,-0.29213017225265503,0.5091729164123535,0.3201361894607544,0.7462989687919617,-0.2923000454902649,0.5160142183303833,0.3022049367427826,0.7340795993804932,-0.2921345829963684,0.5449073910713196,0.28082114458084106,0.7874880433082581,-0.30908870697021484,0.47106683254241943,0.24984541535377502,0.8548504114151001,-0.30636000633239746,0.3597615659236908,0.21435008943080902,0.9060297608375549,-0.24955250322818756,0.28353893756866455,0.19089078903198242,0.8639914989471436,-0.19470804929733276,0.3364017903804779,0.32006457448005676,0.778418242931366,-0.15191949903964996,0.39176350831985474,0.46637633442878723,0.6843113303184509,-0.17074738442897797,0.37238529324531555,0.6032350659370422,0.6273206472396851,-0.20114019513130188,0.3727252185344696,0.6535192131996155,0.586701512336731,-0.2706693708896637,0.39855706691741943,0.6509007811546326,0.5879880785942078,-0.32616060972213745,0.4520706832408905,0.5861069560050964,0.5895338654518127,-0.30108749866485596,0.5105115175247192,0.548793375492096,0.5963411927223206,-0.2836501896381378,0.5043245553970337,0.5563960075378418,0.6337848901748657,-0.24994787573814392,0.4632861018180847,0.5667529702186584,0.6928530335426331,-0.18066608905792236,0.4082682728767395,0.5662434101104736,0.7744209170341492,-0.12017983198165894,0.3027621805667877,0.54236900806427,0.8254724144935608,-0.07515839487314224,0.23972752690315247,0.5054476261138916,0.8363884687423706,-0.04788520187139511,0.2480068951845169,0.4864709973335266,0.8426846265792847,-0.04577089473605156,0.2565034031867981,0.4711620807647705,0.8574345707893372,-0.060500726103782654,0.23501434922218323,0.45377737283706665,0.8652694821357727,-0.08137660473585129,0.21934758126735687,0.44336575269699097,0.8683505058288574,-0.09633079916238785,0.2132355123758316,0.43728524446487427,0.8472703099250793,-0.09791619330644608,0.20843324065208435,0.47864505648612976,0.8176848292350769,-0.09978631138801575,0.19398687779903412,0.5327319502830505,0.8066613674163818,-0.10687439888715744,0.17505182325839996,0.5542852878570557,0.8109596371650696,-0.11167392134666443,0.15557041764259338,0.5528755187988281,0.8063710331916809,-0.11323340982198715,0.17089934647083282,0.5547407865524292,0.8059206008911133,-0.13906553387641907,0.16132359206676483,0.5523834824562073,0.8181670308113098,-0.18181835114955902,0.14380957186222076,0.5261783003807068,0.8378053307533264,-0.22227393090724945,0.1286659985780716,0.4817900061607361,0.8482232689857483,-0.2601524293422699,0.18210765719413757,0.4238807260990143,0.840337872505188,-0.2929532825946808,0.24431845545768738,0.38512226939201355,0.8213282227516174,-0.32633668184280396,0.33783289790153503,0.32371777296066284,0.7892025709152222,-0.3337991237640381,0.4126811921596527,0.3089202344417572,0.7894800305366516,-0.32444509863853455,0.4234526455402374,0.30355319380760193,0.7941168546676636,-0.3117547333240509,0.4257220923900604,0.3015761077404022,0.7776656746864319,-0.3122963011264801,0.4182349145412445,0.35040923953056335,0.7612480521202087,-0.31413331627845764,0.41391828656196594,0.38793468475341797,0.7472469806671143,-0.315345823764801,0.41190028190612793,0.41535183787345886,0.7337311506271362,-0.3152502179145813,0.4120878577232361,0.4386793375015259,0.7201960682868958,-0.3139960467815399,0.4207842946052551,0.45350271463394165,0.7095845341682434,-0.3090876042842865,0.43501898646354675,0.4601230323314667,0.7024116516113281,-0.30132290720939636,0.4503832757472992,0.46149471402168274,0.7035919427871704,-0.2918103337287903,0.45986390113830566,0.45643216371536255,0.7068933248519897,-0.28159770369529724,0.4678645133972168,0.44956356287002563],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,1.000000238418579,1.000000238418579,1.0000001192092896,1.0000003576278687],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000001355268864244863,25.527101516723633,-0.0000011704943290169467,0.0000025574659048288595,25.52710723876953,9.408555570189492e-7],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.348246306180954,-0.09891270846128464,-0.005920770578086376,0.9321511387825012,0.3428623080253601,-0.10174769908189774,-0.005006594117730856,0.9338456988334656,0.3313550353050232,-0.08208130300045013,-0.016114216297864914,0.9397908449172974,0.3025062084197998,-0.04826885089278221,-0.03238585963845253,0.9513733386993408,0.262849897146225,-0.022420432418584824,-0.029946651309728622,0.9641112089157104,0.2601119577884674,-0.00026337351300753653,-0.01634945720434189,0.965440034866333,0.27359187602996826,0.014533181674778461,-0.005516841541975737,0.9617202877998352,0.2828464210033417,0.011851772665977478,-0.008310557343065739,0.95905601978302,0.32561829686164856,0.1051190122961998,0.030635932460427284,0.9391401410102844,0.3383248746395111,0.20592711865901947,0.06928499042987823,0.9156035780906677,0.3584786057472229,0.2254665642976761,0.09524434804916382,0.9008809328079224,0.5245801210403442,0.03906211256980896,0.05468044430017471,0.8487048149108887,0.6530225872993469,-0.08152098208665848,-0.03870847821235657,0.75194251537323,0.7008978128433228,-0.11054825782775879,-0.09722443670034409,0.6979030966758728,0.6875154376029968,-0.09073743969202042,-0.07221942394971848,0.7168498039245605,0.5905498266220093,-0.08700329810380936,-0.09136788547039032,0.7970779538154602,0.44681957364082336,-0.09073778241872787,-0.12243231385946274,0.8815493583679199,0.4539288580417633,-0.09643986076116562,-0.11436901241540909,0.8783892393112183,0.4802742302417755,-0.08749683946371078,-0.10482282936573029,0.8664255142211914,0.4794779121875763,-0.07022503018379211,-0.08948414772748947,0.8701505661010742,0.4327932596206665,-0.0836244449019432,-0.0727619007229805,0.8946523070335388,0.36537081003189087,-0.06442620605230331,-0.02923927642405033,0.9281694293022156,0.29963427782058716,-0.07323656976222992,0.005950446240603924,0.9512204527854919,0.26069045066833496,-0.11764013022184372,0.01589534431695938,0.9580963850021362,0.22876925766468048,-0.14760413765907288,-0.008275269530713558,0.962189793586731,0.19191423058509827,-0.14161238074302673,-0.03669196367263794,0.9704475998878479,0.1552809774875641,-0.1320808082818985,-0.0693923681974411,0.9765384197235107,0.11319711059331894,-0.13154102861881256,-0.09127023071050644,0.9805881977081299,0.13744184374809265,-0.1497480571269989,-0.10367143154144287,0.9736208319664001,0.1961175501346588,-0.14808498322963715,-0.10833845287561417,0.9632608890533447,0.2467367947101593,-0.11003191024065018,-0.1090942844748497,0.9566150903701782,0.29584038257598877,-0.07148944586515427,-0.10018554329872131,0.9472754001617432,0.3774159848690033,-0.06762335449457169,-0.07470708340406418,0.9205450415611267,0.4721914529800415,-0.029892096295952797,-0.01634393446147442,0.8808374404907227,0.5220556855201721,0.02205657958984375,0.03734424710273743,0.8518079519271851,0.5196996331214905,0.06761535257101059,0.08204304426908493,0.8477083444595337,0.4691406786441803,0.07556718587875366,0.0870683342218399,0.8755659461021423,0.39423200488090515,0.0656149685382843,0.07380437850952148,0.9136896133422852,0.27898144721984863,0.03652912750840187,0.015424789860844612,0.9594774842262268,0.2543429136276245,0.03392184153199196,-0.007746926974505186,0.9664880037307739,0.23113107681274414,0.02699369192123413,-0.00036198936868458986,0.9725480079650879,0.17941203713417053,0.02838856354355812,0.0004980614176020026,0.9833642244338989,0.25070205330848694,0.00817677192389965,0.013735531829297543,0.9679323434829712,0.3009016215801239,-0.015505176968872547,0.019914045929908752,0.9533211588859558,0.3354988396167755,-0.034211333841085434,0.020591329783201218,0.9411939978599548,0.36318695545196533,-0.05215190723538399,0.018008960410952568,0.9300812482833862,0.3783379793167114,-0.07015980035066605,0.009960295632481575,0.9229511618614197,0.38160187005996704,-0.08621500432491302,0.0007515655597671866,0.9202969074249268,0.3772171139717102,-0.09873059391975403,-0.00624352041631937,0.9208260178565979,0.3641504645347595,-0.10041598230600357,-0.007044950034469366,0.9258841276168823,0.3482460081577301,-0.09891287237405777,-0.005920592229813337,0.932151198387146],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,0.9999999403953552,0.9999998211860657,1,0.9999998211860657,0.9999999403953552],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000002871927563319332,23.109195709228516,-6.065718025638489e-7,0.000003533056997184758,23.109201431274414,-0.0000023242030238179723],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.5572507381439209,0.15959540009498596,-0.20703673362731934,0.788122296333313,-0.553617537021637,0.1612894982099533,-0.21326212584972382,0.7886778116226196,-0.5485606789588928,0.1639280766248703,-0.2177942544221878,0.7904267311096191,-0.5371207594871521,0.16961702704429626,-0.22423309087753296,0.7952678203582764,-0.5178089737892151,0.1764107644557953,-0.233154758810997,0.803985059261322,-0.5162700414657593,0.1692388504743576,-0.21436013281345367,0.8117100596427917,-0.5238315463066101,0.15112900733947754,-0.1754816174507141,0.8197358250617981,-0.5232238173484802,0.12307074666023254,-0.12131483852863312,0.8344897627830505,-0.47406473755836487,0.09920921921730042,-0.05567064881324768,0.8731099367141724,-0.3282271921634674,0.05136051028966904,0.010234728455543518,0.9431459307670593,-0.3948962092399597,0.09563182294368744,-0.08126877248287201,0.9101136922836304,-0.5868803262710571,0.09349377453327179,-0.08296061307191849,0.7999674677848816,-0.6606878042221069,0.03426104038953781,0.016698531806468964,0.7496926188468933,-0.6374971866607666,0.011328439228236675,0.06188742443919182,0.7678795456886292,-0.6444305777549744,0.006017102859914303,0.07110516726970673,0.7613258957862854,-0.5492671132087708,0.03014373406767845,0.026431173086166382,0.8346846103668213,-0.4729693531990051,0.08755858987569809,-0.04001650959253311,0.8758037686347961,-0.5718486905097961,0.10294661670923233,-0.07136232405900955,0.8107395172119141,-0.6010680198669434,0.099615179002285,-0.06829717010259628,0.7900187373161316,-0.6077594757080078,0.09150441735982895,-0.057663124054670334,0.7867213487625122,-0.5855779051780701,0.07185225188732147,-0.03314252197742462,0.8067449331283569,-0.5511397123336792,0.06758635491132736,-0.03527035936713219,0.8309230804443359,-0.5149125456809998,0.06498385965824127,-0.03905852511525154,0.8538832664489746,-0.4924304187297821,0.05904301628470421,-0.03475147485733032,0.8676511645317078,-0.4726896584033966,0.04939405620098114,-0.018587233498692513,0.8796471953392029,-0.450588196516037,0.04098498448729515,-0.006129838526248932,0.8917695879936218,-0.4286007881164551,0.033569466322660446,0.006885922513902187,0.9028438925743103,-0.4033021926879883,0.029964562505483627,0.014262648299336433,0.9144648909568787,-0.3459714353084564,0.017731081694364548,0.047781601548194885,0.9368597865104675,-0.29581859707832336,0.005500229075551033,0.07402102649211884,0.9523560404777527,-0.29254889488220215,-0.002578896936029196,0.09203069657087326,0.9518082141876221,-0.33361494541168213,0.001433761091902852,0.09571877866983414,0.9378363490104675,-0.41137397289276123,0.017197705805301666,0.0756327360868454,0.9081604480743408,-0.5023289918899536,0.055998049676418304,0.0032415406312793493,0.8628553748130798,-0.5400574207305908,0.1052723228931427,-0.08639004826545715,0.8305374979972839,-0.5265421271324158,0.14277653396129608,-0.15527381002902985,0.8235644102096558,-0.49272090196609497,0.1350158303976059,-0.13285428285598755,0.8493212461471558,-0.44366875290870667,0.12731318175792694,-0.10779089480638504,0.8805285692214966,-0.3793765902519226,0.1431671530008316,-0.13505393266677856,0.9040669202804565,-0.40046051144599915,0.14776359498500824,-0.1108330637216568,0.8975039124488831,-0.45298710465431213,0.17985230684280396,-0.17366576194763184,0.8557429909706116,-0.4614815413951874,0.1987057328224182,-0.26534608006477356,0.82288658618927,-0.5084545016288757,0.19178254902362823,-0.26647865772247314,0.7960418462753296,-0.5305593013763428,0.18878433108329773,-0.2619163393974304,0.7837519645690918,-0.5463096499443054,0.18552523851394653,-0.2542959749698639,0.7761828303337097,-0.5594429969787598,0.18196791410446167,-0.24432514607906342,0.7708544135093689,-0.5673881769180298,0.1763233095407486,-0.2322922646999359,0.7700785994529724,-0.5702413320541382,0.17036186158657074,-0.22070655226707458,0.772716224193573,-0.5693888068199158,0.16492384672164917,-0.21154624223709106,0.7770745158195496,-0.5640853047370911,0.162114217877388,-0.20832684636116028,0.7823852896690369,-0.5572505593299866,0.15959544479846954,-0.2070367932319641,0.7881224155426025],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999998807907104,1.0000001192092896,1.0000001192092896,0.9999998807907104,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.0000034142626645916607,13.547398567199707,6.825279683653207e-7,-0.000003735066229637596,13.54739761352539,-6.556514904332289e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.30788561701774597,0.014501913450658321,0.004691826179623604,0.9513012766838074,-0.3078855574131012,0.014501913450658321,0.004691829904913902,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,1.0000001192092896,1,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,1.7000000476837158],"values":[2.834000825881958,7.754306316375732,-6.139601707458496,2.8340001106262207,7.754297256469727,-6.13960075378418],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.3320007026195526,-0.09571807086467743,-0.11373621225357056,0.9314922094345093,-0.3320007026195526,-0.09571804851293564,-0.11373622715473175,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1,0.9999999403953552,0.9999998807907104,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,1.7000000476837158],"values":[1.1082781981031076e-7,10.288592338562012,5.064072752247739e-7,3.492462212761893e-7,10.288599967956543,-4.472674106636987e-7],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-1.654552761465311e-8,-8.44009306888438e-10,1.0346411727368832e-8,1,-1.654552761465311e-8,2.8230742188384284e-9,9.880750440061092e-9,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999999403953552,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,1.7000000476837158],"values":[-9.227898800645562e-8,10.28859806060791,1.169440366766139e-7,-1.6826524174007318e-8,10.288605690002441,-0.0000012683876775554381],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.0436820425093174,0.029420556500554085,-0.019534049555659294,0.9984211325645447,0.04581921175122261,0.021200548857450485,-0.01311154942959547,0.9986386895179749,0.04619516059756279,0.013804350979626179,-0.002943543018773198,0.9988327026367188,0.04635752737522125,0.014430359937250614,0.001442129840143025,0.9988196492195129,0.047730453312397,0.01828496903181076,0.003410700475797057,0.9986870884895325,0.04976551979780197,0.023310448974370956,0.005675774998962879,0.9984726905822754,0.05256510153412819,0.028291406109929085,0.007822534069418907,0.9981859922409058,0.05852857232093811,0.031063038855791092,0.008724958635866642,0.9977642297744751,0.07895440608263016,0.022882355377078056,0.004756774753332138,0.9966042041778564,0.10509958118200302,0.010024037212133408,-0.008960711769759655,0.9943708181381226,0.11939791589975357,-0.008710386231541634,-0.01588471792638302,0.9926812052726746,0.11962317675352097,-0.031093521043658257,-0.005830751731991768,0.9923152327537537,0.11440384387969971,-0.03023384138941765,0.005226621404290199,0.9929603934288025,0.11176200956106186,0.001553896814584732,0.017210055142641068,0.9935847520828247,0.10777458548545837,0.03671620413661003,0.02104506455361843,0.9932742118835449,0.10012240707874298,0.06516268104314804,0.015907946974039078,0.99271160364151,0.09289278090000153,0.09982389956712723,0.0064362213015556335,0.9906385540962219,0.08585499972105026,0.1510189026594162,-0.013328415341675282,0.9847053289413452,0.08968541026115417,0.19209879636764526,-0.035011157393455505,0.9766415953636169,0.09513932466506958,0.19249452650547028,-0.04057835415005684,0.9758318662643433,0.10237598419189453,0.1850634664297104,-0.041325926780700684,0.97650545835495,0.11009468883275986,0.17546942830085754,-0.04001794755458832,0.9774907827377319,0.11822257936000824,0.15766610205173492,-0.03520134463906288,0.9797579646110535,0.11890432238578796,0.13372547924518585,-0.03306465968489647,0.9833036661148071,0.11668980121612549,0.10887300968170166,-0.03255997225642204,0.9866458773612976,0.11384691298007965,0.08394773304462433,-0.03213191777467728,0.9894236922264099,0.10767888277769089,0.05875261127948761,-0.03268605098128319,0.9919098019599915,0.09987945109605789,0.034375790506601334,-0.03327753767371178,0.9938485622406006,0.09254401177167892,0.01275140605866909,-0.03131561353802681,0.9951343536376953,0.08539532124996185,-0.007721180561929941,-0.02845260314643383,0.9959108829498291,0.08012522011995316,-0.024959417060017586,-0.02283419854938984,0.9962106347084045,0.08366135507822037,-0.02888042852282524,-0.012161404825747013,0.9960014224052429,0.09277508407831192,-0.02082681655883789,-0.0035428558476269245,0.995462954044342,0.09645124524831772,-0.01274975948035717,0.0007651714258827269,0.9952557682991028,0.09438960254192352,-0.008196334354579449,0.003244930412620306,0.9954962730407715,0.09160070866346359,-0.004178721457719803,0.004167591687291861,0.9957783222198486,0.08865471184253693,-0.0003860106226056814,0.0036524254828691483,0.9960556626319885,0.08571775257587433,0.0026187882758677006,0.0018317130161449313,0.9963143467903137,0.08358155936002731,0.00262679741717875,-0.002999883843585849,0.9964929819107056,0.08184529840946198,0.0009368388564325869,-0.00803848635405302,0.9966121912002563,0.07928358763456345,0.0006534955464303493,-0.01211174950003624,0.9967783093452454,0.0751294419169426,0.004187627229839563,-0.01734744943678379,0.9970141053199768,0.07166755944490433,0.007907700724899769,-0.018631847575306892,0.9972231388092041,0.06840048730373383,0.011714059859514236,-0.019397560507059097,0.9974005818367004,0.06523123383522034,0.015524771995842457,-0.019995147362351418,0.9975490570068359,0.06201684847474098,0.01912764273583889,-0.020585961639881134,0.9976794719696045,0.05880027264356613,0.022383414208889008,-0.020823316648602486,0.9978015422821045,0.05529513582587242,0.02460257150232792,-0.020763985812664032,0.9979509115219116,0.051573656499385834,0.0262790285050869,-0.02051982469856739,0.9981124997138977,0.04765380173921585,0.027856143191456795,-0.02005276270210743,0.9982740879058838,0.0436820462346077,0.029420524835586548,-0.01953401230275631,0.9984211325645447],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999997615814209,0.9999997615814209,0.9999999403953552,0.9999998807907104,0.9999997615814209],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,1.7000000476837158],"values":[4.366399765014648,7.562901496887207,-0.17679868638515472,4.366399765014648,7.56290340423584,-0.1767987459897995],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.5298227071762085,0.5134139060974121,-0.4447128474712372,0.5078627467155457,0.534051239490509,0.5084609985351562,-0.45456114411354065,0.49963074922561646,0.5362854599952698,0.5043455958366394,-0.4646063446998596,0.49211207032203674,0.5351476669311523,0.5039048194885254,-0.47473958134651184,0.48406535387039185,0.5279479622840881,0.5099490284919739,-0.484237402677536,0.4761691689491272,0.5212697386741638,0.5148343443870544,-0.4986671805381775,0.46320024132728577,0.5140466690063477,0.5200307369232178,-0.501984715461731,0.46188268065452576,0.5254513621330261,0.5099513530731201,-0.4826865792274475,0.4804832637310028,0.5460657477378845,0.4944593906402588,-0.434013307094574,0.5186082720756531,0.5685389637947083,0.47419118881225586,-0.386029452085495,0.550352156162262,0.5971379280090332,0.44169846177101135,-0.34279027581214905,0.5751726627349854,0.6241691708564758,0.4017977714538574,-0.3002529740333557,0.5990155339241028,0.6385180950164795,0.38110053539276123,-0.2706056833267212,0.6114161014556885,0.6395227909088135,0.3879859447479248,-0.26265665888786316,0.6094990372657776,0.632735550403595,0.4081948697566986,-0.27712514996528625,0.5968453884124756,0.6181347370147705,0.44262129068374634,-0.3027687072753906,0.5747408866882324,0.612506628036499,0.4561419188976288,-0.3057100772857666,0.5686049461364746,0.6065524816513062,0.4693564474582672,-0.31560006737709045,0.5587444305419922,0.6034037470817566,0.4836532771587372,-0.3407147526741028,0.5346933603286743,0.5990428924560547,0.4967442750930786,-0.4087221920490265,0.4768006205558777,0.5879791975021362,0.5067858695983887,-0.49973005056381226,0.38434144854545593,0.5732464790344238,0.5123972296714783,-0.5613647699356079,0.3061162233352661,0.5663658976554871,0.5213313698768616,-0.5643051862716675,0.29833367466926575,0.5667058229446411,0.5215414762496948,-0.5549213886260986,0.3144857585430145,0.5693621635437012,0.5181694626808167,-0.5430188775062561,0.33534714579582214,0.5716963410377502,0.5144014954566956,-0.5278935432434082,0.3603925406932831,0.5703356266021729,0.5121278166770935,-0.5095928311347961,0.39084210991859436,0.560563325881958,0.5159439444541931,-0.4840724766254425,0.43040040135383606,0.5498334169387817,0.518187403678894,-0.4591072201728821,0.4673173129558563,0.5460173487663269,0.5120571255683899,-0.4519437253475189,0.48519009351730347,0.5392882823944092,0.5096511244773865,-0.4535611867904663,0.49366605281829834,0.5054263472557068,0.5349950194358826,-0.4326745867729187,0.5206892490386963,0.45929205417633057,0.567241907119751,-0.3892197012901306,0.5619568824768066,0.4415350556373596,0.5778186321258545,-0.3722720146179199,0.5767027139663696,0.4359217882156372,0.5809141397476196,-0.3683314919471741,0.5803816914558411,0.43595126271247864,0.5814582109451294,-0.373937726020813,0.5762147903442383,0.43901392817497253,0.5795858502388,-0.3773419260978699,0.5735504627227783,0.45488792657852173,0.5696709156036377,-0.39125776290893555,0.56166672706604,0.4792117178440094,0.5516435503959656,-0.39747563004493713,0.5550304651260376,0.5012088418006897,0.5350027680397034,-0.3986848294734955,0.551010251045227,0.5077697038650513,0.5329919457435608,-0.4214962124824524,0.529556930065155,0.5149974822998047,0.5275538563728333,-0.4413878917694092,0.5115089416503906,0.516454815864563,0.5281496644020081,-0.4553018808364868,0.4970238208770752,0.5148429870605469,0.530607283115387,-0.45466485619544983,0.49666154384613037,0.5149310231208801,0.5308013558387756,-0.45258665084838867,0.4982583522796631,0.5166337490081787,0.5289242267608643,-0.4503802955150604,0.5004861950874329,0.5211777091026306,0.5243239998817444,-0.44881677627563477,0.5020177364349365,0.5262773633003235,0.5188645720481873,-0.44817906618118286,0.5029386878013611,0.5282174348831177,0.5162435173988342,-0.4473051428794861,0.5043779015541077,0.5289570689201355,0.5148676037788391,-0.44605425000190735,0.5061139464378357,0.5298224091529846,0.5134141445159912,-0.44471275806427,0.5078628659248352],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999997615814209,0.9999998807907104,0.9999998211860657,0.9999998807907104,0.9999997615814209,0.9999999403953552],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000003174408675477025,17.630796432495117,-0.000011906789040949661,0.000003702555659401696,17.63079833984375,-0.000012033097846142482],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.38447335362434387,0.15783919394016266,0.22161303460597992,0.8821308016777039,0.38487508893013,0.11716759204864502,0.1934039145708084,0.8948395848274231,0.38872087001800537,0.07660845667123795,0.16856417059898376,0.9025593400001526,0.3901370167732239,0.036783382296562195,0.14898893237113953,0.9078779816627502,0.3865906894207001,0.0014315890148282051,0.13684670627117157,0.9120408892631531,0.36465930938720703,-0.025446107611060143,0.13391239941120148,0.9211099743843079,0.3371424973011017,-0.04245779290795326,0.14641113579273224,0.9290295839309692,0.3036918640136719,-0.03694961220026016,0.15926064550876617,0.9386383891105652,0.27174484729766846,-0.006811272352933884,0.17538651823997498,0.9462282657623291,0.22971998155117035,0.025962956249713898,0.19802699983119965,0.9525439739227295,0.1798640638589859,0.04240579158067703,0.20087279379367828,0.9620295166969299,0.1389259248971939,0.035586751997470856,0.1965240240097046,0.9699543714523315,0.1740657091140747,-0.02287687174975872,0.21488095819950104,0.9607309699058533,0.2883547842502594,-0.0971570685505867,0.21302030980587006,0.9284580945968628,0.4086785912513733,-0.10646915435791016,0.19793887436389923,0.8845712542533875,0.4686164855957031,0.013576243072748184,0.21788038313388824,0.8560037612915039,0.5048332810401917,0.20200416445732117,0.18305906653404236,0.8190403580665588,0.49635276198387146,0.32889503240585327,0.21931684017181396,0.7728920578956604,0.45043495297431946,0.44602033495903015,0.2388623058795929,0.735607922077179,0.3992610573768616,0.5389226675033569,0.2167290896177292,0.7093528509140015,0.3875836730003357,0.5709420442581177,0.1528763473033905,0.707412838935852,0.45250779390335083,0.4841359555721283,0.0838356614112854,0.7441912889480591,0.4709356725215912,0.3894471526145935,0.048117607831954956,0.7900855541229248,0.4516699016094208,0.31004175543785095,0.009293459355831146,0.8365297913551331,0.4062725007534027,0.20852364599704742,-0.0011097609531134367,0.8896399736404419,0.3632318079471588,0.08707624673843384,0.02522025629878044,0.9272779226303101,0.34093964099884033,-0.017747700214385986,0.040332674980163574,0.9390519261360168,0.3220497965812683,-0.08463030308485031,0.04427488520741463,0.9418924450874329,0.29213613271713257,-0.09572912007570267,0.03491605445742607,0.9509328603744507,0.2429182231426239,-0.10384929925203323,0.019997531548142433,0.9642645716667175,0.18824224174022675,-0.10736740380525589,0.017049184069037437,0.9760873317718506,0.17483137547969818,-0.09825589507818222,0.04450692608952522,0.9786720275878906,0.18924199044704437,-0.0877913236618042,0.05165558308362961,0.9766328930854797,0.19373269379138947,-0.11474486440420151,0.04269988462328911,0.9733847975730896,0.20342355966567993,-0.15151813626289368,0.055444713681936264,0.9657054543495178,0.22498321533203125,-0.17852309346199036,0.1023600772023201,0.9523835778236389,0.23723919689655304,-0.18219849467277527,0.13607649505138397,0.9444599151611328,0.22251494228839874,-0.1684340238571167,0.1800777018070221,0.9432333707809448,0.23183046281337738,-0.18802785873413086,0.2330668717622757,0.9255160689353943,0.26930180191993713,-0.18097932636737823,0.2879177927970886,0.9010140895843506,0.3151748478412628,-0.1328110545873642,0.3316534161567688,0.8792224526405334,0.34457680583000183,-0.09025689959526062,0.32068052887916565,0.8776585459709167,0.3546120822429657,-0.04246613010764122,0.3045633137226105,0.8829994797706604,0.3569272756576538,0.004432540852576494,0.2759966254234314,0.8924175500869751,0.3676411807537079,0.04807252809405327,0.2546623945236206,0.8931270837783813,0.3785586655139923,0.0831693783402443,0.24106372892856598,0.8897553086280823,0.38621142506599426,0.11083272844552994,0.22863149642944336,0.8867268562316895,0.3881085515022278,0.1279856115579605,0.2177894115447998,0.886317789554596,0.3874785304069519,0.13910990953445435,0.21709993481636047,0.8850855827331543,0.38587209582328796,0.14829279482364655,0.2196846455335617,0.8836575150489807,0.3844733238220215,0.15783922374248505,0.22161298990249634,0.8821308016777039],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,1.0000001192092896,0.9999998211860657,1.0000001192092896,1,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[4.21992552901429e-7,17.82779884338379,-1.4927697122857353e-7,0.000001559614702273393,17.827804565429688,-0.000002689106167963473],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.05372462794184685,-0.2845951020717621,0.7204499840736389,0.6301357746124268,0.08034376055002213,-0.2846415042877197,0.6964906454086304,0.6537774205207825,0.1300605833530426,-0.3070954382419586,0.6700318455696106,0.6631998419761658,0.17712484300136566,-0.3343038558959961,0.6439377665519714,0.6649901866912842,0.20772907137870789,-0.3714652955532074,0.6165468096733093,0.662368655204773,0.21571072936058044,-0.4196358323097229,0.5859130620956421,0.6588479280471802,0.20524448156356812,-0.47803881764411926,0.5361462831497192,0.6647562384605408,0.18290019035339355,-0.5323888063430786,0.4690943658351898,0.6804852485656738,0.15578152239322662,-0.569126307964325,0.4049485921859741,0.6984583139419556,0.13653382658958435,-0.5889129638671875,0.3600870966911316,0.7105472683906555,0.12984679639339447,-0.5889893770217896,0.3301900327205658,0.7260894775390625,0.1344892978668213,-0.5679715871810913,0.2749878168106079,0.7640043497085571,0.1161046177148819,-0.5228250622749329,0.250189870595932,0.8065845966339111,0.0927644670009613,-0.47358348965644836,0.2527562379837036,0.8385867476463318,0.09358938783407211,-0.4375936985015869,0.2704690396785736,0.8524079918861389,0.10075928270816803,-0.42445194721221924,0.2570975422859192,0.8623160719871521,0.04018557816743851,-0.4071631133556366,0.361591637134552,0.8377677798271179,-0.03752432391047478,-0.4053416848182678,0.42318838834762573,0.8094452619552612,-0.09739716351032257,-0.34222647547721863,0.563152015209198,0.7458248138427734,-0.1732277125120163,-0.29540523886680603,0.6385541558265686,0.689185380935669,-0.21816527843475342,-0.2844982445240021,0.6733775734901428,0.6465503573417664,-0.16439461708068848,-0.31416040658950806,0.6718042492866516,0.6503512859344482,-0.09982476383447647,-0.3328585624694824,0.6037049889564514,0.7174820899963379,-0.0887388363480568,-0.2846953272819519,0.5353460907936096,0.7902395725250244,-0.0777926966547966,-0.20964466035366058,0.4120880663394928,0.883278489112854,-0.04461018368601799,-0.13025251030921936,0.24586261808872223,0.9594768285751343,0.0023629702627658844,-0.08031504601240158,0.10215986520051956,0.9915176630020142,0.029995493590831757,-0.11106237024068832,0.021059369668364525,0.9931374192237854,0.009861276485025883,-0.16819997131824493,0.0294734425842762,0.9852628111839294,-0.01089543104171753,-0.2469286322593689,0.06279182434082031,0.9669357538223267,-0.01389331929385662,-0.3341768682003021,0.10968489944934845,0.9360032081604004,0.0024288573767989874,-0.3944225013256073,0.16819046437740326,0.9034029841423035,0.04003814980387688,-0.42801231145858765,0.2492688000202179,0.8677945733070374,0.10047901421785355,-0.4451654851436615,0.3545972406864166,0.8160836100578308,0.162966787815094,-0.44438663125038147,0.4427705407142639,0.7615225315093994,0.19236446917057037,-0.434452623128891,0.4857046604156494,0.7337151169776917,0.20040462911128998,-0.41837403178215027,0.5138047337532043,0.7216688394546509,0.20231404900550842,-0.41180887818336487,0.525534987449646,0.7164463996887207,0.19871720671653748,-0.40168917179107666,0.5274553298950195,0.7217674255371094,0.1797434240579605,-0.38678842782974243,0.5196467041969299,0.7403069138526917,0.1528349667787552,-0.3654024302959442,0.5289592146873474,0.7505496144294739,0.146613210439682,-0.34743356704711914,0.5600861310958862,0.7376300692558289,0.15224973857402802,-0.339717298746109,0.5877839922904968,0.7182772159576416,0.15489983558654785,-0.33225488662719727,0.6229274272918701,0.6910674571990967,0.13132308423519135,-0.3178148567676544,0.6544392108917236,0.6733924150466919,0.09732342511415482,-0.30203551054000854,0.6793802976608276,0.6616231203079224,0.06824033707380295,-0.28984853625297546,0.699032723903656,0.6501418352127075,0.05779376998543739,-0.2872496247291565,0.7127847671508789,0.6372482776641846,0.055288512259721756,-0.28641143441200256,0.7171130776405334,0.6329775452613831,0.054698362946510315,-0.2854919731616974,0.7184921503067017,0.6318793296813965,0.05372470244765282,-0.2845953106880188,0.7204499244689941,0.6301357746124268],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999997615814209,0.9999999403953552,0.9999997019767761,0.9999998807907104,1,0.9999998211860657],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000002641373384904,18.751102447509766,0.0000017390580069331918,0.000013946715625934303,18.751100540161133,0.000004474665729503613],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.055037714540958405,0.05196232721209526,-0.013945184648036957,0.9970337748527527,0.0550377257168293,0.05196233093738556,-0.013945169746875763,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.0000001192092896,0.9999998807907104,0.9999999403953552,1.0000001192092896,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,1.7000000476837158],"values":[-4.5131988525390625,8.246386528015137,-0.08130056411027908,-4.513198375701904,8.246395111083984,-0.08129743486642838],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.43135538697242737,-0.5555515289306641,0.6031647324562073,0.3761480748653412,0.4600326716899872,-0.5387779474258423,0.5921521186828613,0.38398462533950806,0.4929754137992859,-0.516934871673584,0.5791006684303284,0.39293262362480164,0.5309114456176758,-0.489018052816391,0.5644128322601318,0.4005403518676758,0.5700528621673584,-0.45694348216056824,0.5507808327674866,0.4035874605178833,0.6048600077629089,-0.4244005084037781,0.5373197197914124,0.40659093856811523,0.626990795135498,-0.40098869800567627,0.5262530446052551,0.411276638507843,0.6370351910591125,-0.389853298664093,0.5171016454696655,0.41809865832328796,0.6394208073616028,-0.3924582004547119,0.5105950832366943,0.42001238465309143,0.6379837393760681,-0.40347737073898315,0.49781322479248047,0.4270419776439667,0.6325011253356934,-0.416525661945343,0.4816109836101532,0.4410211145877838,0.6215456128120422,-0.4312787652015686,0.47308605909347534,0.4515187740325928,0.6296162605285645,-0.42589694261550903,0.43995267152786255,0.4781598448753357,0.6361145973205566,-0.4203132092952728,0.4043254852294922,0.5051890015602112,0.6508477926254272,-0.40292680263519287,0.36939769983291626,0.5268704891204834,0.6615778207778931,-0.39017102122306824,0.33449992537498474,0.5460689663887024,0.6817899346351624,-0.36234456300735474,0.3117782473564148,0.5537718534469604,0.7140308022499084,-0.32318174839019775,0.3032916486263275,0.5419666171073914,0.7276191115379333,-0.3146137297153473,0.2992611229419708,0.5310662984848022,0.7152486443519592,-0.34192323684692383,0.3008299767971039,0.5301030278205872,0.6834824681282043,-0.37823396921157837,0.30080267786979675,0.547091007232666,0.6493543386459351,-0.4129765033721924,0.3255476951599121,0.5493706464767456,0.6169098615646362,-0.44257935881614685,0.35979345440864563,0.5423048734664917,0.6231002807617188,-0.4327850937843323,0.3508681654930115,0.54893958568573,0.6171192526817322,-0.4460521936416626,0.3208993971347809,0.5632272958755493,0.5571163296699524,-0.5098491311073303,0.29459720849990845,0.5855662822723389,0.49521806836128235,-0.5613517165184021,0.28711995482444763,0.5976667404174805,0.4626753032207489,-0.5841208100318909,0.29304447770118713,0.5990487933158875,0.4371128976345062,-0.6006944179534912,0.30252933502197266,0.5971386432647705,0.40759408473968506,-0.6190969944000244,0.312070369720459,0.5943048000335693,0.37789836525917053,-0.6365877985954285,0.32111045718193054,0.5906241536140442,0.35157132148742676,-0.6514971256256104,0.32739976048469543,0.5871614217758179,0.32603174448013306,-0.6634514927864075,0.3297843337059021,0.5871777534484863,0.2999383509159088,-0.6733145713806152,0.3311411440372467,0.5890925526618958,0.27806562185287476,-0.6793670058250427,0.33311381936073303,0.5917560458183289,0.2700138986110687,-0.6749331951141357,0.33692798018455505,0.5983620882034302,0.26546356081962585,-0.668505072593689,0.3423367142677307,0.6045127511024475,0.2637116014957428,-0.6610649228096008,0.34937918186187744,0.609412431716919,0.2709428369998932,-0.6503264904022217,0.3630430996417999,0.6098074913024902,0.30415454506874084,-0.6264166235923767,0.39728137850761414,0.5977120995521545,0.3446205258369446,-0.5981108546257019,0.43417981266975403,0.5787814259529114,0.3743010461330414,-0.5734359622001648,0.467303067445755,0.5591939091682434,0.38650408387184143,-0.5624868273735046,0.4907282292842865,0.5416723489761353,0.39385759830474854,-0.5559503436088562,0.510855495929718,0.5242347121238708,0.39874765276908875,-0.5519276857376099,0.5289244651794434,0.5065719485282898,0.40360158681869507,-0.5486900806427002,0.5458520650863647,0.4879452586174011,0.4067729413509369,-0.5484861731529236,0.560295581817627,0.46879372000694275,0.4116974174976349,-0.5499481558799744,0.5725752115249634,0.44745954871177673,0.4179149568080902,-0.5519112348556519,0.5835191011428833,0.42455461621284485,0.4245910942554474,-0.5537574291229248,0.5937597751617432,0.40065503120422363,0.4313555359840393,-0.5555514693260193,0.603164792060852,0.376147985458374],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999998807907104,0.9999999403953552,1,0.9999998807907104,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.0000013133010270394152,17.92139434814453,0.000007771351192786824,-2.86967576812458e-7,17.921396255493164,-0.000001652859623391123],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.43107542395591736,-0.19461344182491302,-0.10335654020309448,0.8749954700469971,0.42205917835235596,-0.20970244705677032,-0.08659962564706802,0.8777194619178772,0.4038050174713135,-0.20589210093021393,-0.08766240626573563,0.8870542645454407,0.38957875967025757,-0.1961061656475067,-0.0949864387512207,0.8948454260826111,0.3753999173641205,-0.17710362374782562,-0.1095571517944336,0.9031646847724915,0.35149213671684265,-0.15587767958641052,-0.14231795072555542,0.9120861291885376,0.33088263869285583,-0.1342657059431076,-0.18773214519023895,0.9150115251541138,0.31868278980255127,-0.11008204519748688,-0.23866502940654755,0.9106932878494263,0.31866970658302307,-0.06967867910861969,-0.29653218388557434,0.8975874185562134,0.3221886456012726,-0.01971854455769062,-0.37304437160491943,0.869852602481842,0.29012152552604675,0.017836546525359154,-0.4640938341617584,0.8367366790771484,0.24376076459884644,0.05713457986712456,-0.5700661540031433,0.7825222015380859,0.15938259661197662,0.09745340049266815,-0.6235889196395874,0.7591027021408081,0.09467779099941254,0.12778130173683167,-0.6548550128936768,0.7388321757316589,0.0556364543735981,0.13089407980442047,-0.675011157989502,0.7239691019058228,0.029609130695462227,0.1124725267291069,-0.6947287917137146,0.7098064422607422,0.019391141831874847,0.04640259966254234,-0.6883127689361572,0.7236687541007996,-0.029502280056476593,-0.04696813225746155,-0.6313297152519226,0.7735285758972168,-0.09912422299385071,-0.1054903045296669,-0.5710320472717285,0.8080647587776184,-0.07996035367250443,-0.1482468992471695,-0.5919094085693359,0.7882083654403687,-0.0013013913994655013,-0.14746685326099396,-0.663952112197876,0.7330889701843262,0.049560610204935074,-0.13214808702468872,-0.7299715876579285,0.6687466502189636,0.10432212799787521,-0.11269691586494446,-0.7509870529174805,0.642210841178894,0.20265533030033112,-0.018981346860527992,-0.657025933265686,0.7258701920509338,0.19154629111289978,0.12949244678020477,-0.4685577154159546,0.8526402711868286,0.09277807176113129,0.1435505449771881,-0.24363800883293152,0.9546863436698914,0.07866733521223068,0.13226699829101562,-0.10384573042392731,0.9826154112815857,0.08411147445440292,0.12100951373577118,0.01140872947871685,0.9890155792236328,0.09206151217222214,0.11739563941955566,0.08609319478273392,0.9850537776947021,0.09423607587814331,0.12036649882793427,0.11493045836687088,0.981540858745575,0.09490520507097244,0.1252366453409195,0.13195794820785522,0.978721559047699,0.10170984268188477,0.13603396713733673,0.1307399570941925,0.9767584204673767,0.12352467328310013,0.1505926251411438,0.1210612803697586,0.9733487367630005,0.15254564583301544,0.16127820312976837,0.11333785951137543,0.9684388041496277,0.19181028008460999,0.16101354360580444,0.11408506333827972,0.9613886475563049,0.2533800005912781,0.13516473770141602,0.1329447627067566,0.9486067295074463,0.2924415171146393,0.11155220866203308,0.12377236783504486,0.941655158996582,0.31146785616874695,0.09355434775352478,0.08692023158073425,0.9416369795799255,0.3316556513309479,0.060132041573524475,0.06031404063105583,0.9395482540130615,0.3527764081954956,-0.01531579066067934,0.040088310837745667,0.9347230195999146,0.3709234893321991,-0.08007720112800598,0.014979692175984383,0.9250832796096802,0.38910579681396484,-0.1247277706861496,-0.01504579558968544,0.9125860333442688,0.4077197313308716,-0.15294677019119263,-0.03213144838809967,0.89963299036026,0.4256945550441742,-0.17536544799804688,-0.04624171555042267,0.8865059614181519,0.44290387630462646,-0.19418299198150635,-0.059175051748752594,0.8732854127883911,0.45688462257385254,-0.20979270339012146,-0.07211887091398239,0.8614187836647034,0.46649500727653503,-0.2190888375043869,-0.08218257129192352,0.853011429309845,0.46379005908966064,-0.21845488250255585,-0.08861328661441803,0.8540046811103821,0.4527713358402252,-0.21104638278484344,-0.09328880906105042,0.8612518906593323,0.4409559667110443,-0.20232261717319489,-0.09810519218444824,0.8689066767692566,0.4310756325721741,-0.1946135014295578,-0.10335642844438553,0.8749953508377075],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999998807907104,0.9999997615814209,0.9999999403953552,0.9999998807907104,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.000004441968485480174,17.20370101928711,-0.000006082174877519719,-0.000008713177521713078,17.203702926635742,0.000008789989806246012],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[-0.08050931990146637,0.14672638475894928,-0.6004754304885864,0.7819327712059021,-0.08966201543807983,0.13503269851207733,-0.64165198802948,0.749673068523407,-0.09497468918561935,0.12940824031829834,-0.6662954092025757,0.7282058596611023,-0.09884209930896759,0.13046392798423767,-0.6901784539222717,0.7048851847648621,-0.09766478836536407,0.14084535837173462,-0.7117235064506531,0.6812297105789185,-0.07613996416330338,0.17746619880199432,-0.716650664806366,0.6701644062995911,-0.06044689193367958,0.20170791447162628,-0.717658281326294,0.6637971997261047,-0.053863197565078735,0.210171639919281,-0.7158050537109375,0.6637392640113831,-0.0542733334004879,0.20975060760974884,-0.7130745053291321,0.6667712330818176,-0.06383706629276276,0.19526974856853485,-0.7039816975593567,0.6798561215400696,-0.05884639173746109,0.2009199857711792,-0.6939041018486023,0.6889596581459045,-0.04449494555592537,0.21299642324447632,-0.6530507802963257,0.7253808379173279,-0.03294188901782036,0.22849515080451965,-0.6140231490135193,0.7547717690467834,-0.02112559974193573,0.2415134161710739,-0.5875688791275024,0.7720024585723877,0.009366997517645359,0.266735315322876,-0.5800157785415649,0.769640326499939,0.022927885875105858,0.2721048593521118,-0.5733835101127625,0.7724407315254211,-0.004357507918030024,0.23350033164024353,-0.5765622854232788,0.7829652428627014,-0.019836541265249252,0.20189300179481506,-0.5590980648994446,0.8038999438285828,-0.04914766177535057,0.1380680501461029,-0.5571916103363037,0.8173488974571228,-0.1184108704328537,-0.0038869765121489763,-0.4562486708164215,0.8819302320480347,-0.1193382665514946,-0.17396052181720734,-0.29595425724983215,0.9316153526306152,-0.044442109763622284,-0.1881844401359558,-0.16552788019180298,0.9670636653900146,-0.010738649405539036,-0.0837668776512146,-0.058776289224624634,0.9946925044059753,-0.09362328052520752,-0.20702841877937317,0.0023983633145689964,0.9738419651985168,-0.10422533750534058,-0.34065479040145874,0.01345740631222725,0.9342966675758362,0.01083306409418583,-0.15224689245224,-0.050057172775268555,0.987014651298523,0.04405704513192177,-0.0025388270150870085,-0.039977382868528366,0.9982256293296814,0.03279547765851021,0.014556076377630234,-0.04061993956565857,0.9985302686691284,0.020762071013450623,0.05143143981695175,-0.0408066026866436,0.9976264834403992,0.02434113249182701,0.16859135031700134,-0.03429368883371353,0.9847885370254517,0.029418854042887688,0.30127063393592834,-0.025512950494885445,0.9527432322502136,0.033762674778699875,0.4262520372867584,-0.03762423247098923,0.9031908512115479,0.04707305133342743,0.5192975401878357,-0.09369292110204697,0.8481367230415344,0.07756335288286209,0.5931423306465149,-0.1658715307712555,0.7839979529380798,0.13094313442707062,0.637363612651825,-0.2586260139942169,0.7139566540718079,0.21122850477695465,0.613383412361145,-0.4006471335887909,0.64701247215271,0.2634293735027313,0.5629276037216187,-0.5035905241966248,0.6000950932502747,0.27797332406044006,0.5033534169197083,-0.5790359973907471,0.5779995918273926,0.2952812612056732,0.44356635212898254,-0.6458603739738464,0.5467379093170166,0.30606481432914734,0.37421077489852905,-0.703040599822998,0.5215597152709961,0.27207857370376587,0.345716655254364,-0.712350070476532,0.5468186736106873,0.20567983388900757,0.3300471007823944,-0.6902587413787842,0.6101701259613037,0.150239035487175,0.30653250217437744,-0.6736977100372314,0.6554368138313293,0.09982822835445404,0.2787805497646332,-0.6562951803207397,0.6939685344696045,0.053247690200805664,0.24779507517814636,-0.6371403932571411,0.727883517742157,0.012144212611019611,0.21620453894138336,-0.6167775988578796,0.7567651867866516,-0.021851733326911926,0.18806931376457214,-0.6001583337783813,0.7771502137184143,-0.04248357191681862,0.17402099072933197,-0.5943372845649719,0.7840121388435364,-0.05599009245634079,0.16633568704128265,-0.5967618227005005,0.7829897999763489,-0.06845516711473465,0.15716339647769928,-0.59989994764328,0.7814944982528687,-0.08050915598869324,0.1467263400554657,-0.6004754900932312,0.7819327712059021],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.0000001192092896,1,0.9999999403953552,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.000007552434908575378,19.45049476623535,-0.000007778955477988347,0.0000068158842623233795,19.450496673583984,0.0000010524083791096928],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.14937162399291992,-0.14748123288154602,-0.019200153648853302,0.9775319695472717,0.14937160909175873,-0.14748121798038483,-0.019200151786208153,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1,0.9999998807907104,0.9999998807907104,0.9999999403953552,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.14690029621124268,6.40949821472168,0.2580966651439667,0.14690084755420685,6.409499168395996,0.2580986022949219],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.0202596727758646,0.012515287846326828,-0.011196224950253963,0.999653697013855,0.0202596727758646,0.012515285983681679,-0.011196223087608814,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.000000238418579,1,1.0000001192092896,1.0000003576278687,1],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,1.7000000476837158],"values":[-2.009107902267715e-7,6.8159003257751465,-0.000002612949174363166,-9.266649385608616e-7,6.815901279449463,0.0000032756965993030462],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158],"values":[0.06515958160161972,0.3020924925804138,-0.05072396621108055,0.9496954679489136,0.06042582169175148,0.30767393112182617,-0.06238668039441109,0.9475196003913879,0.051941677927970886,0.30676040053367615,-0.07714542001485825,0.9472321271896362,0.04887143522500992,0.2890799939632416,-0.08093788474798203,0.952624499797821,0.048748768866062164,0.26286524534225464,-0.07822997868061066,0.9604194164276123,0.04977590963244438,0.2329869419336319,-0.07246040552854538,0.9684982895851135,0.05131176859140396,0.20093940198421478,-0.0648103654384613,0.9761096835136414,0.053090695291757584,0.1637742817401886,-0.053204912692308426,0.9836302995681763,0.05507253110408783,0.10414966195821762,-0.02608860656619072,0.9926929473876953,0.044037383049726486,0.029088683426380157,-0.00731023121625185,0.9985795617103577,0.02383418008685112,-0.026132483035326004,-0.0018849072512239218,0.9993726015090942,-0.0011566634057089686,-0.07918965816497803,-0.0028684765566140413,0.9968547821044922,-0.01050418522208929,-0.1493055820465088,-0.011761214584112167,0.9886653423309326,-0.025632819160819054,-0.2241702526807785,-0.019853709265589714,0.9740105271339417,-0.0419660285115242,-0.27026301622390747,-0.023579958826303482,0.9615824222564697,-0.04547058790922165,-0.3191923499107361,-0.03187517449259758,0.9460616707801819,-0.03223026916384697,-0.41783618927001953,-0.04244176298379898,0.9069579839706421,-0.03264356777071953,-0.5167422294616699,-0.04441725090146065,0.8543646931648254,-0.030881425365805626,-0.5694420337677002,-0.040811121463775635,0.8204368352890015,-0.008526962250471115,-0.566165030002594,-0.050435520708560944,0.8227033019065857,0.03478292003273964,-0.49828067421913147,-0.0557013675570488,0.8645252585411072,0.08360841870307922,-0.41596341133117676,-0.060348425060510635,0.9035165309906006,0.1199585422873497,-0.33096927404403687,-0.0601147785782814,0.9340533018112183,0.1367986500263214,-0.2697826623916626,-0.05454589053988457,0.9515924453735352,0.14736363291740417,-0.21693894267082214,-0.04669315740466118,0.9638678431510925,0.1545664519071579,-0.16587820649147034,-0.0376393124461174,0.9732301831245422,0.1540369838476181,-0.12575994431972504,-0.028684405609965324,0.9796091914176941,0.1477254331111908,-0.09248031675815582,-0.021578362211585045,0.9844587445259094,0.13725626468658447,-0.060449227690696716,-0.01810024306178093,0.9885236620903015,0.12474630028009415,-0.03002277947962284,-0.017157578840851784,0.991585910320282,0.11094691604375839,0.0037791363429278135,-0.019679568707942963,0.9936243295669556,0.09456595033407211,0.05748431384563446,-0.032956548035144806,0.9933109879493713,0.08194996416568756,0.11429066210985184,-0.052743151783943176,0.988655686378479,0.07612935453653336,0.1545242965221405,-0.06728740781545639,0.9827507138252258,0.07234285771846771,0.18149429559707642,-0.07577665150165558,0.9777956008911133,0.06864029169082642,0.20646680891513824,-0.08361297845840454,0.9724550843238831,0.06443218886852264,0.229739248752594,-0.09076002240180969,0.9668665528297424,0.05815201252698898,0.24824732542037964,-0.09497646987438202,0.9622739553451538,0.04682445526123047,0.25739938020706177,-0.09248635917901993,0.9607285261154175,0.032841019332408905,0.26109111309051514,-0.08504815399646759,0.9609993100166321,0.022287482395768166,0.267963707447052,-0.07879804074764252,0.9599425196647644,0.022666729986667633,0.2802935540676117,-0.07749789953231812,0.9565123319625854,0.025508049875497818,0.2873069941997528,-0.07777901738882065,0.954334557056427,0.029055044054985046,0.29272031784057617,-0.07834980636835098,0.9525397419929504,0.03295592591166496,0.29728391766548157,-0.0789962038397789,0.9509446620941162,0.036952704191207886,0.301327109336853,-0.0789690762758255,0.9495263695716858,0.041392337530851364,0.3039078712463379,-0.07799282670021057,0.9486009478569031,0.04619315266609192,0.30442795157432556,-0.0738176479935646,0.9485467076301575,0.05154850706458092,0.3039182126522064,-0.06747441738843918,0.9489066004753113,0.05817490816116333,0.30303865671157837,-0.05930174142122269,0.9493505954742432,0.06515957415103912,0.30209243297576904,-0.050723955035209656,0.9496954679489136],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1,0.9999998211860657,1,1,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,1.7000000476837158],"values":[0.5746002793312073,51.81690216064453,-20.522706985473633,0.5745996832847595,51.816890716552734,-20.522695541381836],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-0.18743081390857697,9.191021632659613e-8,-0.00524778850376606,0.9822638034820557,-0.18743079900741577,9.919903476429681e-8,-0.005247789900749922,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,1.7000000476837158],"values":[1.0000001192092896,1.000000238418579,1.0000001192092896,1.0000001192092896,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,1.7000000476837158],"values":[-0.5746011137962341,4.330300807952881,20.522706985473633,-0.5746014714241028,4.3303070068359375,20.522705078125],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[0.6296830773353577,-7.674562141346541e-8,0.017631003633141518,0.7766520380973816,0.6296830773353577,-7.434731941202699e-8,0.017631007358431816,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.9999999403953552,1.0000003576278687,1.0000003576278687,1,1.0000003576278687,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,1.7000000476837158],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,1.7000000476837158],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,1.7000000476837158],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"196bd543-f70b-46a8-959e-76c06bfcf65e","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/one-hand-combo-hit1.json b/public/anims/baked/sword_shield/one-hand-combo-hit1.json new file mode 100644 index 0000000..1cf5bb7 --- /dev/null +++ b/public/anims/baked/sword_shield/one-hand-combo-hit1.json @@ -0,0 +1 @@ +{"name":"sword_shield/one-hand-sword-combo-hit1","duration":1.5222222010294597,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[-0.5590595006942749,56.298553466796875,0.05576476454734802,-0.5830438137054443,56.26823425292969,0.00898239016532898,-0.6239629983901978,56.23336410522461,-0.10380056500434875,-0.6637053489685059,56.21349334716797,-0.2244238555431366,-0.7141396999359131,56.19441604614258,-0.34516286849975586,-0.7737370729446411,56.16643524169922,-0.47111305594444275,-0.8478822708129883,56.144195556640625,-0.6016842722892761,-0.9338604211807251,56.12373733520508,-0.7418932318687439,-1.0166754722595215,56.114463806152344,-0.9054592847824097,-1.0991685390472412,56.251434326171875,-1.1213624477386475,-1.1531919240951538,56.45637512207031,-1.414699673652649,-1.1159791946411133,56.69541931152344,-1.7275384664535522,-1.0078744888305664,56.96617126464844,-1.982285499572754,-0.8618897795677185,57.32954788208008,-2.14268159866333,-0.6719604134559631,57.75862503051758,-2.3482024669647217,-0.5176432132720947,58.20808792114258,-2.5481650829315186,-0.40313923358917236,58.6833610534668,-2.7450201511383057,-0.3385993242263794,59.2278938293457,-2.976700782775879,-0.2878805696964264,59.768836975097656,-3.184596538543701,-0.21636344492435455,60.222198486328125,-3.2928733825683594,0.004610493779182434,60.5987434387207,-3.3430166244506836,0.13491761684417725,60.84749221801758,-3.324899673461914,0.17532069981098175,60.92778778076172,-3.1922411918640137,0.3529963493347168,60.8262825012207,-2.9726598262786865,0.767279326915741,60.4658203125,-2.6867713928222656,1.461098551750183,59.92803955078125,-2.3271420001983643,2.2526755332946777,59.47617721557617,-1.4827978610992432,2.860767126083374,58.94089889526367,-0.3461868464946747,3.0225493907928467,58.44818115234375,1.087772011756897,3.643036127090454,58.190547943115234,2.194953203201294,3.7966196537017822,57.942588806152344,3.587484121322632,3.8630590438842773,58.05365753173828,5.100348949432373,3.885833263397217,58.403289794921875,6.357968807220459,3.9838387966156006,58.75883865356445,7.494500637054443,4.196979522705078,58.88621520996094,8.604361534118652,4.463954448699951,58.8189582824707,9.721515655517578,4.72943639755249,58.56892395019531,10.74023151397705,5.084789276123047,58.08723831176758,11.687664985656738,5.3352861404418945,57.59356689453125,12.65117359161377,5.611867427825928,57.179622650146484,13.510286331176758,5.841495513916016,56.64195251464844,14.642765045166016,5.978096008300781,56.11042785644531,15.836421012878418,5.943517208099365,55.81796646118164,17.01910400390625,5.851102352142334,55.78116226196289,18.2487735748291,6.1079559326171875,56.62559127807617,19.939271926879883],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.28931355476379395,-0.33466553688049316,0.2663405239582062,0.8563640713691711,0.28641682863235474,-0.34268906712532043,0.2679421603679657,0.8536607027053833,0.2814655900001526,-0.3521296977996826,0.26980918645858765,0.8508729934692383,0.2749410569667816,-0.36244997382164,0.27200600504875183,0.8479682803153992,0.2678745687007904,-0.3712531328201294,0.2740928828716278,0.8457466959953308,0.26103153824806213,-0.3774826228618622,0.275804728269577,0.844571590423584,0.25438329577445984,-0.38342028856277466,0.2774813175201416,0.8433755040168762,0.24853171408176422,-0.3886300027370453,0.27895668148994446,0.8422481417655945,0.24288898706436157,-0.39298877120018005,0.2797199785709381,0.8416184186935425,0.23709207773208618,-0.39601486921310425,0.2803242802619934,0.8416519165039062,0.2314048707485199,-0.39343148469924927,0.280307412147522,0.8444473147392273,0.22797580063343048,-0.382693886756897,0.2783285677433014,0.8509439826011658,0.2255682349205017,-0.36953556537628174,0.27533188462257385,0.8583442568778992,0.2225915789604187,-0.36145004630088806,0.2728957235813141,0.8633278012275696,0.2199268639087677,-0.3567512035369873,0.27047863602638245,0.8667191863059998,0.21791449189186096,-0.3537074625492096,0.26791226863861084,0.8692682981491089,0.21699833869934082,-0.3511776030063629,0.2649453282356262,0.8714298605918884,0.21696333587169647,-0.34727591276168823,0.26177477836608887,0.8739567399024963,0.21805857121944427,-0.3426741063594818,0.2581656873226166,0.8765702843666077,0.2212531864643097,-0.3371150493621826,0.2538761496543884,0.8791742920875549,0.22673405706882477,-0.33009594678878784,0.2471817582845688,0.8823432326316833,0.2337186634540558,-0.3220924437046051,0.23978011310100555,0.8855154514312744,0.2449071854352951,-0.30957964062690735,0.2296198457479477,0.8896379470825195,0.25752463936805725,-0.2894015908241272,0.21539141237735748,0.8964008092880249,0.2710700035095215,-0.263532429933548,0.19953763484954834,0.9040223956108093,0.29164639115333557,-0.22415946424007416,0.17970344424247742,0.9123604893684387,0.31639453768730164,-0.16931647062301636,0.151546910405159,0.9210103154182434,0.3369017541408539,-0.1219068244099617,0.12196594476699829,0.9256134629249573,0.35609951615333557,-0.0794818326830864,0.10080576688051224,0.9255884289741516,0.37489446997642517,-0.03866861015558243,0.0867081806063652,0.9221932888031006,0.39382651448249817,0.0070755621418356895,0.07835585623979568,0.9158116579055786,0.409453421831131,0.04949987307190895,0.07117198407649994,0.9082027077674866,0.4251145124435425,0.0915907770395279,0.06490539014339447,0.8981514573097229,0.4404236376285553,0.1351131796836853,0.06104803830385208,0.8854629397392273,0.45609214901924133,0.17787589132785797,0.0566561259329319,0.870132327079773,0.47029805183410645,0.2233329564332962,0.05025660991668701,0.8523006439208984,0.4818311929702759,0.27066996693611145,0.04407717287540436,0.8322461843490601,0.49171167612075806,0.32270774245262146,0.03909916803240776,0.8078060746192932,0.5005301237106323,0.36513328552246094,0.035797808319330215,0.7841338515281677,0.5093001127243042,0.39059194922447205,0.03254401683807373,0.7661541700363159,0.5162283778190613,0.40695011615753174,0.03254189342260361,0.7528883814811707,0.5198119878768921,0.41944196820259094,0.03328792750835419,0.7434754967689514,0.5190837979316711,0.4339611828327179,0.027854744344949722,0.7358354926109314,0.5123883485794067,0.44890135526657104,0.016488196328282356,0.7318974137306213,0.499032199382782,0.45928528904914856,-0.0044795977883040905,0.7348495125770569],"type":"quaternion"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.6468847990036011,-0.06061564385890961,-0.448291152715683,0.6139225363731384,0.6482616662979126,-0.05424141511321068,-0.45162469148635864,0.6106143593788147,0.6508926749229431,-0.04587846249341965,-0.4576449394226074,0.60398268699646,0.6549796462059021,-0.03650427609682083,-0.4650115370750427,0.5945026278495789,0.6598841547966003,-0.027681199833750725,-0.4721916615962982,0.5837993025779724,0.6651186943054199,-0.020121702924370766,-0.47799721360206604,0.5733506679534912,0.6711938381195068,-0.012458978220820427,-0.4819668233394623,0.5630732774734497,0.6778212785720825,-0.0050193811766803265,-0.48315271735191345,0.5541629195213318,0.6855608820915222,0.002961542923003435,-0.48181527853012085,0.5457574725151062,0.695499062538147,0.007072797976434231,-0.4782731533050537,0.5361769199371338,0.70730060338974,0.0054937792010605335,-0.46833738684654236,0.5294861793518066,0.7197636365890503,-0.0007350030355155468,-0.4496290385723114,0.5289360284805298,0.7321625351905823,-0.007982338778674603,-0.42840901017189026,0.5294713973999023,0.7448391318321228,-0.012856807559728622,-0.4129314124584198,0.523962676525116,0.7581589818000793,-0.015241075307130814,-0.40192922949790955,0.5132402181625366,0.7700322270393372,-0.020334133878350258,-0.392084002494812,0.5028984546661377,0.7816500067710876,-0.025759296491742134,-0.38325026631355286,0.49140501022338867,0.7781476974487305,-0.013460882939398289,-0.38358408212661743,0.4971601366996765,0.7618076205253601,0.0027116399724036455,-0.3883962035179138,0.5184497833251953,0.7499949932098389,0.012537244707345963,-0.3908836543560028,0.5334418416023254,0.7446998357772827,0.02483159862458706,-0.3939865827560425,0.5381264090538025,0.737696647644043,0.042588215321302414,-0.40107014775276184,0.5414172410964966,0.7274109721183777,0.061863306909799576,-0.40563881397247314,0.550002932548523,0.7152422666549683,0.07437393069267273,-0.3994998633861542,0.5685918927192688,0.7007265686988831,0.08641088008880615,-0.38988593220710754,0.591188907623291,0.6775673627853394,0.08645759522914886,-0.36656835675239563,0.6317081451416016,0.6760634183883667,0.06854185461997986,-0.33130112290382385,0.6545836329460144,0.6987981796264648,0.032492496073246,-0.2942754030227661,0.6511738300323486,0.7010397911071777,0.002074055839329958,-0.2671792805194855,0.661176323890686,0.6808992624282837,-0.01181059330701828,-0.25262925028800964,0.6873247623443604,0.6702515482902527,-0.029282134026288986,-0.2365177720785141,0.7028263211250305,0.6698158383369446,-0.04983195289969444,-0.21798807382583618,0.7080570459365845,0.6732216477394104,-0.08215559273958206,-0.18838879466056824,0.7103046774864197,0.6801860928535461,-0.1198774054646492,-0.15174803137779236,0.707070529460907,0.6835033893585205,-0.15198346972465515,-0.11565176397562027,0.7045202851295471,0.6850354075431824,-0.1770416498184204,-0.07910935580730438,0.7022282481193542,0.6853695511817932,-0.19804036617279053,-0.03888857364654541,0.6996687650680542,0.6823017001152039,-0.22015488147735596,0.0071221827529370785,0.6970980167388916,0.6720539927482605,-0.23802624642848969,0.04402143508195877,0.6998208165168762,0.6522840261459351,-0.2534075677394867,0.06366109848022461,0.7115175724029541,0.6259850263595581,-0.2667465806007385,0.06842155009508133,0.7295940518379211,0.6058617234230042,-0.2748996317386627,0.0683920681476593,0.7434273362159729,0.5948197841644287,-0.27640077471733093,0.07328193634748459,0.7512800097465515,0.5921973586082458,-0.2692156732082367,0.07968369871377945,0.755298376083374,0.6092278361320496,-0.24850885570049286,0.08208198100328445,0.7485634684562683],"type":"quaternion"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.4074153006076813,0.21364398300647736,0.06289565563201904,0.8856710195541382,0.4101026654243469,0.21005591750144958,0.06290005892515182,0.8852885961532593,0.41111087799072266,0.2059529572725296,0.06609835475683212,0.8855519890785217,0.4107498228549957,0.20153199136257172,0.07176853716373444,0.886294960975647,0.4089035391807556,0.19777309894561768,0.07947900891304016,0.8873369693756104,0.40579044818878174,0.19483186304569244,0.08825359493494034,0.8885864615440369,0.40196916460990906,0.19073031842708588,0.09607932716608047,0.8903996348381042,0.39783141016960144,0.185256689786911,0.10194827616214752,0.8927578926086426,0.39248836040496826,0.1787581443786621,0.1076117530465126,0.8957779407501221,0.3843042850494385,0.16344711184501648,0.10789791494607925,0.9021936058998108,0.3779529929161072,0.1324038803577423,0.10182591527700424,0.9106329083442688,0.3714376389980316,0.09313733130693436,0.09318020194768906,0.919063150882721,0.3623995780944824,0.05494704470038414,0.08627202361822128,0.9263932704925537,0.346121221780777,0.02806166373193264,0.08555327355861664,0.9338593482971191,0.3179827034473419,0.014633212238550186,0.09062451869249344,0.9436419010162354,0.2870866656303406,0.00864367838948965,0.08845371752977371,0.9537727236747742,0.2534118890762329,0.005505331791937351,0.08229965716600418,0.9638355374336243,0.2579544186592102,0.012615310028195381,0.09321820735931396,0.9615668058395386,0.30019378662109375,0.02695241943001747,0.09632330387830734,0.9486196041107178,0.3278547525405884,0.03147120401263237,0.0848398208618164,0.9403845071792603,0.3412964642047882,0.009193404577672482,0.07302042096853256,0.9370700120925903,0.35945749282836914,-0.026058683171868324,0.06669054925441742,0.9304104447364807,0.3753540515899658,-0.04709906503558159,0.06931664794683456,0.9230851531028748,0.3871402144432068,-0.034023407846689224,0.06960646063089371,0.9187599420547485,0.3993551433086395,-0.011676318943500519,0.07876423001289368,0.9133319854736328,0.42155173420906067,-0.017354771494865417,0.07358056306838989,0.9036475419998169,0.3899095356464386,-0.034675706177949905,0.07378357648849487,0.9172372221946716,0.30599093437194824,-0.04136594012379646,0.05502770096063614,0.949542224407196,0.2601921558380127,-0.012143868021667004,0.05214618518948555,0.9640712141990662,0.28608325123786926,0.0017635294934734702,0.08405713737010956,0.9545091390609741,0.29238030314445496,0.013568535447120667,0.11849669367074966,0.9488351941108704,0.28217896819114685,0.028878889977931976,0.13961075246334076,0.9487096071243286,0.2553858757019043,0.04985155537724495,0.13029152154922485,0.9567220211029053,0.21557965874671936,0.07236635684967041,0.11076200008392334,0.9674814343452454,0.18081115186214447,0.0921366810798645,0.09878291934728622,0.9741971492767334,0.1508726328611374,0.10981376469135284,0.09836918115615845,0.9774977564811707,0.1240040510892868,0.12438667565584183,0.10004802793264389,0.9793576598167419,0.10151560604572296,0.137960284948349,0.10140012204647064,0.9799895882606506,0.100645050406456,0.1474432349205017,0.10084058344364166,0.9787554740905762,0.13192118704319,0.15320701897144318,0.09739121049642563,0.9744943976402283,0.19563591480255127,0.15423911809921265,0.09992916136980057,0.9633021950721741,0.2528550326824188,0.15461157262325287,0.10614433884620667,0.9491538405418396,0.2916434109210968,0.15932410955429077,0.10683014988899231,0.9370951056480408,0.3224318027496338,0.16645339131355286,0.1076396107673645,0.925605058670044,0.33766794204711914,0.18166762590408325,0.11002407222986221,0.9169906973838806],"type":"quaternion"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[-0.45238855481147766,-0.04891098290681839,0.047231048345565796,0.8892252445220947,-0.45548176765441895,-0.05111099034547806,0.049412790685892105,0.8874020576477051,-0.4573431611061096,-0.05198245123028755,0.050949838012456894,0.8863064646720886,-0.4587189257144928,-0.05232798680663109,0.05159153416752815,0.8855377435684204,-0.45906561613082886,-0.05203423649072647,0.05106918141245842,0.8854056596755981,-0.4581993818283081,-0.0512784942984581,0.04964112862944603,0.8859794735908508,-0.45716825127601624,-0.0510985404253006,0.04912828281521797,0.8865509629249573,-0.4560466408729553,-0.05180143192410469,0.049956630915403366,0.8870413899421692,-0.4541754126548767,-0.05294926464557648,0.051219552755355835,0.8878612518310547,-0.4498220384120941,-0.05385338515043259,0.05162873491644859,0.8899968862533569,-0.4434318542480469,-0.05260981246829033,0.04800115153193474,0.893474280834198,-0.43484389781951904,-0.04929189756512642,0.04000202938914299,0.898265540599823,-0.42578259110450745,-0.04494596645236015,0.029748382046818733,0.9032187461853027,-0.416096955537796,-0.041263747960329056,0.02051643095910549,0.9081518054008484,-0.40116870403289795,-0.03768615424633026,0.014743523672223091,0.9151098728179932,-0.38509273529052734,-0.03721819818019867,0.014339402318000793,0.9220156073570251,-0.3683568239212036,-0.03873767331242561,0.01767226681113243,0.9287089705467224,-0.33850008249282837,-0.03026374615728855,0.006015083286911249,0.9404603242874146,-0.33406856656074524,-0.02190868742763996,-0.007620215881615877,0.9422633051872253,-0.34030500054359436,-0.01940801925957203,-0.011169569566845894,0.9400484561920166,-0.3666735291481018,-0.018424471840262413,-0.012211431749165058,0.9300870299339294,-0.4098191261291504,-0.018338635563850403,-0.0109186302870512,0.9119170904159546,-0.4488763213157654,-0.01987222582101822,-0.007029097061604261,0.893345296382904,-0.4814854860305786,-0.026652801781892776,0.005915733985602856,0.8760287761688232,-0.5104317665100098,-0.031193388625979424,0.01559089682996273,0.8592109084129333,-0.5198137760162354,-0.028150472790002823,0.010449989698827267,0.8537517189979553,-0.4845525324344635,-0.029752174392342567,0.009969217702746391,0.8741992115974426,-0.4283292293548584,-0.03892217203974724,0.02258697710931301,0.9025015234947205,-0.3167971670627594,-0.013990526087582111,-0.024642733857035637,0.9480699300765991,-0.3310082256793976,0.004981599282473326,-0.05645935982465744,0.9419241547584534,-0.33565187454223633,0.021109171211719513,-0.080954410135746,0.9382636547088623,-0.3345664143562317,0.02968096174299717,-0.09435567259788513,0.937166690826416,-0.32456204295158386,0.027745896950364113,-0.09035055339336395,0.9411303997039795,-0.308047890663147,0.02164842002093792,-0.08020765334367752,0.9477365612983704,-0.29309746623039246,0.018657300621271133,-0.0750228613615036,0.9529519081115723,-0.2790035009384155,0.01988896168768406,-0.07659988105297089,0.9570235013961792,-0.2650052011013031,0.021282631903886795,-0.07812963426113129,0.9608408212661743,-0.2517498731613159,0.02203107811510563,-0.07842661440372467,0.9643577337265015,-0.25126051902770996,0.021718701347708702,-0.07528327405452728,0.96474289894104,-0.2690589725971222,0.02275198884308338,-0.07246019691228867,0.9601245522499084,-0.3060183823108673,0.026533151045441628,-0.07327140867710114,0.9488309025764465,-0.3404993712902069,0.03054473176598549,-0.07637524604797363,0.9366397857666016,-0.36576640605926514,0.031239161267876625,-0.0739348828792572,0.9272392988204956,-0.3877544701099396,0.03056276962161064,-0.07008207589387894,0.9185863733291626,-0.40951013565063477,0.03007734753191471,-0.0684967115521431,0.9092331528663635],"type":"quaternion"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.7078313231468201,-0.2818664610385895,0.46817681193351746,0.44758984446525574,0.7060065269470215,-0.2827876806259155,0.4682903587818146,0.4497666656970978,0.7001987099647522,-0.28526464104652405,0.46814626455307007,0.45736753940582275,0.692777693271637,-0.28843799233436584,0.4695630967617035,0.4651593267917633,0.6848514676094055,-0.2922333776950836,0.4724424183368683,0.47156792879104614,0.6772722601890564,-0.29644662141799927,0.4760778844356537,0.476205438375473,0.6716672778129578,-0.3008246421813965,0.47719964385032654,0.4802584648132324,0.6674270033836365,-0.3046424984931946,0.4770566523075104,0.4838915765285492,0.6649407148361206,-0.308312326669693,0.4758265018463135,0.48619604110717773,0.6669649481773376,-0.3116513192653656,0.4718390107154846,0.4851796627044678,0.670831561088562,-0.31593599915504456,0.46889036893844604,0.4799076318740845,0.675010621547699,-0.31918108463287354,0.47161635756492615,0.46910783648490906,0.680610179901123,-0.3215409219264984,0.47547227144241333,0.45531025528907776,0.6895543932914734,-0.3228948414325714,0.4743526875972748,0.4418633282184601,0.699940025806427,-0.32194432616233826,0.47051018476486206,0.4301813840866089,0.7104812860488892,-0.31918519735336304,0.46696099638938904,0.41867008805274963,0.7203617095947266,-0.31396573781967163,0.46602222323417664,0.40660524368286133,0.7287205457687378,-0.3057692348957062,0.47036445140838623,0.3927198350429535,0.7352486252784729,-0.2938821017742157,0.4794284403324127,0.37840619683265686,0.7402702569961548,-0.279187947511673,0.4917464852333069,0.36364737153053284,0.7451010942459106,-0.2610950469970703,0.5068191885948181,0.3461041748523712,0.7494379878044128,-0.2425084114074707,0.522558331489563,0.3262900114059448,0.7532516121864319,-0.21968016028404236,0.542083203792572,0.30082958936691284,0.7573407292366028,-0.1940373182296753,0.5632714033126831,0.2674134373664856,0.7595145106315613,-0.1731635183095932,0.5799481868743896,0.23835299909114838,0.7577608823776245,-0.14524702727794647,0.5998328328132629,0.21190166473388672,0.7469139695167542,-0.10952124744653702,0.626045823097229,0.195425882935524,0.7327810525894165,-0.0869094654917717,0.6497349143028259,0.1825464963912964,0.7027127146720886,-0.08559510856866837,0.6741122603416443,0.21081043779850006,0.6712199449539185,-0.0851781964302063,0.7024242281913757,0.22092705965042114,0.6363365054130554,-0.08716792613267899,0.7329770922660828,0.22410309314727783,0.6177992224693298,-0.07404585182666779,0.7520486116409302,0.21740344166755676,0.6367721557617188,-0.06436153501272202,0.736624002456665,0.21854948997497559,0.6892469525337219,-0.06296263635158539,0.6854718327522278,0.22605930268764496,0.7558253407478333,-0.06407033652067184,0.6084651947021484,0.23322337865829468,0.8071612119674683,-0.06801103055477142,0.5388633608818054,0.23128251731395721,0.8187212944030762,-0.0667574554681778,0.5250003933906555,0.2227407693862915,0.8131390810012817,-0.057676926255226135,0.5367254614830017,0.2177245318889618,0.8090664744377136,-0.04556208476424217,0.547211766242981,0.20951107144355774,0.8040586113929749,-0.03646901994943619,0.5565799474716187,0.20586062967777252,0.7993981838226318,-0.03300352394580841,0.5648527145385742,0.20202656090259552,0.7963692545890808,-0.03215697407722473,0.5709129571914673,0.19702894985675812,0.8018206357955933,-0.021435674279928207,0.5731526017189026,0.1676912009716034,0.8074038028717041,-0.00956697016954422,0.5736434459686279,0.1376257687807083,0.7971813678741455,0.0010261657880619168,0.5819096565246582,0.16087868809700012],"type":"quaternion"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.3567383885383606,-0.09673290699720383,-0.002930391812697053,0.9291780591011047,0.35382357239723206,-0.1126331090927124,-0.01136942021548748,0.9284359812736511,0.36075976490974426,-0.12428764253854752,-0.0170123390853405,0.92418372631073,0.36750146746635437,-0.13871575891971588,-0.024256067350506783,0.9192999005317688,0.3740426301956177,-0.15312619507312775,-0.03194025158882141,0.9141249060630798,0.38078463077545166,-0.16576476395130157,-0.038956183940172195,0.9088495373725891,0.38723722100257874,-0.17541904747486115,-0.0446036234498024,0.9040387272834778,0.3935116231441498,-0.1828429251909256,-0.04909574240446091,0.8996147513389587,0.39950868487358093,-0.1879943609237671,-0.05286987125873566,0.8956872820854187,0.40112289786338806,-0.18765074014663696,-0.05441201850771904,0.8949452638626099,0.4021947383880615,-0.18260160088539124,-0.05358857661485672,0.8955581188201904,0.4011467397212982,-0.17426902055740356,-0.05117044597864151,0.8978269696235657,0.39622217416763306,-0.16363254189491272,-0.04792362079024315,0.9021838903427124,0.38468748331069946,-0.15110507607460022,-0.04452560842037201,0.9095055460929871,0.3703756034374237,-0.13777947425842285,-0.0412963405251503,0.9176782369613647,0.3536081612110138,-0.125375896692276,-0.039137646555900574,0.9261265397071838,0.33347126841545105,-0.11485705524682999,-0.038478679955005646,0.9349460601806641,0.3083062469959259,-0.10703746229410172,-0.039099566638469696,0.9444370865821838,0.2790287137031555,-0.10312096029520035,-0.038554124534130096,0.9539510607719421,0.24640892446041107,-0.10138460993766785,-0.03721021115779877,0.9631298780441284,0.20744626224040985,-0.09945690631866455,-0.037190768867731094,0.9724665880203247,0.16752737760543823,-0.09698145091533661,-0.039664190262556076,0.9802836179733276,0.1230921819806099,-0.09262879937887192,-0.044614411890506744,0.9870551228523254,0.07405229657888412,-0.08217901736497879,-0.0480923093855381,0.9926983714103699,0.04235280677676201,-0.06593011319637299,-0.04997844994068146,0.9956714510917664,0.02126184105873108,-0.03563695028424263,-0.04907624050974846,0.9979326725006104,0.03928991034626961,0.018633488565683365,-0.04144666716456413,0.9981940388679504,0.07097763568162918,0.0407567098736763,-0.03583257645368576,0.9960005283355713,0.17741362750530243,0.015389818698167801,-0.01891426369547844,0.9838342070579529,0.21852533519268036,-0.007347007282078266,-0.02006409503519535,0.9755973219871521,0.25221288204193115,-0.03399886563420296,-0.026058899238705635,0.9667231440544128,0.2562958002090454,-0.061417870223522186,-0.03986990451812744,0.9638209342956543,0.25592896342277527,-0.07212084531784058,-0.0483705997467041,0.9627872705459595,0.25263869762420654,-0.05622229725122452,-0.04662642627954483,0.9647998213768005,0.2446797490119934,-0.012769799679517746,-0.03513409197330475,0.9688830971717834,0.2320927530527115,0.014091064222157001,-0.03518695384263992,0.971954882144928,0.22109004855155945,0.001754110213369131,-0.049111176282167435,0.9740145206451416,0.22667178511619568,0.01561790518462658,-0.05043080449104309,0.9725393652915955,0.22199620306491852,0.04669129103422165,-0.04599549621343613,0.972842276096344,0.2168937474489212,0.06489209830760956,-0.045392461121082306,0.9729777574539185,0.2138395607471466,0.07751500606536865,-0.04592043161392212,0.9727051854133606,0.20552216470241547,0.08132023364305496,-0.04876188933849335,0.9740482568740845,0.13131532073020935,0.047123540192842484,-0.07917241007089615,0.9870498776435852,0.05803268030285835,-0.02171865478157997,-0.10526445508003235,0.9925119280815125,0.09653341770172119,-0.12295536696910858,-0.10099086165428162,0.9825294613838196],"type":"quaternion"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[-0.5544148683547974,0.15885058045387268,-0.20567473769187927,0.7906254529953003,-0.5594161748886108,0.1622900664806366,-0.1992979645729065,0.7880328893661499,-0.5618118047714233,0.1586054563522339,-0.1933312714099884,0.7885650992393494,-0.5638331174850464,0.15403079986572266,-0.18585936725139618,0.7898246645927429,-0.5658848881721497,0.14943447709083557,-0.1783820241689682,0.7909636497497559,-0.5683822631835938,0.1453530639410019,-0.17177505791187286,0.791395902633667,-0.5708690881729126,0.14199450612068176,-0.1662207841873169,0.7914018034934998,-0.5735162496566772,0.13914881646633148,-0.16146019101142883,0.790978729724884,-0.5763280391693115,0.13674284517765045,-0.15722423791885376,0.79020756483078,-0.5776524543762207,0.13533608615398407,-0.15441489219665527,0.7900366187095642,-0.5797309279441833,0.13504666090011597,-0.15352970361709595,0.7887351512908936,-0.5816490650177002,0.13593614101409912,-0.15460573136806488,0.7869579792022705,-0.5815412998199463,0.13789422810077667,-0.15716585516929626,0.7861894369125366,-0.577020525932312,0.14015869796276093,-0.15998603403568268,0.7885476350784302,-0.570275604724884,0.14158767461776733,-0.16146379709243774,0.792885959148407,-0.5624755620956421,0.1423218697309494,-0.16171643137931824,0.7982566356658936,-0.5534599423408508,0.14208944141864777,-0.1604876071214676,0.804820716381073,-0.5428355932235718,0.14080263674259186,-0.15766726434230804,0.8128008246421814,-0.5304537415504456,0.1394696682691574,-0.15496046841144562,0.8216777443885803,-0.5162915587425232,0.13867662847042084,-0.1534309983253479,0.8310660719871521,-0.4981830418109894,0.13786853849887848,-0.15192951261997223,0.8424508571624756,-0.480500191450119,0.13698087632656097,-0.15052971243858337,0.8530513644218445,-0.46217072010040283,0.13626007735729218,-0.15005318820476532,0.863316535949707,-0.4416775405406952,0.13728384673595428,-0.15285681188106537,0.8733320832252502,-0.4277513921260834,0.14012625813484192,-0.15826064348220825,0.8788327574729919,-0.4163588881492615,0.1413017064332962,-0.16049271821975708,0.883697509765625,-0.42242175340652466,0.1249108538031578,-0.13770164549350739,0.8871276378631592,-0.4362609088420868,0.10445180535316467,-0.11465337127447128,0.8863525390625,-0.5021703839302063,0.06067265197634697,-0.03516414016485214,0.8619207143783569,-0.49403589963912964,0.03635115921497345,0.007171029690653086,0.8686516284942627,-0.47803977131843567,0.01855630986392498,0.032526057213544846,0.8775395750999451,-0.4766281843185425,-0.020141562446951866,0.09078783541917801,0.8741724491119385,-0.4675597846508026,-0.048083916306495667,0.13120563328266144,0.8728464245796204,-0.4568401575088501,-0.054307110607624054,0.13553999364376068,0.8774831891059875,-0.4477328062057495,-0.04130582511425018,0.10756557434797287,0.886712372303009,-0.4334191679954529,-0.02136087976396084,0.06756485253572464,0.898402214050293,-0.42790135741233826,-0.012764516286551952,0.04729146137833595,0.9024971127510071,-0.43904849886894226,-0.010037270374596119,0.0386037603020668,0.8975775837898254,-0.43931758403778076,0.0009460784494876862,0.019588688388466835,0.89811772108078,-0.43497708439826965,0.0131871048361063,0.005388995632529259,0.9003288745880127,-0.41798120737075806,0.027591438964009285,-0.003585095051676035,0.9080294966697693,-0.3675544559955597,0.036798108369112015,0.0023948820307850838,0.9292706251144409,-0.2420388162136078,0.035945091396570206,0.0245407372713089,0.9692899584770203,-0.09170939773321152,0.02867465279996395,0.03911972418427467,0.9946038126945496,-0.0370059609413147,0.010577280074357986,0.060286566615104675,0.9974388480186462],"type":"quaternion"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.04367886111140251,0.029373330995440483,-0.01944381557404995,0.9984244108200073,0.04341297224164009,0.028782863169908524,-0.019375041127204895,0.9984545111656189,0.04262734577059746,0.027011901140213013,-0.018704615533351898,0.9985506534576416,0.04137972369790077,0.024229731410741806,-0.01767599582672119,0.9986932873725891,0.039720695465803146,0.02057851292192936,-0.016344867646694183,0.9988651871681213,0.03770057111978531,0.01621830277144909,-0.014756211079657078,0.9990484714508057,0.035367418080568314,0.011353052221238613,-0.012986887246370316,0.9992254972457886,0.032768964767456055,0.006153289694339037,-0.011105554178357124,0.9993823170661926,0.029948702082037926,0.0007976909982971847,-0.009155578911304474,0.9995091557502747,0.02694941684603691,-0.004540456924587488,-0.007283944170922041,0.9995999932289124,0.023810183629393578,-0.009754051454365253,-0.0056037516333162785,0.999653160572052,0.020565101876854897,-0.014691772870719433,-0.00415816530585289,0.9996719360351562,0.017246341332793236,-0.01909070648252964,-0.002969916444271803,0.9996646046638489,0.013883770443499088,-0.020740913227200508,-0.0019177153008058667,0.9996866583824158,0.01051254104822874,-0.02183810994029045,-0.0009671381558291614,0.9997058510780334,0.007168478332459927,-0.02237893082201481,-0.00045185661292634904,0.9997237324714661,0.0038891579024493694,-0.02252739481627941,-0.00041408545803278685,0.9997385740280151,0.0007133639301173389,-0.022749660536646843,-0.001074545201845467,0.9997403621673584,-0.002318765502423048,-0.02283434383571148,-0.0020624888129532337,0.9997344613075256,-0.00516577810049057,-0.022571345791220665,-0.0029478014912456274,0.9997275471687317,-0.007786814589053392,-0.021352043375372887,-0.003596132155507803,0.9997352361679077,-0.009410509839653969,-0.019838383421301842,-0.0040960246697068214,0.999750554561615,-0.011505460366606712,-0.018385102972388268,-0.0044752308167517185,0.9997547268867493,-0.013652325607836246,-0.01632639579474926,-0.004990107845515013,0.9997611045837402,-0.015994494780898094,-0.012796101160347462,-0.00644097151234746,0.9997695088386536,-0.018905669450759888,-0.00822566170245409,-0.009949452243745327,0.9997379183769226,-0.02182173915207386,-0.002392303664237261,-0.011851356364786625,0.9996888041496277,-0.023355793207883835,0.006275085732340813,-0.013839979656040668,0.9996117353439331,-0.02342873439192772,0.01737356185913086,-0.013224879279732704,0.999487042427063,-0.023655233904719353,0.02893567830324173,-0.015059836208820343,0.9991878867149353,-0.021985888481140137,0.034541040658950806,-0.017524881288409233,0.9990077018737793,-0.018907522782683372,0.04012465849518776,-0.02007637545466423,0.998814046382904,-0.015773290768265724,0.04317502677440643,-0.021943194791674614,0.9987019896507263,-0.01248252671211958,0.04536593705415726,-0.02222348377108574,0.9986452460289001,-0.009151865728199482,0.05362476408481598,-0.02122688852250576,0.9982935786247253,-0.00529019720852375,0.060811854898929596,-0.019065748900175095,0.9979531168937683,-0.001538699259981513,0.06344494968652725,-0.016684778034687042,0.9978446960449219,0.000026761435947264545,0.06603600829839706,-0.014123370870947838,0.9977173209190369,-0.0004123724647797644,0.06773106753826141,-0.013855953700840473,0.9976072907447815,-0.0008813245221972466,0.06961052864789963,-0.016598012298345566,0.9974358081817627,-0.001340006827376783,0.07059647887945175,-0.022873859852552414,0.997241735458374,-0.0013146242126822472,0.0702938660979271,-0.032513368874788284,0.9969954490661621,-0.0010896804742515087,0.06712295860052109,-0.03956678882241249,0.9969592690467834,-0.0011032824404537678,0.06113211438059807,-0.04377828538417816,0.9971685409545898,-0.0017112914938479662,0.05223693326115608,-0.037178702652454376,0.9979409575462341],"type":"quaternion"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.5297933220863342,0.513465404510498,-0.444655179977417,0.507891833782196,0.5307596921920776,0.5131548047065735,-0.44424429535865784,0.5075562596321106,0.5334756374359131,0.5123382210731506,-0.44303593039512634,0.5065889954566956,0.5376607179641724,0.5110822319984436,-0.44115862250328064,0.5050691962242126,0.5429902076721191,0.509513258934021,-0.43866750597953796,0.5031189322471619,0.5491943955421448,0.5076991319656372,-0.4356914460659027,0.5007994174957275,0.5559855699539185,0.5057331919670105,-0.4323347806930542,0.49819740653038025,0.5630824565887451,0.5037139654159546,-0.42870399355888367,0.49540212750434875,0.5699883699417114,0.5019699931144714,-0.4246339201927185,0.4927731454372406,0.5762569308280945,0.5007767081260681,-0.4200385808944702,0.49063029885292053,0.5816943645477295,0.5001636743545532,-0.41514426469802856,0.4890022873878479,0.5861168503761292,0.5001699328422546,-0.4101760685443878,0.48790642619132996,0.5893487334251404,0.5008451342582703,-0.40535470843315125,0.4873497486114502,0.5912582874298096,0.5022023320198059,-0.40084367990493774,0.4873713552951813,0.5914543867111206,0.5045966506004333,-0.3971345126628876,0.4876968264579773,0.5896553993225098,0.508280873298645,-0.39453965425491333,0.4881553053855896,0.5862547755241394,0.5125817656517029,-0.39314574003219604,0.4888781011104584,0.5821317434310913,0.5160887241363525,-0.3926243782043457,0.4905316233634949,0.5769476294517517,0.5188927054405212,-0.39369624853134155,0.4928337335586548,0.5703635811805725,0.5212000608444214,-0.3969082832336426,0.49547937512397766,0.5608994960784912,0.5250940322875977,-0.4026211202144623,0.4975583851337433,0.5494411587715149,0.5292034149169922,-0.40953513979911804,0.5003389120101929,0.5373977422714233,0.5318418145179749,-0.41796377301216125,0.5036409497261047,0.5254172086715698,0.5343208909034729,-0.4291771352291107,0.5042271614074707,0.5128564834594727,0.5370794534683228,-0.4453693628311157,0.500170111656189,0.49981948733329773,0.539271891117096,-0.46538129448890686,0.49273377656936646,0.48538148403167725,0.5426366925239563,-0.4894883930683136,0.47994938492774963,0.4692435562610626,0.5477549433708191,-0.5190999507904053,0.45859599113464355,0.45188698172569275,0.5554500222206116,-0.5514095425605774,0.42804333567619324,0.44043534994125366,0.5645340085029602,-0.5758544206619263,0.39460092782974243,0.43288907408714294,0.572709858417511,-0.593981146812439,0.3630385100841522,0.4305141568183899,0.5788766741752625,-0.6034751534461975,0.3396722674369812,0.4328635334968567,0.5850821733474731,-0.604945182800293,0.3230316638946533,0.43277716636657715,0.5948202610015869,-0.6023166179656982,0.31001219153404236,0.42029979825019836,0.6168057918548584,-0.6012067198753357,0.2853933572769165,0.4038407504558563,0.6413381099700928,-0.5981186628341675,0.2604847252368927,0.3971341550350189,0.6593033671379089,-0.5878772735595703,0.24900583922863007,0.39331668615341187,0.6699571013450623,-0.57965487241745,0.2458856999874115,0.38960015773773193,0.6753815412521362,-0.5762187242507935,0.24503785371780396,0.3844580054283142,0.6768355369567871,-0.579992949962616,0.24019566178321838,0.3858133852481842,0.670695424079895,-0.5883083939552307,0.23496615886688232,0.3974194824695587,0.6604201793670654,-0.5945999622344971,0.2288098931312561,0.4163835942745209,0.649499237537384,-0.5924615859985352,0.23187243938446045,0.4410020112991333,0.6366241574287415,-0.5830672979354858,0.2454782873392105,0.4614906907081604,0.6276290416717529,-0.5684967041015625,0.26442354917526245],"type":"quaternion"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.3846764862537384,0.15799054503440857,0.22142164409160614,0.8820632100105286,0.3852458894252777,0.15712834894657135,0.21923673152923584,0.8825143575668335,0.3868406116962433,0.15473857522010803,0.21302562952041626,0.8837592601776123,0.389278382062912,0.15110647678375244,0.2032991647720337,0.8856063485145569,0.3923712968826294,0.14651194214820862,0.19056710600852966,0.8878418803215027,0.3959384262561798,0.14123456180095673,0.17534239590168,0.8902475237846375,0.39981406927108765,0.13555815815925598,0.15814654529094696,0.8926154971122742,0.40385520458221436,0.12977421283721924,0.13951098918914795,0.8947604894638062,0.40794575214385986,0.12418337911367416,0.11997748911380768,0.896528959274292,0.41199836134910583,0.11909548193216324,0.10009760409593582,0.8978051543235779,0.4158594310283661,0.11589059978723526,0.08092068135738373,0.898377537727356,0.4195157587528229,0.11553451418876648,0.06332211196422577,0.8981361985206604,0.42307382822036743,0.11772070080041885,0.047586847096681595,0.8971543312072754,0.42658481001853943,0.122145876288414,0.033988308161497116,0.8955169320106506,0.4301222264766693,0.12850376963615417,0.02279963344335556,0.8932871222496033,0.43560436367988586,0.13114042580127716,0.0009930944070219994,0.8905335664749146,0.4423232078552246,0.13167186081409454,-0.025845443829894066,0.8867607712745667,0.450229287147522,0.1294594407081604,-0.05781271681189537,0.881584644317627,0.45895397663116455,0.1245044469833374,-0.09347503632307053,0.8747127056121826,0.4680638909339905,0.11679205298423767,-0.13159795105457306,0.8660010099411011,0.47652578353881836,0.10879495739936829,-0.1658422201871872,0.8564947247505188,0.48477551341056824,0.09698349982500076,-0.20324555039405823,0.84514981508255,0.4922431707382202,0.08095793426036835,-0.24381574988365173,0.8316828608512878,0.49754759669303894,0.06761550158262253,-0.2735447287559509,0.8203949332237244,0.500742495059967,0.05822252109646797,-0.2906193137168884,0.8132696747779846,0.4983998239040375,0.04663681611418724,-0.29500895738601685,0.8138749003410339,0.49048328399658203,0.031327031552791595,-0.2747627794742584,0.8264080286026001,0.4816462993621826,0.009802672080695629,-0.2345101684331894,0.8443493247032166,0.4789966940879822,-0.012195018120110035,-0.16224412620067596,0.8626067042350769,0.48386257886886597,-0.029477499425411224,-0.05274007096886635,0.8730558753013611,0.49765923619270325,-0.03839549794793129,0.08630289137363434,0.8622139692306519,0.5183730721473694,-0.0553245022892952,0.20288154482841492,0.8288954496383667,0.544761598110199,-0.08649280667304993,0.2750033140182495,0.787481427192688,0.5649933218955994,-0.09989757090806961,0.3307960331439972,0.7492510080337524,0.5791922211647034,-0.10523489862680435,0.3749801814556122,0.7161368131637573,0.583710253238678,-0.10804154723882675,0.4037453234195709,0.6961315274238586,0.574512779712677,-0.1140238419175148,0.4103167653083801,0.6989805102348328,0.5598836541175842,-0.11255840957164764,0.4127520024776459,0.7095749974250793,0.5492168664932251,-0.11230775713920593,0.4072142541408539,0.7210578322410583,0.5427852272987366,-0.10838586837053299,0.3997438848018646,0.7306445837020874,0.5396522879600525,-0.11665929853916168,0.3772975504398346,0.7435137033462524,0.5409314632415771,-0.11972668766975403,0.368314653635025,0.7465943098068237,0.5476679801940918,-0.12285244464874268,0.36997470259666443,0.7403281331062317,0.5545973181724548,-0.12471090257167816,0.3881271779537201,0.7254145741462708,0.5498268008232117,-0.13263753056526184,0.4256291389465332,0.7063552141189575],"type":"quaternion"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.053471680730581284,-0.28444886207580566,0.7206580638885498,0.6299853920936584,0.05384324491024017,-0.2819397449493408,0.7194636464118958,0.6324421167373657,0.054912202060222626,-0.2747347950935364,0.7160075306892395,0.6394049525260925,0.05667588859796524,-0.2632882595062256,0.710459291934967,0.6501652002334595,0.0591907873749733,-0.24803580343723297,0.7029787302017212,0.6639243960380554,0.06254180520772934,-0.22941851615905762,0.6937384009361267,0.6798402070999146,0.06681099534034729,-0.20790433883666992,0.6829366087913513,0.6970722675323486,0.07205280661582947,-0.18399924039840698,0.6708101630210876,0.7148191332817078,0.07827156782150269,-0.15825305879116058,0.6576352119445801,0.7323561310768127,0.08540253341197968,-0.13125722110271454,0.643721878528595,0.749066174030304,0.09329987317323685,-0.10363208502531052,0.6294031739234924,0.76446533203125,0.10172900557518005,-0.0760134607553482,0.6150189638137817,0.7782190442085266,0.11036820709705353,-0.04903177544474602,0.6008964776992798,0.7901507616043091,0.11881083250045776,-0.02329377271234989,0.5873318314552307,0.8002391457557678,0.12657926976680756,0.0006346735171973705,0.5745749473571777,0.8086043000221252,0.1294044852256775,0.019641373306512833,0.5630404949188232,0.8159988522529602,0.12797485291957855,0.03440352529287338,0.5525811910629272,0.8228565454483032,0.12320151180028915,0.04565221071243286,0.543056309223175,0.829353392124176,0.11693549901247025,0.05905402824282646,0.5348858833312988,0.8347070217132568,0.11090154945850372,0.078095443546772,0.5285760760307312,0.8379793167114258,0.1046687439084053,0.09951644390821457,0.5240949988365173,0.8393243551254272,0.09417650103569031,0.11811867356300354,0.520950198173523,0.8401129245758057,0.07838794589042664,0.1306573450565338,0.5185433030128479,0.8413660526275635,0.06679195165634155,0.13972726464271545,0.517764687538147,0.8413887023925781,0.05505511164665222,0.1477161943912506,0.531968355178833,0.8319606781005859,0.05268070101737976,0.143381729722023,0.5656407475471497,0.8103808164596558,0.06106816977262497,0.14530164003372192,0.6276822686195374,0.7623471021652222,0.08863013237714767,0.1457304060459137,0.6967306137084961,0.6967595219612122,0.1393677145242691,0.13587038218975067,0.757219672203064,0.6234855651855469,0.18374280631542206,0.10884807258844376,0.7980761528015137,0.5634405016899109,0.18335771560668945,0.08236531168222427,0.8161973357200623,0.5416805744171143,0.16398653388023376,0.061163902282714844,0.8237505555152893,0.539260983467102,0.14086246490478516,0.0351463258266449,0.831672728061676,0.5359505414962769,0.12994296848773956,0.012156413868069649,0.8370628356933594,0.5313123464584351,0.12929891049861908,-0.004868943709880114,0.838577151298523,0.529194176197052,0.13579897582530975,-0.019459612667560577,0.8376198410987854,0.5287466049194336,0.1545724719762802,-0.039270348846912384,0.838434636592865,0.5211454629898071,0.17417189478874207,-0.05213843286037445,0.8354320526123047,0.5186511874198914,0.19158975780010223,-0.06115882471203804,0.8310920000076294,0.5184969305992126,0.211577907204628,-0.06585308164358139,0.8258658647537231,0.5185014605522156,0.2319152057170868,-0.07162638753652573,0.8219779133796692,0.5152060389518738,0.2377646118402481,-0.0729888305068016,0.8192603588104248,0.5166750550270081,0.23596663773059845,-0.07073760777711868,0.8173672556877136,0.5207943320274353,0.21699631214141846,-0.06448621302843094,0.8171845078468323,0.5300600528717041,0.19178135693073273,-0.05511751025915146,0.8168005347251892,0.5413122177124023],"type":"quaternion"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.4313414394855499,-0.5555616617202759,0.6031523942947388,0.3761688768863678,0.4321349561214447,-0.5550230145454407,0.6024152040481567,0.37723320722579956,0.4342501163482666,-0.553650438785553,0.6003378033638,0.380121648311615,0.4372837245464325,-0.5518169403076172,0.5971008539199829,0.38438475131988525,0.4408625066280365,-0.5498813390731812,0.5929023623466492,0.3895350992679596,0.444607675075531,-0.5482177138328552,0.5879403948783875,0.39510446786880493,0.4481429159641266,-0.5472075939178467,0.582428514957428,0.4006355404853821,0.4510898292064667,-0.5472391843795776,0.5766010880470276,0.40568289160728455,0.45306333899497986,-0.5487014651298523,0.5707129836082458,0.40981343388557434,0.4536648392677307,-0.5519769191741943,0.5650367736816406,0.412605345249176,0.45247313380241394,-0.5574311017990112,0.5598568916320801,0.41364118456840515,0.4490348696708679,-0.5653985738754272,0.5554596185684204,0.41250067949295044,0.44228261709213257,-0.5767467021942139,0.5497482419013977,0.41173553466796875,0.43219107389450073,-0.591150164604187,0.5410482883453369,0.4135447144508362,0.42017000913619995,-0.6068266034126282,0.5301046371459961,0.4173819124698639,0.4073902666568756,-0.6222253441810608,0.517976701259613,0.42257431149482727,0.3942498564720154,-0.6365380883216858,0.5059301853179932,0.42827677726745605,0.38067692518234253,-0.6497345566749573,0.4954589307308197,0.43295568227767944,0.3683342933654785,-0.6606321930885315,0.4874080717563629,0.4362664222717285,0.357305645942688,-0.6692758798599243,0.4806080460548401,0.4397936165332794,0.34768062829971313,-0.675728976726532,0.47456663846969604,0.4441792368888855,0.33855241537094116,-0.680633544921875,0.47194692492485046,0.44652706384658813,0.33071762323379517,-0.683364748954773,0.4729083180427551,0.4472093880176544,0.3279857337474823,-0.6824480295181274,0.474835604429245,0.44857701659202576,0.3322839140892029,-0.6758331060409546,0.4756094217300415,0.4545687735080719,0.3561239242553711,-0.6546655893325806,0.47030118107795715,0.4726579487323761,0.41012996435165405,-0.608887255191803,0.4598318934440613,0.4996042251586914,0.47392863035202026,-0.5524446964263916,0.4313604533672333,0.5330334901809692,0.532353937625885,-0.49819910526275635,0.38600248098373413,0.5651540756225586,0.5803208351135254,-0.4546371400356293,0.3340061604976654,0.5873438119888306,0.6204925179481506,-0.41511642932891846,0.279377281665802,0.6038342714309692,0.6518694162368774,-0.3848212659358978,0.23048211634159088,0.6114383935928345,0.6760637760162354,-0.3618369996547699,0.18296469748020172,0.6152526140213013,0.6951336860656738,-0.33910465240478516,0.13916680216789246,0.6184090971946716,0.7133923768997192,-0.31032782793045044,0.0970127284526825,0.6207709908485413,0.7289608716964722,-0.2890017628669739,0.0623881034553051,0.6174154877662659,0.7392390370368958,-0.2805536389350891,0.042186785489320755,0.6107664108276367,0.7416601181030273,-0.2775992751121521,0.031052662059664726,0.609848141670227,0.7374327778816223,-0.27610400319099426,0.02417457476258278,0.6159343719482422,0.7278987765312195,-0.2770341634750366,0.012456339783966541,0.6271047592163086,0.7114856243133545,-0.289223849773407,-0.0007663415162824094,0.640419602394104,0.6911534667015076,-0.31195732951164246,-0.014390171505510807,0.6517533659934998,0.6703193783760071,-0.3428725302219391,-0.01694755256175995,0.6578929424285889,0.6465281844139099,-0.38138547539711,-0.005550780799239874,0.660693347454071,0.623634397983551,-0.41665855050086975,0.014143303968012333,0.6612683534622192],"type":"quaternion"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.4312799572944641,-0.19474835693836212,-0.10313624888658524,0.8748906254768372,0.4272627532482147,-0.19148211181163788,-0.10645774751901627,0.8771818280220032,0.4198952317237854,-0.18578730523586273,-0.10547026991844177,0.8820698261260986,0.40896520018577576,-0.17722724378108978,-0.10260073840618134,0.8892756104469299,0.3954869210720062,-0.16646231710910797,-0.09711282700300217,0.8980253338813782,0.38021600246429443,-0.15388675034046173,-0.08921948075294495,0.9076313376426697,0.36234965920448303,-0.13850025832653046,-0.08312789350748062,0.9179380536079407,0.34263572096824646,-0.12087555974721909,-0.07869049906730652,0.9283306002616882,0.3218511939048767,-0.10173162072896957,-0.07555397599935532,0.9382718801498413,0.29950061440467834,-0.08088354766368866,-0.07567570358514786,0.9476446509361267,0.2783931791782379,-0.06139824166893959,-0.0726112648844719,0.955748438835144,0.2558700740337372,-0.044136229902505875,-0.06230500340461731,0.9636911153793335,0.22919027507305145,-0.029424848034977913,-0.05699753388762474,0.9712658524513245,0.20728746056556702,-0.01699499785900116,-0.05996781215071678,0.9762924909591675,0.1824411153793335,-0.005910586565732956,-0.06349048018455505,0.9811468720436096,0.15676265954971313,0.006668915972113609,-0.07271439582109451,0.9849333167076111,0.1306154578924179,0.0204694215208292,-0.083048015832901,0.9877366423606873,0.10306369513273239,0.03663159906864166,-0.09819760173559189,0.9891376495361328,0.07947663962841034,0.05421503633260727,-0.11996233463287354,0.9881058931350708,0.05777770280838013,0.07393818348646164,-0.14792826771736145,0.9845365285873413,0.04009784758090973,0.09241320192813873,-0.17866218090057373,0.9787399172782898,0.024450551718473434,0.1174059733748436,-0.2209775447845459,0.9678775668144226,0.013096031732857227,0.14293445646762848,-0.2697431147098541,0.9521748423576355,0.006239222828298807,0.1664135605096817,-0.3118674159049988,0.9354177713394165,0.007600815966725349,0.18521159887313843,-0.35138237476348877,0.9176978468894958,0.01682928577065468,0.2024216502904892,-0.3812960684299469,0.9018623232841492,0.020291127264499664,0.22012896835803986,-0.4119485318660736,0.8839852809906006,0.039225511252880096,0.23161680996418,-0.4400236904621124,0.866714596748352,0.06967438757419586,0.23989610373973846,-0.45319491624832153,0.8556925654411316,0.09284162521362305,0.2536158263683319,-0.44525477290153503,0.8537023663520813,0.10310493409633636,0.2697350084781647,-0.43317198753356934,0.8537999987602234,0.11664075404405594,0.2841630280017853,-0.4373481273651123,0.8452058434486389,0.149095356464386,0.28393736481666565,-0.46340954303741455,0.8260761499404907,0.22277773916721344,0.25053879618644714,-0.47871890664100647,0.8114361763000488,0.2974887192249298,0.2046479731798172,-0.5117289423942566,0.7795853018760681,0.3580082952976227,0.1601109653711319,-0.5416311621665955,0.7435255646705627,0.4273500442504883,0.15903204679489136,-0.5541290044784546,0.6964350342750549,0.4682259261608124,0.15285612642765045,-0.5693680047988892,0.6581942439079285,0.47842374444007874,0.14333444833755493,-0.6004051566123962,0.6245635151863098,0.48455315828323364,0.13747848570346832,-0.6046533584594727,0.617010772228241,0.4785604476928711,0.1339799463748932,-0.5996912717819214,0.6272158026695251,0.44937217235565186,0.14678211510181427,-0.5849866271018982,0.6590222716331482,0.4268292784690857,0.1579200178384781,-0.5721901655197144,0.6822583675384521,0.38805240392684937,0.16686519980430603,-0.5882561802864075,0.6895840167999268,0.3250907361507416,0.1767575591802597,-0.6166524887084961,0.6948471069335938],"type":"quaternion"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[-0.08082585781812668,0.1466812789440155,-0.6006653904914856,0.7817626595497131,-0.08326415717601776,0.15126042068004608,-0.5922189950942993,0.7870604395866394,-0.08915317058563232,0.15940538048744202,-0.578701376914978,0.7948247194290161,-0.0981658473610878,0.17103257775306702,-0.5594445466995239,0.8050671815872192,-0.10993684828281403,0.18489855527877808,-0.5371185541152954,0.8156163692474365,-0.12427466362714767,0.20049090683460236,-0.5137076377868652,0.8249022364616394,-0.14041191339492798,0.2179003804922104,-0.48671653866767883,0.8342128396034241,-0.15740860998630524,0.23607802391052246,-0.458269864320755,0.8423054218292236,-0.17423470318317413,0.25435441732406616,-0.43083032965660095,0.8481340408325195,-0.1888158619403839,0.27176767587661743,-0.40387582778930664,0.8528629541397095,-0.20310594141483307,0.2911679148674011,-0.38821709156036377,0.850445032119751,-0.22244815528392792,0.31962284445762634,-0.39033573865890503,0.834263801574707,-0.23726733028888702,0.3475804924964905,-0.38175833225250244,0.8228928446769714,-0.24910254776477814,0.37398961186408997,-0.3691948354244232,0.8134954571723938,-0.26644182205200195,0.4065878987312317,-0.372387170791626,0.7905839085578918,-0.2819509208202362,0.43625715374946594,-0.37441325187683105,0.7681133151054382,-0.2973492741584778,0.46386557817459106,-0.3786087930202484,0.7436850666999817,-0.31259453296661377,0.48837003111839294,-0.3842698037624359,0.7184122204780579,-0.33365049958229065,0.5077183842658997,-0.38948819041252136,0.6922415494918823,-0.35610276460647583,0.5270524621009827,-0.39244139194488525,0.6643766164779663,-0.3777627944946289,0.5428011417388916,-0.3957836329936981,0.6371951103210449,-0.3980250358581543,0.5528674721717834,-0.40059104561805725,0.6127320528030396,-0.4155319631099701,0.5628729462623596,-0.40059176087379456,0.5916362404823303,-0.4265599250793457,0.5667192935943604,-0.4106369614601135,0.5729339122772217,-0.44662949442863464,0.5561001300811768,-0.4186379611492157,0.5621538758277893,-0.4712885022163391,0.5441395044326782,-0.4370891749858856,0.5392146706581116,-0.5029463171958923,0.5111621022224426,-0.42968103289604187,0.5487555861473083,-0.5310062170028687,0.4745074212551117,-0.4104061722755432,0.5695979595184326,-0.48399487137794495,0.47919854521751404,-0.4706929624080658,0.5608617663383484,-0.29181423783302307,0.49708911776542664,-0.5179122090339661,0.6320710778236389,-0.056994397193193436,0.5748016834259033,-0.41053858399391174,0.7055584192276001,0.05560898035764694,0.6323036551475525,-0.2482910305261612,0.7317454218864441,0.04312862455844879,0.5862776041030884,-0.14087337255477905,0.7966010570526123,0.03324923664331436,0.4465934634208679,-0.13116618990898132,0.8844457268714905,0.01556822843849659,0.2635466158390045,-0.1119396910071373,0.9580032825469971,0.019519466906785965,0.10633477568626404,-0.07365629822015762,0.9914064407348633,0.04424857720732689,0.06600184738636017,-0.06825454533100128,0.9944984316825867,0.059000164270401,0.04225325584411621,-0.07023269683122635,0.9948874711990356,0.08262443542480469,0.02598319575190544,-0.05668015033006668,0.9946283102035522,0.09595976769924164,-0.018595360219478607,-0.10632748156785965,0.9895152449607849,0.11483538150787354,-0.06703632324934006,-0.1750185787677765,0.9755447506904602,0.1482035368680954,-0.09719108045101166,-0.2511574923992157,0.951582670211792,0.15620577335357666,-0.11599520593881607,-0.3109644949436188,0.9302935004234314,0.1489028036594391,-0.10717166215181351,-0.3292768597602844,0.9262391328811646,0.1375618726015091,-0.08062661439180374,-0.35085535049438477,0.9227548837661743],"type":"quaternion"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5],"values":[0.06514779478311539,0.30207815766334534,-0.05072128772735596,0.9497009515762329,0.06968709826469421,0.3111051917076111,-0.052301548421382904,0.9463729858398438,0.07335133105516434,0.32341042160987854,-0.053619761019945145,0.9418865442276001,0.07736214250326157,0.33827242255210876,-0.05400500074028969,0.9363067746162415,0.0821552500128746,0.3528110980987549,-0.05339831858873367,0.9305500984191895,0.08791550248861313,0.3656057119369507,-0.05176929011940956,0.9251612424850464,0.09438657760620117,0.37858837842941284,-0.04996204376220703,0.9193834066390991,0.10158911347389221,0.39095577597618103,-0.04823332652449608,0.9135134220123291,0.10939840972423553,0.4023357629776001,-0.04684436693787575,0.9077243804931641,0.11781349033117294,0.41229578852653503,-0.04617412015795708,0.9022195339202881,0.12768065929412842,0.41647300124168396,-0.04624025896191597,0.8989492654800415,0.1377459466457367,0.41198012232780457,-0.045852180570364,0.8995532393455505,0.14823560416698456,0.40386494994163513,-0.04982322081923485,0.9013528823852539,0.15925589203834534,0.3987182676792145,-0.053174253553152084,0.9015730023384094,0.17017622292041779,0.3964550495147705,-0.056987058371305466,0.9003421664237976,0.1786089688539505,0.395590603351593,-0.05921159312129021,0.8989443182945251,0.1861237734556198,0.39501598477363586,-0.060396697372198105,0.8975926637649536,0.19153666496276855,0.39345404505729675,-0.06025008484721184,0.8971496820449829,0.19578592479228973,0.3911489248275757,-0.05937029421329498,0.89729905128479,0.19685745239257812,0.3874831199645996,-0.057232070714235306,0.8987928032875061,0.19736842811107635,0.38105323910713196,-0.053090762346982956,0.901679277420044,0.19487851858139038,0.3731386959552765,-0.048559028655290604,0.9057769775390625,0.1895962357521057,0.3601717948913574,-0.04116852954030037,0.9124881625175476,0.1835840493440628,0.33859947323799133,-0.03002643771469593,0.9223588109016418,0.17475458979606628,0.30987805128097534,-0.01637839339673519,0.934434711933136,0.15404172241687775,0.2654617130756378,0.0038393917493522167,0.9517281651496887,0.13753926753997803,0.20209082961082458,0.0258526261895895,0.9693162441253662,0.12293863296508789,0.1430118829011917,0.04719112068414688,0.9809213876724243,0.11743371188640594,0.08824261277914047,0.055664174258708954,0.9875850081443787,0.11486156284809113,0.03657633811235428,0.05931684002280235,0.9909341335296631,0.11029673367738724,-0.012911731377243996,0.05624552071094513,0.9922219514846802,0.10229704529047012,-0.05850865691900253,0.05224397033452988,0.9916565418243408,0.08178719878196716,-0.10192076861858368,0.04483044892549515,0.9904106855392456,0.05710447207093239,-0.1454647034406662,0.03140408545732498,0.9872147440910339,0.03290386125445366,-0.19231615960597992,0.01590506173670292,0.9806522727012634,0.03407324478030205,-0.23772349953651428,0.0034108704421669245,0.9707291126251221,0.047289662063121796,-0.2795568108558655,-0.0075111486949026585,0.958934485912323,0.06406793743371964,-0.3268866240978241,-0.018821019679307938,0.9427015781402588,0.07470066845417023,-0.3616929054260254,-0.03057803213596344,0.9287965893745422,0.07524912804365158,-0.38198694586753845,-0.038374509662389755,0.9202994108200073,0.07113710790872574,-0.3925275206565857,-0.0448312871158123,0.9158885478973389,0.060674071311950684,-0.4004283547401428,-0.047361548990011215,0.9130896329879761,0.04910677671432495,-0.408805251121521,-0.044804371893405914,0.9101974368095398,0.05052894353866577,-0.41717061400413513,-0.03243514895439148,0.9068425893783569,0.06319376081228256,-0.4187202751636505,-0.01422420609742403,0.9058021306991577],"type":"quaternion"}],"uuid":"268e8269-8871-4dc4-9434-8e87b676e618","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/one-hand-combo-hit2.json b/public/anims/baked/sword_shield/one-hand-combo-hit2.json new file mode 100644 index 0000000..57b6ea0 --- /dev/null +++ b/public/anims/baked/sword_shield/one-hand-combo-hit2.json @@ -0,0 +1 @@ +{"name":"sword_shield/one-hand-sword-combo-hit2","duration":1.5222222010294597,"tracks":[{"name":"Hips.position","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[6.114113807678223,57.44379806518555,21.347911834716797,6.280303478240967,58.80095291137695,22.926664352416992,6.436324596405029,60.08711242675781,24.182016372680664,6.539061069488525,61.123321533203125,25.172183990478516,6.656832695007324,62.06179428100586,26.139326095581055,6.783809661865234,62.93651580810547,27.133575439453125,6.9023895263671875,63.661842346191406,28.099763870239258,7.024378776550293,64.26797485351562,29.130386352539062,7.1341962814331055,64.673583984375,30.177654266357422,7.208714485168457,64.83621215820312,31.20856475830078,7.218499183654785,64.82466888427734,32.2325325012207,7.086217403411865,64.62474060058594,33.297210693359375,6.8209333419799805,64.31747436523438,34.4244384765625,6.549182891845703,64.10235595703125,35.65305709838867,6.353745460510254,63.850685119628906,36.959754943847656,6.304308891296387,63.559444427490234,38.285362243652344,6.309323310852051,63.00397491455078,39.72880935668945,6.3337931632995605,62.22782516479492,41.120811462402344,6.348611831665039,61.52271270751953,42.3232307434082,6.335160732269287,60.8929443359375,43.32312774658203,6.271069049835205,60.21820831298828,44.127685546875,6.164546012878418,59.48382568359375,44.99279022216797,6.00084924697876,58.852012634277344,46.03312683105469,5.795962333679199,58.26277542114258,47.10599899291992,5.390137195587158,57.72060775756836,48.302940368652344,4.80778169631958,57.41838455200195,49.96641159057617,4.043550968170166,57.47618865966797,51.99249267578125,3.0694143772125244,57.99717330932617,54.1519775390625,2.237022638320923,58.36261749267578,55.99559783935547,1.6294550895690918,58.663978576660156,57.72461700439453,1.2281360626220703,58.93765640258789,59.26199722290039,0.8294920921325684,59.60144805908203,61.03555679321289,0.502044677734375,60.298973083496094,62.706275939941406,0.2059178352355957,61.077850341796875,64.24629974365234,-0.043511390686035156,61.78984832763672,65.85041046142578,-0.26275634765625,62.29792022705078,67.51805114746094,-0.45856142044067383,62.59745407104492,69.37371063232422,-0.5314559936523438,62.61785125732422,71.58712005615234,-0.4283785820007324,62.40882873535156,74.14940643310547,-0.22312498092651367,62.18868637084961,76.63622283935547,0.1340789794921875,61.9525146484375,78.8487319946289,0.6770553588867188,61.100486755371094,81.03330993652344,1.2173471450805664,59.97623062133789,83.0361557006836,1.6553888320922852,59.060874938964844,84.85073852539062,1.649247169494629,58.67424011230469,86.39253997802734,1.4826478958129883,58.479610443115234,87.64776611328125],"type":"vector"},{"name":"Hips.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.4877471923828125,0.4612392783164978,-0.020773597061634064,0.7408977150917053,0.4725467562675476,0.46257683634757996,-0.036158397793769836,0.7492762207984924,0.4553796350955963,0.4605802297592163,-0.05224943906068802,0.7601087093353271,0.44065359234809875,0.4583767056465149,-0.06586562842130661,0.7690103650093079,0.42608460783958435,0.4588955044746399,-0.07780459523200989,0.7757662534713745,0.4147455096244812,0.45767495036125183,-0.0867004245519638,0.7816666960716248,0.4054122269153595,0.45504528284072876,-0.09127480536699295,0.7875555157661438,0.39886462688446045,0.45031270384788513,-0.09272219240665436,0.7934280633926392,0.3952375650405884,0.43850743770599365,-0.08836094290018082,0.8023034334182739,0.3904210329055786,0.42445316910743713,-0.07870731502771378,0.8131519556045532,0.3855946958065033,0.4088069498538971,-0.06420916318893433,0.8246640563011169,0.38004374504089355,0.39021527767181396,-0.0426216758787632,0.8375453352928162,0.3668314814567566,0.36372166872024536,-0.014246123842895031,0.8561181426048279,0.3438691794872284,0.3199540972709656,0.02079314924776554,0.8825820088386536,0.31217655539512634,0.26456916332244873,0.055793751031160355,0.9107337594032288,0.2847016453742981,0.21044398844242096,0.08356261253356934,0.9314910769462585,0.27148059010505676,0.16117791831493378,0.10857218503952026,0.942619800567627,0.2709077298641205,0.1110241487622261,0.13033196330070496,0.9472572207450867,0.2757686674594879,0.05488036945462227,0.1514018326997757,0.9476377367973328,0.2882617712020874,0.007804058957844973,0.16862590610980988,0.9425548315048218,0.3064255714416504,-0.025215549394488335,0.1812686324119568,0.9341355562210083,0.3175475597381592,-0.07063020765781403,0.19471652805805206,0.92534339427948,0.32744941115379333,-0.11632676422595978,0.20585182309150696,0.9148060083389282,0.3340810537338257,-0.1473412811756134,0.21292734146118164,0.9062793850898743,0.3412227928638458,-0.17818784713745117,0.2155357450246811,0.8974187970161438,0.34999555349349976,-0.21880292892456055,0.21376271545886993,0.8854004144668579,0.35596102476119995,-0.25858253240585327,0.21300078928470612,0.87238609790802,0.35232165455818176,-0.29382890462875366,0.21792596578598022,0.8614187836647034,0.3416656255722046,-0.32345694303512573,0.2233634889125824,0.8536679744720459,0.3281574249267578,-0.3440249264240265,0.2303355485200882,0.8490613698959351,0.3117934465408325,-0.35816359519958496,0.2386574149131775,0.8470810651779175,0.29567503929138184,-0.36701250076293945,0.24840600788593292,0.8462698459625244,0.28261542320251465,-0.3665550947189331,0.25502729415893555,0.8489564061164856,0.27287033200263977,-0.358834832906723,0.25809893012046814,0.8544965386390686,0.26556241512298584,-0.3488253653049469,0.2586572468280792,0.8607519865036011,0.26213982701301575,-0.33910125494003296,0.25563496351242065,0.8665701746940613,0.2612076699733734,-0.32478296756744385,0.24946171045303345,0.8741026520729065,0.26210886240005493,-0.29823192954063416,0.23644503951072693,0.8868204355239868,0.2644706070423126,-0.25808852910995483,0.217321515083313,0.9034472703933716,0.267517626285553,-0.21051830053329468,0.19266732037067413,0.9203236699104309,0.27082303166389465,-0.15962161123752594,0.16332517564296722,0.9351474642753601,0.2727128267288208,-0.12234916538000107,0.137055441737175,0.9443908929824829,0.2822928726673126,-0.08674536645412445,0.119700588285923,0.947870135307312,0.29416725039482117,-0.05090134218335152,0.10400675982236862,0.948713481426239,0.3123658001422882,-0.00975467637181282,0.09132494032382965,0.9455115795135498,0.3328399360179901,0.03583741560578346,0.07872746884822845,0.939007580280304],"type":"quaternion"},{"name":"LeftUpLeg.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.6253519058227539,-0.22266988456249237,0.08189317584037781,0.7434020042419434,0.6553272604942322,-0.18937478959560394,0.08671411126852036,0.7260606288909912,0.6833246946334839,-0.15162278711795807,0.09307374060153961,0.7081066966056824,0.7050536274909973,-0.11628133803606033,0.10272146016359329,0.6919727921485901,0.725274920463562,-0.08260700106620789,0.11625709384679794,0.6735255122184753,0.7435319423675537,-0.049869097769260406,0.13187122344970703,0.6536690592765808,0.7595457434654236,-0.02108030766248703,0.14872980117797852,0.6328706741333008,0.7748403549194336,0.006838604342192411,0.1631435602903366,0.6107043027877808,0.7887147665023804,0.032161224633455276,0.1715104579925537,0.589473307132721,0.8010599613189697,0.04812260717153549,0.17240546643733978,0.5711947679519653,0.8120501041412354,0.05261339247226715,0.16657181084156036,0.5568305850028992,0.8233454823493958,0.04877171292901039,0.16043958067893982,0.5422016978263855,0.8415704369544983,0.04275494068861008,0.15211093425750732,0.5165205001831055,0.8743936419487,0.0342339351773262,0.12874357402324677,0.4665713608264923,0.9125217199325562,0.025284793227910995,0.09791234880685806,0.3963305652141571,0.9431277513504028,0.015796417370438576,0.07151881605386734,0.32426154613494873,0.961471438407898,0.0014385776594281197,0.050178516656160355,0.2702826261520386,0.9708435535430908,-0.013939996249973774,0.02716655656695366,0.23776130378246307,0.977417528629303,-0.03387577086687088,-0.00042397237848490477,0.20858395099639893,0.9788419008255005,-0.05263010784983635,-0.020565854385495186,0.19666127860546112,0.975688099861145,-0.06483474373817444,-0.030484884977340698,0.207122802734375,0.9760733842849731,-0.07286591827869415,-0.04946068301796913,0.19880880415439606,0.9774028658866882,-0.08452507108449936,-0.06531209498643875,0.18241062760353088,0.9786666631698608,-0.09796920418739319,-0.06320154666900635,0.16917207837104797,0.9811913967132568,-0.10586128383874893,-0.055112000554800034,0.1517217457294464,0.9843056201934814,-0.12402892112731934,-0.04159857705235481,0.11844354122877121,0.9882962703704834,-0.129177063703537,-0.044275909662246704,0.06799595803022385,0.977654755115509,-0.14483636617660522,-0.08859823644161224,0.1239512711763382,0.965475857257843,-0.14319385588169098,-0.13664959371089935,0.1693481057882309,0.9543758034706116,-0.13725627958774567,-0.1745465248823166,0.19965235888957977,0.9475107192993164,-0.1274653673171997,-0.20741845667362213,0.2072525918483734,0.9339051842689514,-0.11729412525892258,-0.24287743866443634,0.2346779704093933,0.9162881970405579,-0.0963701456785202,-0.2695635259151459,0.2801147997379303,0.8949077129364014,-0.06914844363927841,-0.28407612442970276,0.3371341824531555,0.8673478960990906,-0.02547219954431057,-0.290824294090271,0.4030880928039551,0.8415985107421875,0.011601484380662441,-0.2937825620174408,0.4530663788318634,0.8124635219573975,0.04044179245829582,-0.29194095730781555,0.5030287504196167,0.7898662090301514,0.05451590195298195,-0.2781628668308258,0.5438425540924072,0.7763984203338623,0.05128249526023865,-0.2508901059627533,0.5758730173110962,0.7647883296012878,0.0425214022397995,-0.21449412405490875,0.6060388088226318,0.7532473206520081,0.0273487139493227,-0.1715417504310608,0.6343847513198853,0.7462988495826721,0.008873158134520054,-0.14139866828918457,0.6503582000732422,0.7511498332023621,-0.01353335753083229,-0.11434277892112732,0.650012731552124,0.7550995945930481,-0.02818142995238304,-0.08415006101131439,0.6495762467384338,0.7360278367996216,-0.043323397636413574,-0.04711826518177986,0.6739183664321899,0.7179001569747925,-0.05621183291077614,-0.003313317894935608,0.6938650012016296],"type":"quaternion"},{"name":"LeftLeg.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.3452269434928894,0.18956774473190308,0.1110309511423111,0.9124442934989929,0.3312785029411316,0.19538982212543488,0.11042557656764984,0.916451632976532,0.31639420986175537,0.19528084993362427,0.1088668629527092,0.9219046235084534,0.301932692527771,0.18979328870773315,0.10664939880371094,0.9281384944915771,0.28495100140571594,0.1817384660243988,0.10444667935371399,0.9353421330451965,0.2641885280609131,0.16846950352191925,0.10120780020952225,0.9442348480224609,0.24352124333381653,0.15436309576034546,0.0964982807636261,0.9526581764221191,0.21255595982074738,0.16293586790561676,0.09485580772161484,0.9587879180908203,0.18101230263710022,0.18634158372879028,0.0963105782866478,0.9608515501022339,0.16272935271263123,0.22113069891929626,0.10193286836147308,0.9561538100242615,0.1620500534772873,0.24945709109306335,0.10927348583936691,0.9484567642211914,0.1799641102552414,0.2505539655685425,0.11515011638402939,0.9442331194877625,0.20682275295257568,0.23122049868106842,0.1194014847278595,0.9431355595588684,0.22060590982437134,0.20929017663002014,0.11969161778688431,0.9450950026512146,0.22224868834018707,0.18849526345729828,0.11690851300954819,0.9494248032569885,0.209407776594162,0.1676456332206726,0.108859121799469,0.9571797251701355,0.19942401349544525,0.1456216275691986,0.10247752815485,0.963598906993866,0.20211286842823029,0.122922882437706,0.10066850483417511,0.9663882255554199,0.19613279402256012,0.09933948516845703,0.09455093741416931,0.9709396362304688,0.1935977041721344,0.07611630111932755,0.08931084722280502,0.9740378856658936,0.20937834680080414,0.05447093024849892,0.08918140083551407,0.9722347259521484,0.20955784618854523,0.03230104222893715,0.10078421980142593,0.9720518589019775,0.1958904266357422,0.00743694044649601,0.10284709930419922,0.9751893281936646,0.18500028550624847,-0.022109240293502808,0.09168809652328491,0.9782021641731262,0.16301530599594116,-0.057570960372686386,0.08276958018541336,0.9814585447311401,0.10939353704452515,-0.1182728111743927,0.06524365395307541,0.9847780466079712,0.03220051899552345,-0.1591663956642151,0.09428355097770691,0.982211709022522,0.18502934277057648,-0.1909462809562683,0.05527309700846672,0.9624180793762207,0.3128984272480011,-0.22313053905963898,0.02833111211657524,0.9227701425552368,0.38525038957595825,-0.2467990517616272,-0.00681311683729291,0.8891715407371521,0.40715596079826355,-0.23870332539081573,-0.01727244071662426,0.8814456462860107,0.44022881984710693,-0.21592889726161957,-0.026632938534021378,0.8711280226707458,0.48291143774986267,-0.1844659447669983,-0.03008451499044895,0.8554904460906982,0.5187453031539917,-0.15606842935085297,-0.028958482667803764,0.8400639295578003,0.5383954644203186,-0.11201107501983643,-0.007244381587952375,0.8351834416389465,0.5316133499145508,-0.06180492788553238,0.02671874687075615,0.8443065285682678,0.501296877861023,-0.010072256438434124,0.06132792681455612,0.8630405068397522,0.44665753841400146,0.03928423672914505,0.08897080272436142,0.8894031643867493,0.3875059187412262,0.08287027478218079,0.1046067327260971,0.9121562838554382,0.36903247237205505,0.11271447688341141,0.11216825246810913,0.9157122373580933,0.3836852014064789,0.13692736625671387,0.11757026612758636,0.9056565761566162,0.40749987959861755,0.15810728073120117,0.1231527030467987,0.8909429907798767,0.39548665285110474,0.18305523693561554,0.12792621552944183,0.8909074068069458,0.3898361027240753,0.2072553038597107,0.13282541930675507,0.8873728513717651,0.41368070244789124,0.2183297723531723,0.13251830637454987,0.8738645911216736,0.42321908473968506,0.22208842635154724,0.12550778687000275,0.8693734407424927],"type":"quaternion"},{"name":"LeftFoot.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[-0.4268278479576111,0.027297265827655792,-0.06415598839521408,0.9016411900520325,-0.43858009576797485,0.02283492684364319,-0.05784663185477257,0.896537721157074,-0.4479284882545471,0.017790358513593674,-0.05135050043463707,0.89241623878479,-0.4547557532787323,0.012455844320356846,-0.044437557458877563,0.889419674873352,-0.459746778011322,0.007508276961743832,-0.03721083700656891,0.8872383236885071,-0.4634936451911926,0.0020996718667447567,-0.029272103682160378,0.8856140971183777,-0.4672389328479767,-0.003930677659809589,-0.019947491586208344,0.8838973045349121,-0.4673466980457306,-0.012235412374138832,-0.004729586187750101,0.8839768171310425,-0.46946582198143005,-0.020277151837944984,0.011086777783930302,0.8826481699943542,-0.478310763835907,-0.02398882992565632,0.019795449450612068,0.8776397109031677,-0.4936230480670929,-0.022207675501704216,0.01840139366686344,0.86919766664505,-0.5165289044380188,-0.017671853303909302,0.011135286651551723,0.8560150265693665,-0.5457350611686707,-0.012237031012773514,0.002284480258822441,0.8378652930259705,-0.5742567777633667,-0.007046493701636791,-0.005628311540931463,0.8186256289482117,-0.6013652086257935,-0.0029639911372214556,-0.011571105569601059,0.7988849878311157,-0.6216185092926025,-0.0021911233197897673,-0.012114163488149643,0.7832234501838684,-0.641053318977356,-0.0009475317201577127,-0.012680616229772568,0.76739102602005,-0.6635688543319702,0.00146106060128659,-0.013908295892179012,0.7479845285415649,-0.6809779405593872,0.001210897695273161,-0.011033858172595501,0.7322198748588562,-0.6953961253166199,-0.00014517625095322728,-0.0070061879232525826,0.7185924649238586,-0.7126277685165405,-0.0009304046398028731,-0.003859884338453412,0.7015311121940613,-0.7265066504478455,0.0006740716053172946,-0.004614601843059063,0.6871436238288879,-0.7360203862190247,-0.0009306115680374205,-0.00013852723350282758,0.6769587397575378,-0.744647741317749,-0.0064260996878147125,0.010042463429272175,0.667351245880127,-0.7494728565216064,-0.012717085890471935,0.0192255899310112,0.6616337299346924,-0.7468062043190002,-0.009898616001009941,0.0086244260892272,0.6649121642112732,-0.7150892615318298,0.00877700001001358,-0.028666546568274498,0.6983899474143982,-0.7176574468612671,-0.0034063011407852173,-0.011064430698752403,0.6963001489639282,-0.6778335571289062,-0.018591666594147682,0.006945224013179541,0.7349474430084229,-0.5823605060577393,-0.03928104043006897,0.032091256231069565,0.811346709728241,-0.48364540934562683,-0.04574856534600258,0.030932096764445305,0.8735201358795166,-0.46174702048301697,-0.03189849853515625,0.008977288380265236,0.8863924741744995,-0.46089208126068115,-0.01626979373395443,-0.017163945361971855,0.8871410489082336,-0.47288817167282104,0.0017494854982942343,-0.046329379081726074,0.8799018859863281,-0.44725891947746277,0.012636709026992321,-0.064307302236557,0.8920001983642578,-0.41254377365112305,0.016148095950484276,-0.06980440020561218,0.9081157445907593,-0.35917872190475464,0.012707875110208988,-0.06399151682853699,0.930985689163208,-0.3000471591949463,0.003678402630612254,-0.04821005463600159,0.9526982307434082,-0.25601357221603394,-0.006728868931531906,-0.028231339529156685,0.9662374258041382,-0.27049532532691956,-0.012443087063729763,-0.013460892252624035,0.9625467658042908,-0.3214040994644165,-0.01395240519195795,-0.005118860863149166,0.9468255639076233,-0.3850356638431549,-0.01313871517777443,-0.0013451854465529323,0.9228072166442871,-0.42258283495903015,-0.015501084737479687,-0.001191573217511177,0.9061909914016724,-0.4665580093860626,-0.013401619158685207,-0.007342859636992216,0.8843585848808289,-0.4791865944862366,-0.009159316308796406,-0.013235860504209995,0.8775655031204224,-0.484353631734848,-0.009376171976327896,-0.011130922473967075,0.8747512698173523],"type":"quaternion"},{"name":"RightUpLeg.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.7734711170196533,-0.04050315544009209,0.5891968011856079,0.23010684549808502,0.7500633597373962,-0.07454647868871689,0.5898155570030212,0.28976789116859436,0.7264847755432129,-0.09946344792842865,0.5872232913970947,0.34277650713920593,0.7059716582298279,-0.12009894847869873,0.5853298902511597,0.38022270798683167,0.6935592293739319,-0.13742482662200928,0.579328179359436,0.4055475890636444,0.6886518597602844,-0.15329690277576447,0.5706266760826111,0.4202902615070343,0.6899547576904297,-0.1684991717338562,0.5608733892440796,0.4254310727119446,0.6937041282653809,-0.17905190587043762,0.5494024157524109,0.42996758222579956,0.6993701457977295,-0.18546441197395325,0.5353018045425415,0.4358169734477997,0.6999914646148682,-0.2011832445859909,0.520261824131012,0.44594281911849976,0.700995147228241,-0.21544088423252106,0.5067506432533264,0.4532051980495453,0.697606086730957,-0.22729796171188354,0.49214038252830505,0.4684862196445465,0.6941450238227844,-0.23226283490657806,0.47558918595314026,0.48788487911224365,0.6969572305679321,-0.2299680858850479,0.4608480632305145,0.4989834129810333,0.7042404413223267,-0.22231975197792053,0.444960355758667,0.5065863132476807,0.7084018588066101,-0.21246761083602905,0.42539992928504944,0.5215930938720703,0.7058565020561218,-0.20702619850635529,0.4035816490650177,0.5440852642059326,0.7011922597885132,-0.20221608877182007,0.38105374574661255,0.567658543586731,0.7016341686248779,-0.1870218813419342,0.3560117185115814,0.5882075428962708,0.7042720913887024,-0.17938819527626038,0.3141823709011078,0.6108275055885315,0.6970039010047913,-0.18588893115520477,0.2572140097618103,0.6430177688598633,0.6889363527297974,-0.18576835095882416,0.19818326830863953,0.6719973087310791,0.6820439100265503,-0.1812431365251541,0.14194397628307343,0.6941317915916443,0.6735635995864868,-0.18121710419654846,0.0951293408870697,0.710227370262146,0.666612982749939,-0.17304910719394684,0.05549516901373863,0.7229118347167969,0.6671510338783264,-0.1523236781358719,0.018628794699907303,0.7289444804191589,0.6743783950805664,-0.13177111744880676,-0.011678550392389297,0.7264391779899597,0.6903746128082275,-0.1190139576792717,-0.03246689215302467,0.7128565311431885,0.7053083181381226,-0.1108039915561676,-0.047372397035360336,0.69858318567276,0.7181994318962097,-0.11105292290449142,-0.05598943680524826,0.6846327781677246,0.7299246788024902,-0.11803197115659714,-0.05969177186489105,0.6706082224845886,0.7467522025108337,-0.1341727077960968,-0.05661863461136818,0.6489631533622742,0.7642244100570679,-0.1506342589855194,-0.05080198124051094,0.6250516176223755,0.7830899357795715,-0.16681818664073944,-0.042146217077970505,0.5976332426071167,0.8003964424133301,-0.18155533075332642,-0.026248332113027573,0.5707138180732727,0.8149315118789673,-0.19013556838035583,-0.0014441445237025619,0.5474787950515747,0.8279067873954773,-0.19807082414627075,0.031085342168807983,0.5238053202629089,0.8430086374282837,-0.20670418441295624,0.07049352675676346,0.4915693700313568,0.861158549785614,-0.21377474069595337,0.11540903151035309,0.4465278089046478,0.8816911578178406,-0.21344146132469177,0.16503944993019104,0.387072890996933,0.9014168977737427,-0.20392262935638428,0.21672135591506958,0.31447580456733704,0.9111107587814331,-0.19531171023845673,0.26161903142929077,0.25156715512275696,0.9127421975135803,-0.18946322798728943,0.29772016406059265,0.20583516359329224,0.9127086400985718,-0.17313392460346222,0.33023351430892944,0.16713328659534454,0.9104411602020264,-0.14372093975543976,0.3613303303718567,0.14100223779678345,0.904035747051239,-0.11225160211324692,0.39038798213005066,0.1331024169921875],"type":"quaternion"},{"name":"RightLeg.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.27418652176856995,-0.059042494744062424,-0.08969072252511978,0.9556627869606018,0.4113140106201172,0.011435012333095074,-0.051882095634937286,0.9099441170692444,0.5042073726654053,0.018848422914743423,-0.03698055446147919,0.8625845909118652,0.5520215630531311,0.011529880575835705,-0.04216308519244194,0.8326833844184875,0.565683126449585,0.005272923037409782,-0.05392254516482353,0.8228409290313721,0.556046187877655,0.00021026529429946095,-0.06920355558395386,0.828265368938446,0.5271709561347961,0.0009920364245772362,-0.08299240469932556,0.845696210861206,0.4800255596637726,0.008640613406896591,-0.08892673999071121,0.872692883014679,0.41606974601745605,0.026413876563310623,-0.08511628955602646,0.9049549698829651,0.36637330055236816,0.04060002788901329,-0.08240190148353577,0.9259222745895386,0.31503233313560486,0.04592921957373619,-0.0835832878947258,0.9442769289016724,0.26559537649154663,0.0008351196884177625,-0.09176672250032425,0.9597069025039673,0.2284448742866516,-0.07103745639324188,-0.09020503610372543,0.9667624831199646,0.20962047576904297,-0.11431841552257538,-0.08740997314453125,0.967134952545166,0.21215850114822388,-0.11449981480836868,-0.08246282488107681,0.9669945240020752,0.24937239289283752,-0.09349949657917023,-0.0674852579832077,0.9615181088447571,0.2866468131542206,-0.08336372673511505,-0.05631760507822037,0.9527394771575928,0.29230812191963196,-0.12239574640989304,-0.06953591108322144,0.9459070563316345,0.270976185798645,-0.15845105051994324,-0.07922448962926865,0.9461441040039062,0.2602631449699402,-0.1622026562690735,-0.079165980219841,0.948517918586731,0.2839089632034302,-0.13863788545131683,-0.07150769978761673,0.9460771083831787,0.30871695280075073,-0.12749822437763214,-0.0680341124534607,0.9401113986968994,0.32659733295440674,-0.11951944231987,-0.06249936297535896,0.9354908466339111,0.35368812084198,-0.1084718406200409,-0.053321029990911484,0.9275211095809937,0.37816253304481506,-0.1019497662782669,-0.04595819115638733,0.9189598560333252,0.3884955048561096,-0.10358139127492905,-0.03933265060186386,0.9147650599479675,0.3908975422382355,-0.11153791099786758,-0.03222193568944931,0.9130827784538269,0.38624662160873413,-0.12713171541690826,-0.02666885033249855,0.9132030606269836,0.38904979825019836,-0.1449848860502243,-0.02113999053835869,0.9094904065132141,0.39752858877182007,-0.16156314313411713,-0.014566756784915924,0.9031368494033813,0.4075220823287964,-0.17722637951374054,-0.008574413135647774,0.8957918882369995,0.4038148522377014,-0.19082947075366974,-0.005578439217060804,0.8946991562843323,0.40135592222213745,-0.18906408548355103,0.0023274461273103952,0.8961935043334961,0.3939107060432434,-0.17489801347255707,0.011039861477911472,0.9022877216339111,0.3838033676147461,-0.16215664148330688,0.014920946210622787,0.9089431166648865,0.3736519515514374,-0.15835040807724,0.013295207172632217,0.9138559103012085,0.36496415734291077,-0.16131922602653503,0.008022913709282875,0.9169039726257324,0.3581696152687073,-0.161213681101799,0.004370548762381077,0.919622540473938,0.34864315390586853,-0.15137651562690735,0.004974874667823315,0.9249369502067566,0.32060670852661133,-0.1366511434316635,0.0032116698566824198,0.9372979402542114,0.2673236131668091,-0.1206228956580162,-0.004633898846805096,0.9560161232948303,0.2226051688194275,-0.11913938075304031,-0.015185493044555187,0.9674823880195618,0.1901460587978363,-0.11422975361347198,-0.022992435842752457,0.974816620349884,0.1640634387731552,-0.09486735612154007,-0.026995673775672913,0.98150634765625,0.1429484635591507,-0.057655781507492065,-0.02792292833328247,0.9876547455787659,0.14496023952960968,-0.021063009276986122,-0.0266091488301754,0.9888553619384766],"type":"quaternion"},{"name":"RightFoot.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[-0.08362096548080444,-0.004706903826445341,0.08902259171009064,0.9925020933151245,-0.1572791337966919,-0.02749144472181797,0.12934209406375885,0.9786613583564758,-0.234464630484581,-0.041139088571071625,0.15204738080501556,0.9592786431312561,-0.28558239340782166,-0.04035281762480736,0.14971189200878143,0.9457275867462158,-0.3166700005531311,-0.02881397306919098,0.13023561239242554,0.9391105771064758,-0.3547137379646301,-0.0018943512113764882,0.08485444635152817,0.9311145544052124,-0.38794246315956116,0.028646791353821754,0.03293118625879288,0.920649528503418,-0.41202306747436523,0.048439525067806244,-0.0004824821080546826,0.9098848104476929,-0.4211569130420685,0.058234166353940964,-0.016212064772844315,0.904971182346344,-0.4262833893299103,0.0648648589849472,-0.026309246197342873,0.9018774032592773,-0.424177348613739,0.06984159350395203,-0.03327058255672455,0.9022687077522278,-0.3852742910385132,0.050867464393377304,-0.0023088729940354824,0.9213961958885193,-0.33490830659866333,0.030228037387132645,0.02913244441151619,0.9413150548934937,-0.30231863260269165,0.01805783621966839,0.04834572970867157,0.9518088102340698,-0.2858907878398895,0.009807205758988857,0.06749133765697479,0.9558322429656982,-0.3153086006641388,0.009676587767899036,0.08241462707519531,0.9453542828559875,-0.3523511588573456,0.009750420227646828,0.08867760002613068,0.9316060543060303,-0.3694050908088684,0.01428394578397274,0.0763167068362236,0.9260192513465881,-0.3690972626209259,0.022816205397248268,0.05535125732421875,0.9274604916572571,-0.3744916617870331,0.027513757348060608,0.04379894584417343,0.9257864952087402,-0.3815751075744629,0.033960964530706406,0.02933668904006481,0.9232478141784668,-0.3892074227333069,0.03532501682639122,0.02514636144042015,0.9201290011405945,-0.39610251784324646,0.03806978464126587,0.018745476379990578,0.9172252416610718,-0.4116983115673065,0.04281065985560417,0.009459709748625755,0.9102649688720703,-0.42847952246665955,0.04605216532945633,0.0029430354479700327,0.9023723602294922,-0.44259458780288696,0.04820063337683678,-0.0023081700783222914,0.8954225182533264,-0.4578096866607666,0.05023607239127159,-0.007331145461648703,0.887599527835846,-0.4729306399822235,0.05116764456033707,-0.01002407819032669,0.8795555830001831,-0.48905402421951294,0.05209515988826752,-0.01248841267079115,0.8706068396568298,-0.5074355006217957,0.054151419550180435,-0.016959184780716896,0.8598193526268005,-0.5251283645629883,0.0566616915166378,-0.022089960053563118,0.8488473296165466,-0.5400160551071167,0.058721430599689484,-0.025642884895205498,0.8392121195793152,-0.5585383772850037,0.057544827461242676,-0.02291017398238182,0.8271629810333252,-0.5743418335914612,0.054819803684949875,-0.01752145029604435,0.8165900111198425,-0.5858684778213501,0.0519840344786644,-0.013129601255059242,0.8086305856704712,-0.59698086977005,0.04724915698170662,-0.005879115778952837,0.8008413314819336,-0.61078280210495,0.042906999588012695,0.0000932353432290256,0.79063481092453,-0.630271852016449,0.043041180819272995,-0.00041155717917717993,0.775180459022522,-0.6531417369842529,0.045678503811359406,-0.004647612571716309,0.7558424472808838,-0.668132483959198,0.04789283871650696,-0.008035014383494854,0.7424558997154236,-0.6701783537864685,0.04977543652057648,-0.011261314153671265,0.7404435276985168,-0.6725324392318726,0.05461821332573891,-0.021486014127731323,0.7377366423606873,-0.6774637699127197,0.06190600246191025,-0.03500301018357277,0.7321102023124695,-0.682876467704773,0.05928388610482216,-0.02970239147543907,0.7275183200836182,-0.6806302666664124,0.05058339610695839,-0.01335844025015831,0.7307567000389099,-0.6768175363540649,0.0428895503282547,0.0018011892680078745,0.7348981499671936],"type":"quaternion"},{"name":"Spine.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[-0.0021293519530445337,0.045452021062374115,-0.03370387852191925,0.9983955025672913,-0.0026184222660958767,0.03928675502538681,-0.028305428102612495,0.9988235831260681,-0.003092869883403182,0.033840399235486984,-0.021714724600315094,0.9991865158081055,-0.003532383358106017,0.028640367090702057,-0.01672985590994358,0.9994434714317322,-0.004238648805767298,0.022566620260477066,-0.012909285724163055,0.9996529817581177,-0.005216158926486969,0.01835460029542446,-0.010312971659004688,0.9997648000717163,-0.0061988262459635735,0.015216683968901634,-0.008688761852681637,0.9998273253440857,-0.007503044325858355,0.013771120458841324,-0.006734530441462994,0.999854326248169,-0.009642443619668484,0.013007006607949734,-0.005479572340846062,0.9998539090156555,-0.011257519014179707,0.012319508008658886,-0.004316166043281555,0.9998514652252197,-0.012450975365936756,0.011873762123286724,-0.00356812565587461,0.9998456239700317,-0.012775439769029617,0.011330196633934975,-0.0035319568123668432,0.999847948551178,-0.011520914733409882,0.009421630762517452,-0.0037193766329437494,0.9998823404312134,-0.009097286500036716,0.007102493662387133,-0.0037493896670639515,0.9999263882637024,-0.005651798564940691,0.004374222364276648,-0.003681872272863984,0.9999676942825317,-0.004307296592742205,-0.00019292203069198877,-0.002838502638041973,0.9999866485595703,-0.004857865162193775,-0.006550888530910015,-0.0016978781204670668,0.9999653100967407,-0.005850361660122871,-0.013504804112017155,0.002836924046278,0.9998877048492432,-0.006331014912575483,-0.01704534702003002,0.010289606638252735,0.9997817277908325,-0.005767982453107834,-0.01993316225707531,0.017479397356510162,0.9996318817138672,-0.0046098134480416775,-0.02251163311302662,0.01813078671693802,0.9995715618133545,-0.0020751000847667456,-0.026189642027020454,0.0188105721026659,0.9994778633117676,-0.0012563789496198297,-0.031578850001096725,0.019839361310005188,0.9993035793304443,-0.0014773714356124401,-0.037834055721759796,0.021215323358774185,0.9990577101707458,-0.00220368430018425,-0.04518584534525871,0.02432815358042717,0.9986799359321594,-0.0031552237924188375,-0.0513678640127182,0.029048379510641098,0.998252272605896,-0.004164467100054026,-0.05437808483839035,0.03204210102558136,0.997997522354126,-0.005092276725918055,-0.053131744265556335,0.03017488867044449,0.99811851978302,-0.005325869657099247,-0.05209553986787796,0.029140716418623924,0.9982026219367981,-0.005204909481108189,-0.051156654953956604,0.02873498760163784,0.9982635974884033,-0.0045935166999697685,-0.04933681711554527,0.0275970920920372,0.9983903169631958,-0.00461367703974247,-0.04649604856967926,0.02370322123169899,0.9986265301704407,-0.00453190365806222,-0.043674442917108536,0.019469719380140305,0.9988458156585693,-0.004792540334165096,-0.04121333360671997,0.01518583670258522,0.9990234971046448,-0.00576462596654892,-0.0379786416888237,0.011066948063671589,0.9992006421089172,-0.00704175466671586,-0.0346161387860775,0.007970266044139862,0.9993440508842468,-0.009064444340765476,-0.031908124685287476,0.006175777409225702,0.9994305968284607,-0.011415958404541016,-0.028854135423898697,0.005393030121922493,0.9995039105415344,-0.013524268753826618,-0.023927584290504456,0.004168778192251921,0.9996135234832764,-0.015173988416790962,-0.016720661893486977,0.002996206982061267,0.9997406005859375,-0.015502859838306904,-0.007261960301548243,0.0008548717014491558,0.9998530745506287,-0.014560632407665253,0.005099596921354532,0.00003679217115859501,0.9998810291290283,-0.012220347300171852,0.01734025776386261,0.0010973288444802165,0.9997743368148804,-0.009200411848723888,0.028307966887950897,0.001718053943477571,0.9995554089546204,-0.006167043931782246,0.03277601674199104,0.0008309216937050223,0.9994433522224426,-0.0020627870690077543,0.03729374706745148,-0.00037864703335799277,0.9993021488189697],"type":"quaternion"},{"name":"LeftShoulder.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.47830936312675476,0.6199969053268433,-0.5524798035621643,0.28563985228538513,0.49201712012290955,0.6146303415298462,-0.5349278450012207,0.30659592151641846,0.49966076016426086,0.6120659708976746,-0.5178669691085815,0.3279149830341339,0.5055290460586548,0.6072544455528259,-0.5024499893188477,0.3510364592075348,0.5129315853118896,0.59733647108078,-0.4886552095413208,0.3759077489376068,0.5194185972213745,0.5873140096664429,-0.4803988039493561,0.3930440843105316,0.5258929133415222,0.575943648815155,-0.47666341066360474,0.40560778975486755,0.5297795534133911,0.5667092204093933,-0.47607627511024475,0.41415682435035706,0.5328297019004822,0.5555064678192139,-0.4798275828361511,0.4210352301597595,0.5373682379722595,0.5448706150054932,-0.4829989969730377,0.4255155324935913,0.5434696674346924,0.5357820987701416,-0.48462048172950745,0.42745906114578247,0.5521619915962219,0.5283437967300415,-0.48338058590888977,0.42698153853416443,0.566278338432312,0.5213564038276672,-0.4756273627281189,0.42578741908073425,0.584522008895874,0.5160126686096191,-0.46282389760017395,0.42173340916633606,0.6064110398292542,0.5124461650848389,-0.4446393847465515,0.41468116641044617,0.6234152913093567,0.5026043653488159,-0.4282952845096588,0.4186949133872986,0.632626473903656,0.4855337142944336,-0.4168393015861511,0.43621760606765747,0.6347944736480713,0.4715171158313751,-0.40127161145210266,0.462264746427536,0.6303433179855347,0.47180086374282837,-0.38437798619270325,0.4820009469985962,0.6229584217071533,0.4802621901035309,-0.36126014590263367,0.5007616877555847,0.6253896951675415,0.4827248752117157,-0.3418408930301666,0.5089294910430908,0.6306062340736389,0.48163700103759766,-0.32272711396217346,0.5159542560577393,0.6344133019447327,0.4733688533306122,-0.3043123483657837,0.5299394130706787,0.6391491293907166,0.46393921971321106,-0.27985644340515137,0.5458288788795471,0.6407442092895508,0.45242705941200256,-0.25538283586502075,0.5652753710746765,0.6337482929229736,0.44869473576545715,-0.2294967919588089,0.5868282914161682,0.6207557916641235,0.451889306306839,-0.2066078931093216,0.6064417362213135,0.6069430708885193,0.4597412049770355,-0.19308781623840332,0.6188499927520752,0.597840428352356,0.4651414155960083,-0.18523497879505157,0.6260339021682739,0.5948330163955688,0.4678645431995392,-0.18536411225795746,0.6268306970596313,0.5966486930847168,0.46977996826171875,-0.196046382188797,0.6203893423080444,0.5978968739509583,0.4731431305408478,-0.2136905938386917,0.6107301712036133,0.5981650948524475,0.47909098863601685,-0.23493099212646484,0.5978946089744568,0.5956762433052063,0.4868047833442688,-0.258038192987442,0.5844717025756836,0.5892683863639832,0.49784204363822937,-0.2863028049468994,0.5682840347290039,0.578527569770813,0.5112122297286987,-0.3155895471572876,0.5516985058784485,0.5662805438041687,0.5214878916740417,-0.3457946181297302,0.5364726185798645,0.5520046353340149,0.5323487520217896,-0.3755338191986084,0.5204518437385559,0.5338200926780701,0.5477726459503174,-0.4054720103740692,0.5005735158920288,0.5115464329719543,0.5695001482963562,-0.4318114221096039,0.47699978947639465,0.4881707727909088,0.5950649976730347,-0.4536512494087219,0.4492078125476837,0.46063870191574097,0.6219043731689453,-0.47506946325302124,0.41875550150871277,0.44158339500427246,0.6404584050178528,-0.48693498969078064,0.39712926745414734,0.4264526069164276,0.6546677350997925,-0.49277693033218384,0.38303956389427185,0.41567620635032654,0.666713297367096,-0.49188077449798584,0.37518003582954407,0.4100208580493927,0.6835864782333374,-0.4840856194496155,0.360906720161438],"type":"quaternion"},{"name":"LeftArm.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.5324680805206299,-0.13961680233478546,0.4669557511806488,0.692052960395813,0.49685138463974,-0.14076396822929382,0.5104387402534485,0.6875874996185303,0.4517836570739746,-0.14555031061172485,0.5486586093902588,0.6882444620132446,0.40747082233428955,-0.15629921853542328,0.5786146521568298,0.6890161037445068,0.36935439705848694,-0.16501526534557343,0.5958806872367859,0.6937388181686401,0.3432779908180237,-0.16685394942760468,0.6097257137298584,0.6946614980697632,0.33190736174583435,-0.16336558759212494,0.6168695092201233,0.6947094798088074,0.32921653985977173,-0.15839365124702454,0.6192722320556641,0.6950035691261292,0.3311447203159332,-0.15495097637176514,0.6062062382698059,0.7062913179397583,0.32920312881469727,-0.15788057446479797,0.5871206521987915,0.7224876284599304,0.3225926458835602,-0.16521121561527252,0.5681965351104736,0.7387773394584656,0.3155440092086792,-0.17368094623088837,0.5403067469596863,0.7604837417602539,0.3124469518661499,-0.1807066947221756,0.4913202226161957,0.792670488357544,0.3127725422382355,-0.18447020649909973,0.40973737835884094,0.8368150591850281,0.3139713406562805,-0.17797636985778809,0.29408615827560425,0.8850196599960327,0.31471747159957886,-0.1600663810968399,0.144399493932724,0.9243811368942261,0.32026368379592896,-0.1360596865415573,-0.02111019194126129,0.9372690320014954,0.3335599899291992,-0.11037874966859818,-0.1538916379213333,0.9235104918479919,0.3425271809101105,-0.10559366643428802,-0.1949225217103958,0.9129788279533386,0.3441498577594757,-0.08487759530544281,-0.15577447414398193,0.9220038056373596,0.3346100449562073,-0.07274278253316879,-0.14505861699581146,0.9282793402671814,0.3232147991657257,-0.07536183297634125,-0.18501517176628113,0.9249984622001648,0.3075120151042938,-0.0665583461523056,-0.1907920092344284,0.9298412799835205,0.2960160970687866,-0.05301380529999733,-0.17650723457336426,0.9372348785400391,0.28052884340286255,-0.043065138161182404,-0.17959871888160706,0.9419093728065491,0.26543158292770386,-0.03172395005822182,-0.17763179540634155,0.9470937848091125,0.2540993094444275,-0.019578440114855766,-0.16907250881195068,0.9520844221115112,0.24112199246883392,-0.006666064262390137,-0.1580161154270172,0.9575211405754089,0.23227913677692413,-0.0000761865230742842,-0.16795513033866882,0.9580383896827698,0.23054225742816925,0.01260246243327856,-0.19056928157806396,0.9541356563568115,0.2343129813671112,0.030480002984404564,-0.22115987539291382,0.9461801052093506,0.2444000542163849,0.05354992300271988,-0.25623655319213867,0.9336722493171692,0.26108381152153015,0.0780617967247963,-0.28451859951019287,0.9191250205039978,0.2801724672317505,0.10188785940408707,-0.3034566342830658,0.9050062894821167,0.29701295495033264,0.12659092247486115,-0.318665474653244,0.8911848068237305,0.31081539392471313,0.1459053009748459,-0.3254898488521576,0.8810004591941833,0.3235246241092682,0.15986773371696472,-0.3293881416320801,0.8725122213363647,0.326364666223526,0.16095808148384094,-0.3220504820346832,0.8739920854568481,0.31675657629966736,0.14565549790859222,-0.2924309968948364,0.8904682993888855,0.29410359263420105,0.11521593481302261,-0.24008728563785553,0.9179251194000244,0.2676294445991516,0.06632330268621445,-0.15188488364219666,0.9491610527038574,0.246195986866951,0.0002045157307293266,-0.020726975053548813,0.9689984321594238,0.2435116022825241,-0.0744488388299942,0.13617947697639465,0.9573999643325806,0.2544500529766083,-0.13730576634407043,0.3138260841369629,0.9043868184089661,0.2703448235988617,-0.17771752178668976,0.4762324094772339,0.8176385760307312,0.28012993931770325,-0.20974501967430115,0.6030567288398743,0.7168381214141846],"type":"quaternion"},{"name":"LeftForeArm.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.17075860500335693,-0.048412200063467026,0.8164978623390198,0.5493897199630737,0.1401435136795044,-0.056457288563251495,0.8247390389442444,0.5449568033218384,0.11047716438770294,-0.06579261273145676,0.8320920467376709,0.5395265817642212,0.08844202756881714,-0.06676316261291504,0.8334535956382751,0.5413647294044495,0.07881679385900497,-0.06279146671295166,0.8310679197311401,0.546965479850769,0.0749983862042427,-0.05387559533119202,0.8249544501304626,0.5576045513153076,0.08016906678676605,-0.03882957994937897,0.8144291043281555,0.5733851194381714,0.09665713459253311,-0.02038407512009144,0.8036757111549377,0.5868110656738281,0.1240907534956932,-0.0020891553722321987,0.7966303825378418,0.5915886759757996,0.14969615638256073,0.013337621465325356,0.7918295860290527,0.5919620394706726,0.17455510795116425,0.025721650570631027,0.7901373505592346,0.5869853496551514,0.19993725419044495,0.03664838895201683,0.7905880808830261,0.5776265859603882,0.2247520238161087,0.04567771032452583,0.7929288148880005,0.5645033717155457,0.24279090762138367,0.046232543885707855,0.8039260506629944,0.5409418344497681,0.24157395958900452,0.04081233963370323,0.8234829902648926,0.5117148756980896,0.21682633459568024,0.036368101835250854,0.8353171944618225,0.5038936734199524,0.16512945294380188,0.04027985408902168,0.8271023631095886,0.5357345938682556,0.10857006162405014,0.039954300969839096,0.7813171148300171,0.613318681716919,0.0800882875919342,0.06346690654754639,0.697509765625,0.7092516422271729,0.08467885106801987,0.10305649787187576,0.6156107783317566,0.7766802906990051,0.07065801322460175,0.12446516007184982,0.5802170038223267,0.8017880916595459,0.036152493208646774,0.13825608789920807,0.581429123878479,0.800948441028595,0.0312790684401989,0.13851527869701385,0.5489867329597473,0.8236799836158752,0.04155908524990082,0.13524723052978516,0.5041347146034241,0.8519561290740967,0.04578894376754761,0.1330668181180954,0.466071218252182,0.8734839558601379,0.061763565987348557,0.13541316986083984,0.4163679778575897,0.8969315886497498,0.07576977461576462,0.1442756950855255,0.3640953004360199,0.9169940948486328,0.09738080948591232,0.173214390873909,0.32663267850875854,0.9240264892578125,0.11140774935483932,0.1937345266342163,0.3053821623325348,0.9256333112716675,0.1189802885055542,0.20025791227817535,0.2884467840194702,0.9287297129631042,0.12436708807945251,0.20443478226661682,0.2774456739425659,0.930463969707489,0.12795977294445038,0.20597445964813232,0.27062711119651794,0.9316446781158447,0.1261538714170456,0.1960364133119583,0.2673012614250183,0.9349892735481262,0.12284918129444122,0.18272413313388824,0.2650584578514099,0.9387565851211548,0.1153787299990654,0.16439610719680786,0.2700890898704529,0.9416547417640686,0.11008597165346146,0.1421830952167511,0.2598366141319275,0.9487623572349548,0.09885522723197937,0.10784335434436798,0.2500602602958679,0.9571141004562378,0.08912735432386398,0.08637351542711258,0.25522467494010925,0.9588828682899475,0.07805615663528442,0.07953349500894547,0.2921464443206787,0.9498590230941772,0.06865368038415909,0.09475406259298325,0.3791198134422302,0.9179196357727051,0.06597314774990082,0.10820743441581726,0.4901868402957916,0.8623546361923218,0.07918044924736023,0.11217022687196732,0.584452211856842,0.7997274398803711,0.09242517501115799,0.10057608783245087,0.6429725885391235,0.7536101937294006,0.09524035453796387,0.08686163276433945,0.6668215394020081,0.7339845895767212,0.08610203862190247,0.07256598025560379,0.6701156497001648,0.733665943145752,0.08453284204006195,0.06873290240764618,0.6693451404571533,0.7349197864532471],"type":"quaternion"},{"name":"RightShoulder.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.6002330183982849,-0.45034050941467285,0.037208154797554016,0.6599465012550354,0.5770204067230225,-0.48133403062820435,0.06112152338027954,0.6569849252700806,0.5559436082839966,-0.5073511004447937,0.08552528917789459,0.6528453826904297,0.5344892740249634,-0.5331392288208008,0.1107090413570404,0.6463956236839294,0.5110635757446289,-0.5607331991195679,0.1367238312959671,0.6369448900222778,0.4885592460632324,-0.5822541117668152,0.15620094537734985,0.6307861804962158,0.469343900680542,-0.5987471342086792,0.17099833488464355,0.6260812878608704,0.45747610926628113,-0.6063948273658752,0.1833234429359436,0.6240139603614807,0.45036664605140686,-0.6077004671096802,0.19410821795463562,0.6246535181999207,0.45375362038612366,-0.603978157043457,0.20903140306472778,0.62098628282547,0.46690788865089417,-0.5951486229896545,0.23393356800079346,0.610794723033905,0.48724365234375,-0.583438515663147,0.2652178406715393,0.5931717157363892,0.5113871097564697,-0.573932945728302,0.30093711614608765,0.5643767714500427,0.5354017615318298,-0.5660839676856995,0.3372834026813507,0.5283311605453491,0.5560071468353271,-0.5614501237869263,0.3718157112598419,0.48721951246261597,0.564652144908905,-0.5577757358551025,0.4127674996852875,0.44685256481170654,0.5603377819061279,-0.5551238059997559,0.4577809274196625,0.410238653421402,0.5506805777549744,-0.5518648028373718,0.5020144581794739,0.3744029998779297,0.5448386669158936,-0.5481345057487488,0.5306971669197083,0.34793657064437866,0.5369653701782227,-0.5499070882797241,0.5532276630401611,0.3212624788284302,0.5164270401000977,-0.5692718625068665,0.5652227997779846,0.29959285259246826,0.48886364698410034,-0.5973488092422485,0.5725081562995911,0.27644404768943787,0.4521569013595581,-0.6248369812965393,0.583040714263916,0.25533607602119446,0.4115907549858093,-0.6490342020988464,0.5946605801582336,0.23606452345848083,0.3729305565357208,-0.6684096455574036,0.6051539182662964,0.21895234286785126,0.3458808362483978,-0.680185079574585,0.6140273809432983,0.20170556008815765,0.3278017044067383,-0.6894203424453735,0.6183496117591858,0.1867871880531311,0.31511303782463074,-0.6987021565437317,0.6158763766288757,0.1822509914636612,0.30023670196533203,-0.7116171717643738,0.6081421971321106,0.18336328864097595,0.2842482924461365,-0.7261942028999329,0.5963979363441467,0.19014312326908112,0.26716816425323486,-0.7412670850753784,0.5802190899848938,0.2061314433813095,0.2499638795852661,-0.7541237473487854,0.5643482804298401,0.22433553636074066,0.23329924046993256,-0.7650691270828247,0.5486860275268555,0.2432784140110016,0.21829485893249512,-0.7728776335716248,0.5354716777801514,0.2612999975681305,0.2073744535446167,-0.7755701541900635,0.5246505737304688,0.2832464575767517,0.202786386013031,-0.773882269859314,0.5169786214828491,0.30449479818344116,0.20214782655239105,-0.7693682909011841,0.5116240978240967,0.3247299790382385,0.21268248558044434,-0.7597801089286804,0.5054588913917542,0.3493017256259918,0.24299970269203186,-0.7431740164756775,0.4960356056690216,0.3776138722896576,0.2985217869281769,-0.7156456112861633,0.47938665747642517,0.4110043942928314,0.3747614920139313,-0.6742575168609619,0.4496091306209564,0.4503135085105896,0.46197566390037537,-0.615354061126709,0.40549972653388977,0.49344488978385925,0.539925217628479,-0.5529477000236511,0.3580877184867859,0.5239302515983582,0.6042597889900208,-0.49199506640434265,0.31039735674858093,0.5444855690002441,0.6558011770248413,-0.4370899796485901,0.2638171911239624,0.5561274290084839,0.7033695578575134,-0.38494226336479187,0.2107032984495163,0.5591912865638733],"type":"quaternion"},{"name":"RightArm.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.23993687331676483,0.1918298453092575,-0.6474790573120117,0.6974256038665771,0.14841483533382416,0.21119210124015808,-0.6664326190948486,0.6994558572769165,0.04542350023984909,0.2282228022813797,-0.6698673367500305,0.7050736546516418,-0.062351029366254807,0.2357543259859085,-0.6539445519447327,0.7161626219749451,-0.16404664516448975,0.2383429855108261,-0.6183785796165466,0.7306773066520691,-0.2340053915977478,0.24075116217136383,-0.5655747056007385,0.7532633543014526,-0.28355836868286133,0.24166542291641235,-0.5144594311714172,0.7723496556282043,-0.3260095715522766,0.23280441761016846,-0.4624250531196594,0.7910012602806091,-0.3534505069255829,0.22142687439918518,-0.4212760329246521,0.8053380846977234,-0.3685096502304077,0.20796449482440948,-0.39914795756340027,0.813407838344574,-0.36883991956710815,0.1841285228729248,-0.39125537872314453,0.8227837681770325,-0.3441184163093567,0.1523953676223755,-0.40199601650238037,0.8347199559211731,-0.27814334630966187,0.15241408348083496,-0.47209471464157104,0.8225162029266357,-0.1266874223947525,0.15632838010787964,-0.5413559675216675,0.816361129283905,0.05661194398999214,0.15596434473991394,-0.5338743925094604,0.8291250467300415,0.21865621209144592,0.12499118596315384,-0.4671472907066345,0.8475494384765625,0.3109095096588135,0.06472335755825043,-0.3913955092430115,0.86368727684021,0.3433849811553955,0.03419315069913864,-0.3197861909866333,0.8824139833450317,0.318456768989563,0.034322261810302734,-0.2643887400627136,0.9096735119819641,0.28508302569389343,0.03104265406727791,-0.21865640580654144,0.932712972164154,0.2727741003036499,0.012899605557322502,-0.1637742668390274,0.9479482769966125,0.2791607975959778,-0.004945920780301094,-0.10321857780218124,0.954667866230011,0.291000634431839,-0.028076430782675743,-0.05091067776083946,0.9549546837806702,0.28804606199264526,-0.027430837973952293,-0.004146921914070845,0.9572146534919739,0.2750208377838135,-0.017899256199598312,0.009072146378457546,0.9612288475036621,0.2589600086212158,-0.00592392822727561,-0.007849372923374176,0.9658380150794983,0.24658434092998505,0.00672856206074357,-0.03969574347138405,0.9682846665382385,0.23065847158432007,0.019702579826116562,-0.0712408572435379,0.9702233076095581,0.21232454478740692,0.03247295692563057,-0.10188378393650055,0.9713307619094849,0.19304904341697693,0.04090438410639763,-0.12122558802366257,0.9728120565414429,0.17054125666618347,0.046911343932151794,-0.12852545082569122,0.9758054614067078,0.14339560270309448,0.05111207067966461,-0.1306895911693573,0.9796660542488098,0.1221361979842186,0.055621981620788574,-0.12857891619205475,0.9825764298439026,0.10476106405258179,0.06274358928203583,-0.1329846978187561,0.9835667014122009,0.08515280485153198,0.07284989207983017,-0.14427122473716736,0.9831722974777222,0.06120621785521507,0.0945982113480568,-0.17818838357925415,0.9775243401527405,0.037319380789995193,0.11838076263666153,-0.22618192434310913,0.9661443829536438,0.016423098742961884,0.14257559180259705,-0.27358514070510864,0.9510802626609802,0.002734482055529952,0.16345837712287903,-0.3079570531845093,0.9372493624687195,-0.00670516025274992,0.22072899341583252,-0.3261992633342743,0.9191451668739319,-0.01351859513670206,0.28569915890693665,-0.3330194056034088,0.8984939455986023,-0.02258460968732834,0.34637367725372314,-0.3466617166996002,0.8714016675949097,-0.019396739080548286,0.3844204843044281,-0.36934688687324524,0.8458295464515686,-0.0020931276958435774,0.38469237089157104,-0.3953341245651245,0.8340973258018494,0.058268800377845764,0.34967732429504395,-0.43619436025619507,0.8270822763442993,0.1452014446258545,0.30991557240486145,-0.4652828574180603,0.8163214921951294],"type":"quaternion"},{"name":"RightForeArm.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.11386403441429138,-0.04313250258564949,-0.37832698225975037,0.9176291823387146,0.07408256828784943,0.004605441354215145,-0.3984752893447876,0.9141706824302673,0.01677832193672657,0.05686607211828232,-0.41096776723861694,0.9097198843955994,-0.052960868924856186,0.10811427980661392,-0.4184597134590149,0.9002209901809692,-0.1283198446035385,0.1542869657278061,-0.4196685254573822,0.8852162957191467,-0.2062903791666031,0.19634182751178741,-0.4181023836135864,0.8626033663749695,-0.2739296853542328,0.2287738174200058,-0.39756643772125244,0.8453200459480286,-0.3236995339393616,0.2449365109205246,-0.3655601739883423,0.8376100063323975,-0.3594207763671875,0.2508293092250824,-0.3401406407356262,0.8319889903068542,-0.38286247849464417,0.255038857460022,-0.32442429661750793,0.826511025428772,-0.3921474814414978,0.26584282517433167,-0.3343799114227295,0.8147011995315552,-0.3795160949230194,0.26945069432258606,-0.3347053527832031,0.8193511366844177,-0.3003181219100952,0.21798202395439148,-0.2566007375717163,0.8924399018287659,-0.1740264594554901,0.17765730619430542,-0.17920641601085663,0.9518601298332214,-0.046890195459127426,0.14528605341911316,-0.14204122126102448,0.9780171513557434,0.06552854925394058,0.12351862341165543,-0.12939366698265076,0.9816855192184448,0.11565395444631577,0.11176848411560059,-0.10641315579414368,0.9812279343605042,0.11783566325902939,0.10892891883850098,-0.06102105602622032,0.9851526021957397,0.09586050361394882,0.09387382119894028,-0.0602748841047287,0.9891235828399658,0.0649493858218193,0.0834670439362526,-0.03823208808898926,0.9936564564704895,0.02083607017993927,0.07444647699594498,-0.019480885937809944,0.9968169927597046,0.021565988659858704,0.06797339767217636,-0.007590955123305321,0.9974251389503479,0.037107255309820175,0.06537735462188721,-0.010475031100213528,0.997115433216095,0.031141286715865135,0.07372381538152695,-0.008844980970025063,0.9967531561851501,0.013040531426668167,0.10202821344137192,-0.0043886806815862656,0.9946863651275635,-0.008908635936677456,0.1388748437166214,-0.0017896813806146383,0.9902682900428772,-0.038459885865449905,0.18737806379795074,-0.007042487617582083,0.9815093874931335,-0.07671723514795303,0.24857258796691895,-0.022255435585975647,0.9653138518333435,-0.11724092811346054,0.31276214122772217,-0.042528387159109116,0.9416080713272095,-0.15407080948352814,0.36831650137901306,-0.06848040223121643,0.9142841696739197,-0.1876469850540161,0.41592422127723694,-0.10133431106805801,0.8840401768684387,-0.21867147088050842,0.45845305919647217,-0.13515299558639526,0.8507274985313416,-0.2458455115556717,0.4930714964866638,-0.16647548973560333,0.8177569508552551,-0.2708030343055725,0.5210443139076233,-0.1966833472251892,0.7851715087890625,-0.2962498366832733,0.5466867685317993,-0.22606833279132843,0.7498418092727661,-0.32229557633399963,0.5683159828186035,-0.2549301087856293,0.7128486037254333,-0.3484598696231842,0.5826038718223572,-0.28630486130714417,0.6761494278907776,-0.37237679958343506,0.5864009261131287,-0.31792017817497253,0.6452877521514893,-0.3972666561603546,0.5739879608154297,-0.3471069633960724,0.6262858510017395,-0.4417741298675537,0.5172073841094971,-0.29434436559677124,0.6713371276855469,-0.48729872703552246,0.44635066390037537,-0.23191918432712555,0.7138098478317261,-0.4602245092391968,0.3923838436603546,-0.2204379439353943,0.7652682662010193,-0.34772437810897827,0.34227079153060913,-0.21952854096889496,0.8448347449302673,-0.2457166463136673,0.364738404750824,-0.2079223394393921,0.8737033605575562,-0.16455291211605072,0.4139679968357086,-0.13703350722789764,0.8847455382347107,-0.09779059886932373,0.4124009907245636,-0.09236623346805573,0.9010165929794312],"type":"quaternion"},{"name":"Head.quaternion","times":[0.01111110020428896,0.04444452002644539,0.07777782529592514,0.1111111268401146,0.14444442093372345,0.17777784168720245,0.2111111432313919,0.24444444477558136,0.277777761220932,0.311111181974411,0.34444448351860046,0.3777777850627899,0.41111108660697937,0.4444445073604584,0.4777778089046478,0.5111110806465149,0.5444443821907043,0.5777776837348938,0.6111112236976624,0.6444445252418518,0.6777778267860413,0.7111111283302307,0.7444444298744202,0.7777777314186096,0.8111110329627991,0.8444443345069885,0.8777778744697571,0.9111111760139465,0.944444477558136,0.9777777791023254,1.0111111402511597,1.0444444417953491,1.0777777433395386,1.1111112833023071,1.1444445848464966,1.177777886390686,1.2111111879348755,1.244444489479065,1.2777777910232544,1.3111110925674438,1.3444443941116333,1.3777779340744019,1.4111112356185913,1.4444445371627808,1.4777778387069702,1.5111111402511597],"values":[0.07681261748075485,-0.4142216742038727,0.006736618932336569,0.9069040417671204,0.09534371644258499,-0.40897008776664734,0.027456706389784813,0.907137930393219,0.11164695024490356,-0.4014396667480469,0.047914884984493256,0.9077914953231812,0.12198422849178314,-0.3953767418861389,0.06687209755182266,0.9079235792160034,0.12750279903411865,-0.3922310471534729,0.08415237814188004,0.9070922136306763,0.12520046532154083,-0.39033955335617065,0.09999193251132965,0.9066209197044373,0.12249166518449783,-0.38792264461517334,0.115139901638031,0.9062309861183167,0.11511731892824173,-0.385696679353714,0.1275470107793808,0.9064865708351135,0.1039002314209938,-0.3769626319408417,0.13554370403289795,0.9103471040725708,0.09807024896144867,-0.3642277121543884,0.1408601850271225,0.915357232093811,0.09463491290807724,-0.3488461375236511,0.14250247180461884,0.9214357137680054,0.09597423672676086,-0.32901856303215027,0.13506683707237244,0.929673433303833,0.11782588064670563,-0.2978346049785614,0.11424844712018967,0.9404035806655884,0.15790332853794098,-0.24573174118995667,0.08709190785884857,0.9524166584014893,0.21391969919204712,-0.1801658272743225,0.05818836763501167,0.958328127861023,0.2575702965259552,-0.1170835867524147,0.028711875900626183,0.9587098360061646,0.2706384062767029,-0.0595942884683609,-0.0011404462857171893,0.9608340859413147,0.2603309154510498,0.002002301160246134,-0.027371270582079887,0.9651293158531189,0.23655164241790771,0.06933807581663132,-0.04981817677617073,0.9678603410720825,0.2048925906419754,0.12203410267829895,-0.06576155871152878,0.9689180254936218,0.1740683615207672,0.15311673283576965,-0.06989219784736633,0.9702425599098206,0.15791897475719452,0.19219042360782623,-0.07380815595388412,0.9657519459724426,0.16473601758480072,0.22778475284576416,-0.07880758494138718,0.9564337730407715,0.16250750422477722,0.24804463982582092,-0.07783631980419159,0.9518438577651978,0.14866690337657928,0.26751071214675903,-0.06989377737045288,0.949447751045227,0.12844780087471008,0.2940511703491211,-0.05531821399927139,0.945502519607544,0.10488706082105637,0.3176157772541046,-0.03756044805049896,0.9416518211364746,0.08849593997001648,0.33552825450897217,-0.02236686646938324,0.9375974535942078,0.08263219892978668,0.350387841463089,-0.012779668904840946,0.9328649640083313,0.08495704084634781,0.36050301790237427,-0.0068198200315237045,0.9288559556007385,0.0914333313703537,0.3676801323890686,-0.006731367204338312,0.9254220724105835,0.10538274794816971,0.3744703531265259,-0.011202322319149971,0.9211628437042236,0.12216955423355103,0.37523049116134644,-0.014292825944721699,0.9187341332435608,0.1448337882757187,0.3708950877189636,-0.016618359833955765,0.9171607494354248,0.1653580665588379,0.3644934594631195,-0.01978451758623123,0.9161931276321411,0.1814967840909958,0.3587237000465393,-0.021752038970589638,0.915370523929596,0.19440072774887085,0.34906989336013794,-0.024238279089331627,0.9163902997970581,0.20128118991851807,0.326858252286911,-0.021653590723872185,0.9231363534927368,0.20670866966247559,0.2909497916698456,-0.015610853210091591,0.9340107440948486,0.20220863819122314,0.2459939867258072,-0.0049074310809373856,0.9479317665100098,0.19277842342853546,0.19487859308719635,0.008540615439414978,0.9616578817367554,0.19001367688179016,0.157842218875885,0.015137042850255966,0.9688919186592102,0.18156862258911133,0.1221618801355362,0.010875643230974674,0.975700318813324,0.17071250081062317,0.08594851940870285,0.004791052546352148,0.9815534949302673,0.15163567662239075,0.046742334961891174,-0.003902262542396784,0.9873229265213013,0.13473957777023315,-0.0020624001044780016,-0.008344796486198902,0.9908437728881836],"type":"quaternion"}],"uuid":"3b987981-dc8f-4377-95d5-d43cc95a0b2f","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/one-hand-combo-hit3.json b/public/anims/baked/sword_shield/one-hand-combo-hit3.json new file mode 100644 index 0000000..a75d737 --- /dev/null +++ b/public/anims/baked/sword_shield/one-hand-combo-hit3.json @@ -0,0 +1 @@ +{"name":"sword_shield/one-hand-sword-combo-hit3","duration":1.5222222010294595,"tracks":[{"name":"Hips.position","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[1.284482479095459,58.373958587646484,88.84265899658203,1.1425180435180664,58.283843994140625,89.95478820800781,0.9942507743835449,58.07050704956055,90.91831970214844,0.7870941162109375,57.71174621582031,91.76636505126953,0.6352939605712891,57.54385757446289,92.40302276611328,0.597902774810791,57.55157470703125,92.93280792236328,0.6329336166381836,57.60364532470703,93.31060791015625,0.6623029708862305,57.6735725402832,93.48802947998047,0.6506428718566895,57.74392318725586,93.59590148925781,0.6533102989196777,57.89592361450195,93.6672134399414,0.7182722091674805,58.005130767822266,93.54376220703125,0.8324069976806641,58.12457275390625,93.31049346923828,0.9628877639770508,58.180320739746094,92.86903381347656,1.1438016891479492,58.122440338134766,92.28075408935547,1.3387675285339355,57.98112487792969,91.61113739013672,1.5098953247070312,57.74934005737305,90.81010437011719,1.61976957321167,57.47880554199219,89.92535400390625,1.6188569068908691,57.27084732055664,89.01678466796875,1.5332651138305664,57.2153205871582,88.04105377197266,1.3398313522338867,57.41921615600586,87.03388214111328,1.0124025344848633,57.76841354370117,85.96669006347656,0.6637454032897949,58.12934112548828,84.88141632080078,0.31711244583129883,58.395938873291016,83.94519805908203,-0.057080745697021484,58.66661834716797,83.24140167236328,-0.4317793846130371,58.96745300292969,82.543212890625,-0.7296295166015625,59.16238021850586,81.85116577148438,-0.9078497886657715,59.221153259277344,81.17950439453125,-0.9629931449890137,59.133670806884766,80.5527114868164,-0.9370722770690918,58.794185638427734,80.05147552490234,-0.8627104759216309,58.24051284790039,79.65845489501953,-0.7553143501281738,57.56953811645508,79.29008483886719,-0.6282835006713867,56.878231048583984,78.93815612792969,-0.4929952621459961,56.29975128173828,78.60285949707031,-0.35947132110595703,55.9033317565918,78.29269409179688,-0.24126100540161133,55.704933166503906,77.9816665649414,-0.11040925979614258,55.682125091552734,77.67256164550781,-0.06344938278198242,55.80465316772461,77.37322998046875,-0.07973432540893555,55.998016357421875,77.09217834472656,-0.12400293350219727,56.17729187011719,76.83585357666016,-0.18832969665527344,56.31276321411133,76.60865020751953,-0.2649092674255371,56.390777587890625,76.41412353515625,-0.3459205627441406,56.39649963378906,76.2556381225586,-0.42384862899780273,56.37015151977539,76.13335418701172,-0.4909353256225586,56.34054946899414,76.04352569580078,-0.5392560958862305,56.31110763549805,75.98343658447266,-0.5604925155639648,56.27611541748047,75.96683502197266],"type":"vector"},{"name":"Hips.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.35276633501052856,0.07676809281110764,0.06944524496793747,0.9299677014350891,0.37332648038864136,0.11395247280597687,0.06332380324602127,0.9184945821762085,0.3934278190135956,0.1527591496706009,0.05922437459230423,0.9046390056610107,0.4120885729789734,0.19420798122882843,0.05729448050260544,0.8883600831031799,0.4281103014945984,0.23253269493579865,0.055231597274541855,0.8715501427650452,0.4425550401210785,0.26541852951049805,0.05544678866863251,0.8547652959823608,0.4534033536911011,0.2879897356033325,0.05814490467309952,0.841490626335144,0.45897695422172546,0.29538238048553467,0.06267800182104111,0.8355602025985718,0.45909860730171204,0.2896754741668701,0.06832920759916306,0.8370470404624939,0.45466721057891846,0.26926860213279724,0.07601764053106308,0.8455728888511658,0.44539839029312134,0.23687852919101715,0.08724156022071838,0.8590097427368164,0.43353402614593506,0.19417470693588257,0.10088800638914108,0.8741658926010132,0.41994184255599976,0.14155329763889313,0.11856784671545029,0.8885680437088013,0.4078109860420227,0.0894024521112442,0.13545218110084534,0.898526668548584,0.39443090558052063,0.03299520164728165,0.1528542935848236,0.9055225849151611,0.37990501523017883,-0.024937273934483528,0.16996751725673676,0.9089341759681702,0.3646472096443176,-0.08141876757144928,0.1861226111650467,0.9087143540382385,0.34937402606010437,-0.13346801698207855,0.2007167488336563,0.9054484367370605,0.3350992500782013,-0.17830979824066162,0.21310435235500336,0.9002781510353088,0.3229130506515503,-0.21346347033977509,0.2229008823633194,0.8946930766105652,0.3135809600353241,-0.23647715151309967,0.23042255640029907,0.8903095126152039,0.3080368936061859,-0.2518453896045685,0.23555462062358856,0.8866798877716064,0.30332306027412415,-0.26491522789001465,0.24006713926792145,0.8832796216011047,0.2993185520172119,-0.27600181102752686,0.2441023886203766,0.8801394701004028,0.29607465863227844,-0.2854764759540558,0.2474897801876068,0.8772638440132141,0.2934889793395996,-0.29353028535842896,0.2503327429294586,0.8746643662452698,0.2913535535335541,-0.30032068490982056,0.25288453698158264,0.8723359704017639,0.28953588008880615,-0.306080162525177,0.2552773356437683,0.8702398538589478,0.2879895567893982,-0.31105291843414307,0.25743040442466736,0.8683534264564514,0.28665849566459656,-0.31552305817604065,0.25928428769111633,0.8666278123855591,0.2854446470737457,-0.3197631239891052,0.2609480917453766,0.8649734258651733,0.28428030014038086,-0.3238932490348816,0.2624678313732147,0.8633588552474976,0.2834722101688385,-0.32726746797561646,0.2636549770832062,0.8619893789291382,0.28306418657302856,-0.3298761546611786,0.264480322599411,0.8608754873275757,0.28295668959617615,-0.33180174231529236,0.26509594917297363,0.8599810004234314,0.2830962836742401,-0.3331412672996521,0.2655221223831177,0.8592854142189026,0.2834526300430298,-0.3340027332305908,0.2657833993434906,0.8587526082992554,0.28398776054382324,-0.33446651697158813,0.26590752601623535,0.8583567142486572,0.2846572697162628,-0.3346068859100342,0.2659326195716858,0.8580724596977234,0.28541627526283264,-0.33451470732688904,0.26589447259902954,0.8578680753707886,0.28621846437454224,-0.33428478240966797,0.26583048701286316,0.8577102422714233,0.2870182991027832,-0.33401182293891907,0.26577821373939514,0.8575654625892639,0.28776636719703674,-0.3338046371936798,0.2657778561115265,0.857395589351654,0.28841742873191833,-0.33377110958099365,0.2658638656139374,0.8571631908416748,0.28892752528190613,-0.3340141475200653,0.26607105135917664,0.8568323254585266,0.28925901651382446,-0.33463647961616516,0.26642245054244995,0.856368362903595],"type":"quaternion"},{"name":"LeftUpLeg.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.7012354731559753,-0.06877859681844711,0.03465626761317253,0.7087575793266296,0.6840723752975464,-0.08155489712953568,0.06960570067167282,0.721490740776062,0.6646395921707153,-0.09555978327989578,0.10548444837331772,0.733481764793396,0.64298415184021,-0.11042207479476929,0.14350439608097076,0.744167149066925,0.6258465647697449,-0.12160149961709976,0.1783711165189743,0.7494751214981079,0.613971471786499,-0.1315649449825287,0.20696771144866943,0.7502626776695251,0.607146143913269,-0.14015571773052216,0.22577975690364838,0.7488346695899963,0.6063515543937683,-0.14665555953979492,0.23048418760299683,0.7467978000640869,0.6105480790138245,-0.1519399881362915,0.2229110598564148,0.7446179986000061,0.6209917068481445,-0.15620416402816772,0.20119889080524445,0.7412749528884888,0.6349354386329651,-0.1616688370704651,0.1677084118127823,0.7366098761558533,0.6509794592857361,-0.16626715660095215,0.12380775809288025,0.7302414774894714,0.6663386225700378,-0.170421302318573,0.06898319721221924,0.722627580165863,0.6756777763366699,-0.17246869206428528,0.014299428090453148,0.7165958285331726,0.6817436814308167,-0.17156201601028442,-0.04506592079997063,0.7097613215446472,0.6840308904647827,-0.1667947769165039,-0.10645737498998642,0.7021026015281677,0.6835840344429016,-0.15788374841213226,-0.16666127741336823,0.6928272843360901,0.6825512051582336,-0.14531655609607697,-0.22243201732635498,0.6808310747146606,0.6823233366012573,-0.13104356825351715,-0.2707865238189697,0.6662861108779907,0.6851155161857605,-0.11732782423496246,-0.309243768453598,0.6490139961242676,0.6903886795043945,-0.10790880024433136,-0.33530884981155396,0.6318917274475098,0.6945196390151978,-0.10235103964805603,-0.35289615392684937,0.6185718178749084,0.697417676448822,-0.09860949218273163,-0.3677740693092346,0.6071466207504272,0.7010279297828674,-0.09539926797151566,-0.3804706335067749,0.5955677032470703,0.7053378820419312,-0.0914248675107956,-0.3916543424129486,0.5837351679801941,0.7082173824310303,-0.08669044822454453,-0.40166664123535156,0.5740877389907837,0.7087753415107727,-0.08304428309202194,-0.4101346731185913,0.5679177045822144,0.7066924571990967,-0.08037776499986649,-0.4172338843345642,0.5657216310501099,0.6999795436859131,-0.07788475602865219,-0.42317628860473633,0.5699862837791443,0.6894323825836182,-0.07502390444278717,-0.42834123969078064,0.5792911052703857,0.6770423054695129,-0.07160207629203796,-0.43313220143318176,0.5906634330749512,0.6645486950874329,-0.06790702044963837,-0.43765631318092346,0.601847767829895,0.6539938449859619,-0.06486175209283829,-0.44121065735816956,0.6110795140266418,0.6463053822517395,-0.06291183084249496,-0.44379258155822754,0.6175593733787537,0.6417917013168335,-0.0621035173535347,-0.4455762505531311,0.6210542321205139,0.6399831771850586,-0.06261597573757172,-0.44661644101142883,0.6221209764480591,0.640896201133728,-0.06329892575740814,-0.4473375678062439,0.6205919981002808,0.6433412432670593,-0.06391488015651703,-0.44780394434928894,0.6176557540893555,0.6458470821380615,-0.0643070787191391,-0.44800618290901184,0.6148468255996704,0.6478654146194458,-0.06435869634151459,-0.4480213522911072,0.6127032041549683,0.6490821242332458,-0.0640447586774826,-0.44792041182518005,0.6115211248397827,0.6491544246673584,-0.06333033740520477,-0.4477711021900177,0.6116281151771545,0.6486841440200806,-0.06248868256807327,-0.4476735293865204,0.6122846603393555,0.6481070518493652,-0.06173058971762657,-0.4476962089538574,0.6129556894302368,0.6474347114562988,-0.06110000237822533,-0.4479577839374542,0.6135380864143372,0.6466274261474609,-0.06048927828669548,-0.44828736782073975,0.6142088770866394],"type":"quaternion"},{"name":"LeftLeg.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.42620405554771423,0.22720485925674438,0.11861769109964371,0.8675585985183716,0.4250634014606476,0.23164166510105133,0.11120975762605667,0.8679260611534119,0.4216702878475189,0.23623026907444,0.10409458726644516,0.8692259788513184,0.41698047518730164,0.2399827241897583,0.0971948429942131,0.8712570071220398,0.4016793966293335,0.24456484615802765,0.09071577340364456,0.877845287322998,0.3774784505367279,0.24976585805416107,0.08513660728931427,0.8876253962516785,0.35447537899017334,0.2538233697414398,0.0804852694272995,0.8963498473167419,0.34067806601524353,0.2566768229007721,0.07724776118993759,0.901159405708313,0.3406507074832916,0.25825580954551697,0.07552281767129898,0.900864839553833,0.35101383924484253,0.2587878108024597,0.07534105330705643,0.8967396020889282,0.3692929446697235,0.25779828429222107,0.07588823139667511,0.8896088004112244,0.3903476893901825,0.2560370862483978,0.07642526179552078,0.8810408115386963,0.4120243787765503,0.25362157821655273,0.07701928168535233,0.8717683553695679,0.4321519434452057,0.2511666417121887,0.07781358808279037,0.8626152276992798,0.4539353549480438,0.2485044151544571,0.07880137860774994,0.8520438075065613,0.4754208028316498,0.2461923509836197,0.08020687103271484,0.8407920002937317,0.4941677749156952,0.24480809271335602,0.08234623819589615,0.8301122188568115,0.5067121982574463,0.2448391616344452,0.08537902683019638,0.8221964240074158,0.5094826221466064,0.24668042361736298,0.08922084420919418,0.8195217251777649,0.4990083575248718,0.2506488263607025,0.09365303069353104,0.8242542147636414,0.47987717390060425,0.24909593164920807,0.09392348676919937,0.8359709978103638,0.4603050649166107,0.2377449870109558,0.0884912982583046,0.8507443070411682,0.4456241726875305,0.22173067927360535,0.08105896413326263,0.8635300397872925,0.43124446272850037,0.20713885128498077,0.0751628577709198,0.8749127388000488,0.41051042079925537,0.20257419347763062,0.07393009215593338,0.885990560054779,0.3899468183517456,0.20458441972732544,0.07642107456922531,0.8945650458335876,0.37486031651496887,0.20655235648155212,0.07849754393100739,0.9003632664680481,0.3672550916671753,0.20836833119392395,0.07976934313774109,0.9029635787010193,0.3746173679828644,0.20912647247314453,0.0783715471625328,0.8998810052871704,0.39381152391433716,0.20916907489299774,0.07502973079681396,0.8919256925582886,0.41718536615371704,0.2087743878364563,0.07133694738149643,0.8816352486610413,0.4398788809776306,0.208375945687294,0.06812529265880585,0.8708875179290771,0.4569840729236603,0.20803479850292206,0.0656580701470375,0.8623086214065552,0.4670546054840088,0.20776419341564178,0.06378266215324402,0.857103168964386,0.46963950991630554,0.20766454935073853,0.06247889995574951,0.8558099269866943,0.4659879207611084,0.20764373242855072,0.061602141708135605,0.8578721284866333,0.4567136764526367,0.20803295075893402,0.06136883422732353,0.8627681136131287,0.4444657862186432,0.20871275663375854,0.06162574514746666,0.8689599633216858,0.4325424134731293,0.2093791663646698,0.062005624175071716,0.8747701048851013,0.422319233417511,0.2100280374288559,0.06238530948758125,0.8795696496963501,0.4146568179130554,0.21059025824069977,0.0626249685883522,0.8830569386482239,0.41048553586006165,0.21108198165893555,0.062620609998703,0.8848868608474731,0.4083709418773651,0.2114628106355667,0.06248325854539871,0.8857835531234741,0.40718528628349304,0.21167676150798798,0.06230601295828819,0.8862906098365784,0.4068085253238678,0.21179170906543732,0.06214802339673042,0.8864472508430481,0.4077579379081726,0.2136022299528122,0.06290263682603836,0.8855229020118713],"type":"quaternion"},{"name":"LeftFoot.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[-0.4876272678375244,-0.010707711800932884,-0.007891491055488586,0.872950553894043,-0.4892895221710205,-0.012980137951672077,-0.003787766210734844,0.8720166087150574,-0.48881053924560547,-0.016480501741170883,0.001773250289261341,0.8722324967384338,-0.4872437119483948,-0.020909732207655907,0.009413950145244598,0.8729649186134338,-0.48025503754615784,-0.02616974338889122,0.01836560294032097,0.8765460252761841,-0.469157874584198,-0.030928760766983032,0.026267828419804573,0.8821815848350525,-0.4587549567222595,-0.034192442893981934,0.03194873034954071,0.8873297572135925,-0.45293691754341125,-0.035571567714214325,0.03421121463179588,0.8901755213737488,-0.4546626806259155,-0.034472472965717316,0.03253437951207161,0.8894014954566956,-0.46268945932388306,-0.0306295994669199,0.02620939537882805,0.8856034278869629,-0.47430580854415894,-0.025092341005802155,0.016732659190893173,0.8798434138298035,-0.48805442452430725,-0.018562445417046547,0.005648262798786163,0.8725975155830383,-0.5024121403694153,-0.011889004148542881,-0.005550685338675976,0.8645287156105042,-0.5148949027061462,-0.006344352383166552,-0.014880786649882793,0.8571006655693054,-0.5287020206451416,-0.0011396931950002909,-0.023351984098553658,0.8484854698181152,-0.5421664714813232,0.0029415423050522804,-0.029713094234466553,0.8397404551506042,-0.5541379451751709,0.005389920901507139,-0.033447857946157455,0.8317351341247559,-0.5631160736083984,0.005981659982353449,-0.03402038663625717,0.8256555795669556,-0.5662668347358704,0.004511084407567978,-0.03134288266301155,0.8236135244369507,-0.5618603825569153,0.000863276538439095,-0.025570591911673546,0.826836347579956,-0.5517156720161438,-0.004969610366970301,-0.017018884420394897,0.8338438272476196,-0.5395259261131287,-0.011417563073337078,-0.008253414183855057,0.8418511748313904,-0.5297474265098572,-0.016535766422748566,-0.0010559773072600365,0.8479936122894287,-0.5211766362190247,-0.019734973087906837,0.0053627947345376015,0.8532037734985352,-0.5088855624198914,-0.023467333987355232,0.012217182666063309,0.8604274988174438,-0.4957716763019562,-0.026821041479706764,0.018403222784399986,0.8678435683250427,-0.4845961034297943,-0.030162567272782326,0.022866683080792427,0.8739187121391296,-0.4765987992286682,-0.03320588544011116,0.02567383460700512,0.8781182765960693,-0.47601357102394104,-0.034713443368673325,0.027806667611002922,0.8783125281333923,-0.4813334047794342,-0.03506126627326012,0.02946949377655983,0.8753401637077332,-0.48839759826660156,-0.03488932177424431,0.030801020562648773,0.8713792562484741,-0.49507442116737366,-0.03476943075656891,0.03172336146235466,0.8675748109817505,-0.499175488948822,-0.03483733534812927,0.03238386660814285,0.8651944994926453,-0.5003000497817993,-0.035049062222242355,0.03297780081629753,0.8645136952400208,-0.49816325306892395,-0.03556971997022629,0.03377151861786842,0.8656949400901794,-0.4932889938354492,-0.03625694662332535,0.034437522292137146,0.868427038192749,-0.48632052540779114,-0.03741014748811722,0.035804592072963715,0.8722447156906128,-0.4783852994441986,-0.038893986493349075,0.037568192929029465,0.8764835596084595,-0.4708181619644165,-0.040383774787187576,0.03940587490797043,0.8804240822792053,-0.46424680948257446,-0.04184272885322571,0.04121183976531029,0.8837565779685974,-0.4590778350830078,-0.04316709190607071,0.04294629395008087,0.886306881904602,-0.45575666427612305,-0.04433179646730423,0.04452615603804588,0.8878840208053589,-0.45369046926498413,-0.045298367738723755,0.04591886326670647,0.8888219594955444,-0.4523663818836212,-0.046018216758966446,0.047087471932172775,0.8893985748291016,-0.4517156481742859,-0.046557940542697906,0.04793357104063034,0.8896559476852417,-0.4523082375526428,-0.04895296320319176,0.04732098802924156,0.8892589807510376],"type":"quaternion"},{"name":"RightUpLeg.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.89385986328125,-0.09060920029878616,0.4192637503147125,0.13047005236148834,0.8814768195152283,-0.07765576243400574,0.4476233720779419,0.12884698808193207,0.8685898184776306,-0.06606335937976837,0.47462767362594604,0.12615849077701569,0.8558136224746704,-0.05520593374967575,0.49933192133903503,0.12330057471990585,0.8427655100822449,-0.0457441620528698,0.5210629105567932,0.1270720660686493,0.82975172996521,-0.040414586663246155,0.53939288854599,0.13760119676589966,0.8186472654342651,-0.03817719966173172,0.553619384765625,0.1478675752878189,0.8108132481575012,-0.03940730541944504,0.5630548000335693,0.1549137532711029,0.8070830702781677,-0.045239001512527466,0.5669772028923035,0.15845270454883575,0.8063535690307617,-0.05788761377334595,0.5655392408370972,0.1631205976009369,0.8064261078834534,-0.08038230985403061,0.5601671934127808,0.17154687643051147,0.8042210340499878,-0.1142684742808342,0.5510299801826477,0.19114719331264496,0.8008021712303162,-0.15393251180648804,0.5391573905944824,0.21054689586162567,0.797406792640686,-0.1877879500389099,0.5257717370986938,0.22900262475013733,0.7925962209701538,-0.21995985507965088,0.5120006799697876,0.24751606583595276,0.7858598232269287,-0.24981385469436646,0.4982302784919739,0.26792529225349426,0.7780194282531738,-0.2743787467479706,0.48573949933052063,0.28889307379722595,0.7699618339538574,-0.2927148640155792,0.47587695717811584,0.3082497715950012,0.760438084602356,-0.3067095875740051,0.46851617097854614,0.3288704454898834,0.7502201199531555,-0.31280118227005005,0.46397432684898376,0.35221174359321594,0.7482112646102905,-0.3123854100704193,0.4595964848995209,0.3624449670314789,0.7524722814559937,-0.30799904465675354,0.45311498641967773,0.3655254542827606,0.7556570172309875,-0.3027269244194031,0.4510044753551483,0.36596983671188354,0.7559951543807983,-0.29515334963798523,0.4584848880767822,0.3621428608894348,0.7527869939804077,-0.28514543175697327,0.47329431772232056,0.35776573419570923,0.7477678656578064,-0.275863915681839,0.49006426334381104,0.3529580235481262,0.7404472231864929,-0.26865553855895996,0.5092450976371765,0.3467443585395813,0.7314218878746033,-0.2611404061317444,0.5309819579124451,0.3389482796192169,0.7306259274482727,-0.2588702440261841,0.5372835993766785,0.33241280913352966,0.7301891446113586,-0.2612944543361664,0.5344414710998535,0.33603766560554504,0.7218827605247498,-0.26502782106399536,0.5335450172424316,0.35210129618644714,0.7119023203849792,-0.2708593010902405,0.5297770500183105,0.37305042147636414,0.7016416192054749,-0.27785438299179077,0.5250612497329712,0.39345496892929077,0.693010151386261,-0.28318172693252563,0.5192427635192871,0.41222813725471497,0.6880621910095215,-0.28581395745277405,0.5139841437339783,0.42508962750434875,0.6862645149230957,-0.28657087683677673,0.5093297362327576,0.4330143928527832,0.6874294281005859,-0.2856059670448303,0.5053393840789795,0.4364655911922455,0.6905441284179688,-0.2834593951702118,0.5014939308166504,0.4373825192451477,0.694342851638794,-0.28162816166877747,0.49721387028694153,0.437438040971756,0.6982241272926331,-0.2803858816623688,0.49250513315200806,0.43738487362861633,0.7017671465873718,-0.2798301577568054,0.4875067174434662,0.4376702606678009,0.7045559287071228,-0.28006699681282043,0.4823700785636902,0.43872836232185364,0.7067611217498779,-0.28062087297439575,0.4776657819747925,0.43997278809547424,0.708420991897583,-0.281124085187912,0.4738194942474365,0.4411395490169525,0.7093071937561035,-0.2814939618110657,0.4710545241832733,0.44243884086608887,0.7092418074607849,-0.2814998924732208,0.4695685803890228,0.4441163241863251],"type":"quaternion"},{"name":"RightLeg.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.14712826907634735,-0.0077429404482245445,-0.0313967801630497,0.9885886907577515,0.14293190836906433,-0.010285167954862118,-0.04075835272669792,0.9888394474983215,0.1368648260831833,-0.011585609056055546,-0.049273744225502014,0.9892956614494324,0.13384243845939636,-0.008944837376475334,-0.05467277020215988,0.9894528985023499,0.14002926647663116,-0.008217869326472282,-0.05870193615555763,0.9883715510368347,0.15290409326553345,-0.006194110028445721,-0.06101562827825546,0.9863362312316895,0.1635362207889557,0.0015295185148715973,-0.06131836771965027,0.9846286773681641,0.16930699348449707,0.007473996374756098,-0.06174950674176216,0.9835986495018005,0.17299054563045502,0.00865661446005106,-0.062428493052721024,0.982904851436615,0.17869319021701813,0.003951869439333677,-0.06275624781847,0.9818934798240662,0.19469770789146423,-0.006528810597956181,-0.059484101831912994,0.9790361523628235,0.23138533532619476,-0.016843898221850395,-0.0594438798725605,0.9708983302116394,0.2633671760559082,-0.023876570165157318,-0.062418755143880844,0.9623780846595764,0.28537821769714355,-0.021164586767554283,-0.05785961076617241,0.9564327597618103,0.2986561059951782,-0.016729319468140602,-0.04889464005827904,0.9529606699943542,0.3067416846752167,-0.011380727402865887,-0.036747708916664124,0.951015055179596,0.3074107766151428,-0.00626463582739234,-0.021142415702342987,0.9513213634490967,0.2996385991573334,-0.003124979790300131,-0.006300308275967836,0.9540268778800964,0.2948206663131714,-0.0011732111452147365,0.001167746027931571,0.9555512070655823,0.2986961007118225,-0.0006311123142950237,0.006032190751284361,0.9543290138244629,0.2931147813796997,0.003951838240027428,0.011496301740407944,0.956000030040741,0.28419944643974304,0.007650123443454504,0.016190804541110992,0.9585979580879211,0.2734736502170563,0.0068495264276862144,0.018996452912688255,0.9616674780845642,0.2543008029460907,-0.0014347103424370289,0.01611381024122238,0.9669898748397827,0.23260946571826935,-0.01659000851213932,0.007674161344766617,0.9723984599113464,0.21150749921798706,-0.03447548300027847,-0.004133791197091341,0.9767594337463379,0.18900667130947113,-0.05494391918182373,-0.02052745595574379,0.9802225828170776,0.1612078696489334,-0.07865284383296967,-0.03553113713860512,0.9831395149230957,0.14159844815731049,-0.08682574331760406,-0.03945555165410042,0.9853194355964661,0.14603020250797272,-0.09176018089056015,-0.03337904065847397,0.9844496846199036,0.1740097552537918,-0.10770132392644882,-0.023255271837115288,0.9785603284835815,0.2130267173051834,-0.12395196408033371,-0.013907493092119694,0.9690521359443665,0.2537163197994232,-0.13683398067951202,-0.010231111198663712,0.957496702671051,0.28991201519966125,-0.1449042409658432,-0.007828762754797935,0.9459875822067261,0.31284499168395996,-0.14803043007850647,-0.006876298226416111,0.9381725788116455,0.324916273355484,-0.14729323983192444,-0.007301495876163244,0.9341738820075989,0.32899266481399536,-0.14388053119182587,-0.008557520806789398,0.9332679510116577,0.32882261276245117,-0.13889549672603607,-0.009250075556337833,0.9340761303901672,0.32836025953292847,-0.13290947675704956,-0.009832954034209251,0.935103178024292,0.32901209592819214,-0.12659341096878052,-0.01012119185179472,0.9357471466064453,0.3314115107059479,-0.12049375474452972,-0.010045109316706657,0.9357065558433533,0.33619287610054016,-0.1150960698723793,-0.00952152069658041,0.9346852898597717,0.3413317799568176,-0.11055989563465118,-0.008919344283640385,0.933375358581543,0.3455675542354584,-0.10704250633716583,-0.008436408825218678,0.9322305917739868,0.34888169169425964,-0.10495221614837646,-0.00811457447707653,0.9312361478805542,0.3512074947357178,-0.09866474568843842,-0.0049941400066018105,0.9310712218284607],"type":"quaternion"},{"name":"RightFoot.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[-0.6682496666908264,0.03620108962059021,0.015038889832794666,0.7429035902023315,-0.6524023413658142,0.03680282086133957,0.015455720946192741,0.7568209171295166,-0.6385368704795837,0.04169607162475586,0.00837810430675745,0.7684151530265808,-0.6295764446258545,0.05069546401500702,-0.005005871411412954,0.775266706943512,-0.6238305568695068,0.05302977189421654,-0.007981862872838974,0.7797176837921143,-0.6205982565879822,0.05077620968222618,-0.0035895390901714563,0.7824747562408447,-0.6185325980186462,0.049703121185302734,-0.0013566201087087393,0.784184455871582,-0.6187900304794312,0.04870351403951645,0.0005072066560387611,0.7840450406074524,-0.6188516616821289,0.04873832315206528,0.0005655474378727376,0.783994197845459,-0.6175787448883057,0.04883323982357979,0.0005853396723978221,0.7849913835525513,-0.6162222027778625,0.051728591322898865,-0.0036561342421919107,0.7858632206916809,-0.6149678230285645,0.05213508754968643,-0.0034537063911557198,0.786819338798523,-0.6038852334022522,0.05141787230968475,-0.0007209888426586986,0.7954108119010925,-0.5835362076759338,0.05317831411957741,-0.002012006239965558,0.8103416562080383,-0.5555483102798462,0.05516531318426132,-0.003727605566382408,0.8296439051628113,-0.5234475135803223,0.05636243149638176,-0.00451274449005723,0.8501797914505005,-0.48906755447387695,0.056785907596349716,-0.004659839905798435,0.8703830242156982,-0.45402759313583374,0.05634164810180664,-0.0028096341993659735,0.8892000317573547,-0.42710787057876587,0.05369395390152931,0.003821283346042037,0.9025969505310059,-0.4151870012283325,0.05018846318125725,0.011631612665951252,0.9082761406898499,-0.41294875741004944,0.05173405259847641,0.00932453852146864,0.909235954284668,-0.42117440700531006,0.05898916721343994,-0.0031320282723754644,0.9050539135932922,-0.43218994140625,0.0692107304930687,-0.020772481337189674,0.8988828063011169,-0.44050246477127075,0.07991013675928116,-0.038950636982917786,0.8933391571044922,-0.4476906955242157,0.08948536217212677,-0.0554833859205246,0.8879679441452026,-0.4534582793712616,0.09923483431339264,-0.0727984756231308,0.8827391862869263,-0.45575177669525146,0.10850602388381958,-0.09014365077018738,0.8788577318191528,-0.4520333707332611,0.11895230412483215,-0.10981419682502747,0.8771870136260986,-0.44950178265571594,0.13314345479011536,-0.13675758242607117,0.8726502060890198,-0.45613959431648254,0.14870119094848633,-0.16634495556354523,0.861483633518219,-0.4717097580432892,0.1621723473072052,-0.19226272404193878,0.8451184034347534,-0.49079862236976624,0.17179957032203674,-0.2110973745584488,0.8276711702346802,-0.5110393166542053,0.17581769824028015,-0.21944473683834076,0.8122628927230835,-0.527005672454834,0.17521359026432037,-0.2193937450647354,0.8021419048309326,-0.5365012288093567,0.17360952496528625,-0.2172871232032776,0.7967512607574463,-0.5409854650497437,0.17174021899700165,-0.21439561247825623,0.794905424118042,-0.5427093505859375,0.1697697937488556,-0.21107718348503113,0.7950416803359985,-0.5431313514709473,0.16812178492546082,-0.20817533135414124,0.7958683967590332,-0.5436893701553345,0.16672861576080322,-0.20581264793872833,0.7963947653770447,-0.5449239611625671,0.16568243503570557,-0.20411930978298187,0.7962051033973694,-0.547021746635437,0.16502691805362701,-0.20311714708805084,0.795158326625824,-0.5501666069030762,0.1647467464208603,-0.20287950336933136,0.7931047677993774,-0.5533661842346191,0.16469953954219818,-0.20300894975662231,0.7908523082733154,-0.5559727549552917,0.1646965593099594,-0.20319639146327972,0.788974404335022,-0.5579084157943726,0.16469046473503113,-0.20328234136104584,0.7875858545303345,-0.5576359033584595,0.15968339145183563,-0.20726493000984192,0.787771999835968],"type":"quaternion"},{"name":"Spine.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.0015836125239729881,0.04155900329351425,-0.002116649877279997,0.9991325736045837,0.005482208915054798,0.04516002908349037,-0.004148940090090036,0.9989561438560486,0.009228832088410854,0.04804425686597824,-0.0068224575370550156,0.998779296875,0.012424962595105171,0.0502132922410965,-0.01072248537093401,0.9986037015914917,0.014449232257902622,0.050815828144550323,-0.012657055631279945,0.9985233545303345,0.01489467266947031,0.0507982112467289,-0.013319700956344604,0.9985090494155884,0.015146545134484768,0.0505271852016449,-0.014501168392598629,0.9985025525093079,0.015121783129870892,0.04991699382662773,-0.01557929441332817,0.9985173940658569,0.014917800202965736,0.04887163266539574,-0.015505867078900337,0.9985733032226562,0.014590349048376083,0.04726829007267952,-0.014184480533003807,0.9986749291419983,0.014272776432335377,0.04560372605919838,-0.013017207384109497,0.9987727999687195,0.014029163867235184,0.043770939111709595,-0.01208573579788208,0.9988699555397034,0.01394760049879551,0.04196568578481674,-0.011811693198978901,0.9989519119262695,0.013969596475362778,0.04030103608965874,-0.011925823986530304,0.9990187883377075,0.013753469102084637,0.03864430636167526,-0.012282904237508774,0.9990828633308411,0.013817437924444675,0.037055812776088715,-0.012692008167505264,0.9991371035575867,0.01396895106881857,0.03559398651123047,-0.013047217391431332,0.9991834759712219,0.01415255106985569,0.0342881940305233,-0.01231356244534254,0.9992359280586243,0.014387748204171658,0.03310053050518036,-0.010787594132125378,0.9992902278900146,0.014684495516121387,0.03198782727122307,-0.008587468415498734,0.9993435144424438,0.01508435420691967,0.030891764909029007,-0.0065421536564826965,0.9993875026702881,0.015630587935447693,0.029732659459114075,-0.005607254337519407,0.9994199872016907,0.01630515791475773,0.028565580025315285,-0.005535975098609924,0.9994435906410217,0.017079047858715057,0.027470925822854042,-0.005790004041045904,0.9994599223136902,0.01793545112013817,0.026482265442609787,-0.006046888418495655,0.9994701147079468,0.018874596804380417,0.02555653266608715,-0.0063365427777171135,0.9994751214981079,0.019904514774680138,0.024674123153090477,-0.006872288417071104,0.9994738101959229,0.02106388844549656,0.023852746933698654,-0.007706390228122473,0.9994638562202454,0.022369248792529106,0.023052461445331573,-0.0086721982806921,0.9994463920593262,0.02379174716770649,0.02228604257106781,-0.009614745154976845,0.9994222521781921,0.025308700278401375,0.021609948948025703,-0.010538394562900066,0.9993905425071716,0.02689730003476143,0.021074827760457993,-0.011451068334281445,0.9993504285812378,0.028533723205327988,0.020723512396216393,-0.012334045954048634,0.9993019104003906,0.03019322082400322,0.020581485703587532,-0.013135301880538464,0.999245822429657,0.031857192516326904,0.020745158195495605,-0.013951212167739868,0.9991797208786011,0.033503223210573196,0.02123454585671425,-0.014772269874811172,0.999103844165802,0.03510677441954613,0.02199552021920681,-0.01556189451366663,0.9990202784538269,0.03664407506585121,0.022947775200009346,-0.016302572563290596,0.9989318251609802,0.038092076778411865,0.024007774889469147,-0.01699097640812397,0.9988413453102112,0.03942679986357689,0.0251097921282053,-0.01761724054813385,0.9987515211105347,0.04062420874834061,0.02619103156030178,-0.018173182383179665,0.9986658692359924,0.04165980964899063,0.027190018445253372,-0.018650362268090248,0.9985876679420471,0.04250859469175339,0.028059212490916252,-0.01904110237956047,0.9985204935073853,0.043145615607500076,0.028750330209732056,-0.01933162473142147,0.9984678626060486,0.04354577511548996,0.02920658513903618,-0.019508643075823784,0.9984338879585266,0.04368295520544052,0.029365791007876396,-0.01952981948852539,0.9984228014945984],"type":"quaternion"},{"name":"LeftShoulder.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.4055384397506714,0.6999990940093994,-0.47457069158554077,0.3468754291534424,0.4007810950279236,0.7128324508666992,-0.46612972021102905,0.33759090304374695,0.4049098789691925,0.724777340888977,-0.4523472487926483,0.32577258348464966,0.41252830624580383,0.7299298048019409,-0.4413345754146576,0.31976044178009033,0.42219361662864685,0.7316192388534546,-0.4301531910896301,0.318518728017807,0.4319283962249756,0.7276835441589355,-0.4261649549007416,0.3198406994342804,0.4412599205970764,0.7213616371154785,-0.4249457120895386,0.3230298161506653,0.45096686482429504,0.7139009833335876,-0.42476892471313477,0.32641342282295227,0.45886704325675964,0.7067683935165405,-0.42432236671447754,0.33146658539772034,0.46421322226524353,0.7006877064704895,-0.42304685711860657,0.33848822116851807,0.4696963131427765,0.6948011517524719,-0.4212905466556549,0.34518271684646606,0.47582805156707764,0.6880949139595032,-0.42027196288108826,0.35140374302864075,0.4848432242870331,0.6798251271247864,-0.42039814591407776,0.35500746965408325,0.49336329102516174,0.668917179107666,-0.42379140853881836,0.3599214553833008,0.5014995336532593,0.6571734547615051,-0.42773815989494324,0.3655974566936493,0.5075763463973999,0.6451152563095093,-0.43200984597206116,0.37357738614082336,0.5098661780357361,0.635101318359375,-0.43417271971702576,0.3849376440048218,0.5101128816604614,0.626460611820221,-0.4354812800884247,0.39709973335266113,0.508284330368042,0.6183706521987915,-0.4369582533836365,0.41028326749801636,0.5078102350234985,0.6114355325698853,-0.4364736080169678,0.4216233789920807,0.5111530423164368,0.6065137982368469,-0.4318423271179199,0.42939016222953796,0.5192076563835144,0.6028789281845093,-0.42395609617233276,0.432691365480423,0.5226627588272095,0.5960983037948608,-0.42158323526382446,0.440179705619812,0.5235323309898376,0.587356686592102,-0.422661155462265,0.4497596025466919,0.5245396494865417,0.5787314772605896,-0.4237438440322876,0.4586602449417114,0.5252657532691956,0.5700798630714417,-0.4253256916999817,0.46712207794189453,0.5266928672790527,0.5623438954353333,-0.42612287402153015,0.47411322593688965,0.5279242396354675,0.5549442768096924,-0.4272787868976593,0.4803808033466339,0.5286427736282349,0.5469428300857544,-0.430279940366745,0.4860551953315735,0.52830970287323,0.538313090801239,-0.435403436422348,0.491458922624588,0.5285225510597229,0.5307909846305847,-0.440177321434021,0.4951452910900116,0.5306090116500854,0.5258507132530212,-0.44252634048461914,0.49609026312828064,0.5318422317504883,0.5213194489479065,-0.44559547305107117,0.4968043863773346,0.5330438017845154,0.5181686282157898,-0.4479624032974243,0.49668431282043457,0.5337113738059998,0.5158043503761292,-0.4499262273311615,0.496653288602829,0.5339648127555847,0.514119565486908,-0.45099881291389465,0.4971545338630676,0.5336441397666931,0.5132883191108704,-0.4509994685649872,0.49835580587387085,0.532998263835907,0.5130242705345154,-0.45030391216278076,0.4999452531337738,0.5323941707611084,0.5128732323646545,-0.4494914412498474,0.5014726519584656,0.5317941308021545,0.5128589868545532,-0.4486222267150879,0.5029004216194153,0.5312095880508423,0.5129666924476624,-0.4477144479751587,0.5042154788970947,0.530717670917511,0.5131590962409973,-0.44678083062171936,0.5053645968437195,0.5302957892417908,0.5133630037307739,-0.4459359049797058,0.506345808506012,0.5299843549728394,0.5135631561279297,-0.4452259838581085,0.5070928931236267,0.5297822952270508,0.5137085318565369,-0.4447644054889679,0.5075618028640747,0.5297031402587891,0.5137718319892883,-0.4446357488632202,0.5076930522918701],"type":"quaternion"},{"name":"LeftArm.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.2881375551223755,-0.22134484350681305,0.6939930319786072,0.6215761303901672,0.2969590127468109,-0.22910337150096893,0.7447801828384399,0.5519325733184814,0.2963864505290985,-0.2466873973608017,0.7644756436347961,0.5166018605232239,0.2871371805667877,-0.27038487792015076,0.7589076161384583,0.5181732773780823,0.277081161737442,-0.2879582643508911,0.7460613250732422,0.5326336622238159,0.2721320688724518,-0.295697420835495,0.739596962928772,0.5399106740951538,0.27072155475616455,-0.299198180437088,0.7392506003379822,0.5391649603843689,0.27049535512924194,-0.3015172779560089,0.7392491698265076,0.537987232208252,0.2702521085739136,-0.30377158522605896,0.7378941774368286,0.5387011170387268,0.2707076370716095,-0.3052963316440582,0.7375680208206177,0.5380566716194153,0.27209848165512085,-0.3056762218475342,0.7384560704231262,0.5359171032905579,0.27244555950164795,-0.3066236078739166,0.7362746000289917,0.5381961464881897,0.27411022782325745,-0.30784571170806885,0.7333476543426514,0.5406439900398254,0.2759243845939636,-0.31108519434928894,0.7262712717056274,0.5473772883415222,0.27522170543670654,-0.31685763597488403,0.7122042179107666,0.5626895427703857,0.2721719443798065,-0.3233410716056824,0.6941595673561096,0.5826796293258667,0.2652152180671692,-0.3302277624607086,0.671181857585907,0.6083793640136719,0.2531651556491852,-0.33724910020828247,0.6414406895637512,0.6408777832984924,0.24237416684627533,-0.33838799595832825,0.608354926109314,0.6757608652114868,0.24495837092399597,-0.32354432344436646,0.5640605688095093,0.719131588935852,0.2515343427658081,-0.30144914984703064,0.5098507404327393,0.7654483318328857,0.25809943675994873,-0.2754742205142975,0.44012507796287537,0.8147321939468384,0.2638297975063324,-0.2473839968442917,0.3570168912410736,0.8612397909164429,0.27096065878868103,-0.21756067872047424,0.2761686444282532,0.8960907459259033,0.28191491961479187,-0.1832171380519867,0.22344352304935455,0.9148926138877869,0.29558777809143066,-0.14257360994815826,0.2099771499633789,0.9209833145141602,0.3082965016365051,-0.10079766064882278,0.20334723591804504,0.9238197803497314,0.32272040843963623,-0.05740240588784218,0.20214711129665375,0.9228721857070923,0.33757880330085754,-0.015506777912378311,0.205296590924263,0.9185060858726501,0.3517823815345764,0.021513311192393303,0.21003057062625885,0.9119613766670227,0.3646419048309326,0.05049668624997139,0.21366088092327118,0.9048953056335449,0.3747314214706421,0.0725596621632576,0.21631339192390442,0.898621141910553,0.38069549202919006,0.09074094891548157,0.21889203786849976,0.893825113773346,0.38485318422317505,0.10593345761299133,0.22106681764125824,0.8898289799690247,0.38781270384788513,0.11854974180459976,0.2227591872215271,0.8865244388580322,0.38973307609558105,0.12887489795684814,0.2239626944065094,0.8839344382286072,0.390768438577652,0.13717509806156158,0.2246965765953064,0.8820399641990662,0.39107003808021545,0.14369888603687286,0.2250039428472519,0.8807883262634277,0.3907851278781891,0.1486796736717224,0.22494442760944366,0.8801030516624451,0.3900597095489502,0.15233825147151947,0.22459247708320618,0.8798890709877014,0.38903966546058655,0.15488643944263458,0.22403234243392944,0.8800386190414429,0.3878710865974426,0.15652775764465332,0.22335496544837952,0.8804360628128052,0.38670051097869873,0.157462477684021,0.22265663743019104,0.8809610605239868,0.3856772184371948,0.1578870415687561,0.222035750746727,0.8814901113510132,0.38494810461997986,0.15801268815994263,0.22159995138645172,0.881895899772644,0.38466086983680725,0.15804758667945862,0.221453458070755,0.8820518255233765],"type":"quaternion"},{"name":"LeftForeArm.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.08949127048254013,0.07849092036485672,0.6568453907966614,0.7445700764656067,0.10059234499931335,0.0989827960729599,0.6398410201072693,0.7554383277893066,0.0971972718834877,0.09827972203493118,0.635160505771637,0.7599111199378967,0.08667956292629242,0.0890800878405571,0.633704662322998,0.76352459192276,0.07329016178846359,0.07579101622104645,0.6356565356254578,0.7647385597229004,0.06135514751076698,0.06250420212745667,0.644121527671814,0.7598922848701477,0.0534202940762043,0.057701341807842255,0.6516552567481995,0.754428505897522,0.0465409979224205,0.05610043182969093,0.6616379022598267,0.746272087097168,0.04130302742123604,0.05477047711610794,0.6782853007316589,0.7315895557403564,0.037005871534347534,0.05421710014343262,0.6957111358642578,0.7153160572052002,0.03293277323246002,0.05522071570158005,0.7105252742767334,0.700728178024292,0.031410180032253265,0.05643836408853531,0.7242382168769836,0.6865180730819702,0.029204757884144783,0.0625312477350235,0.7295418381690979,0.68044513463974,0.03412457928061485,0.06859772652387619,0.7359685301780701,0.6726665496826172,0.041715268045663834,0.07636228948831558,0.737816333770752,0.669369637966156,0.05062802508473396,0.08530261367559433,0.7349474430084229,0.6708297729492188,0.060641463845968246,0.09448409825563431,0.7338634729385376,0.6699550747871399,0.0728887990117073,0.10171855986118317,0.7389501333236694,0.6620372533798218,0.08786676079034805,0.10572892427444458,0.7509931325912476,0.6458407640457153,0.11394774913787842,0.10299191623926163,0.7771155834197998,0.6103277802467346,0.1487628072500229,0.09240572154521942,0.8098140358924866,0.5599392652511597,0.1807832568883896,0.08711393177509308,0.8331264853477478,0.5153919458389282,0.18813534080982208,0.07894542068243027,0.8334433436393738,0.5135610699653625,0.2035854607820511,0.043589700013399124,0.8068685531616211,0.5528255105018616,0.22372105717658997,-0.019905244931578636,0.7461463809013367,0.6267521977424622,0.23700706660747528,-0.10210439562797546,0.6525729894638062,0.7124261260032654,0.22226010262966156,-0.18388037383556366,0.5656076073646545,0.7725778222084045,0.19182640314102173,-0.2428636997938156,0.5061782002449036,0.8049866557121277,0.15484559535980225,-0.2721637487411499,0.5043295621871948,0.8047369122505188,0.11721985787153244,-0.2921811044216156,0.5216493606567383,0.792951226234436,0.08323652297258377,-0.3058297336101532,0.5536249279975891,0.7700904607772827,0.05583006143569946,-0.31470584869384766,0.5938000082969666,0.7384069561958313,0.03694477304816246,-0.3190779387950897,0.6361020803451538,0.7015686631202698,0.027566060423851013,-0.3183753490447998,0.6753106713294983,0.6647050380706787,0.02785315364599228,-0.31162527203559875,0.7072007656097412,0.6340197324752808,0.03243240714073181,-0.3028395473957062,0.7285802364349365,0.6135203242301941,0.03537556156516075,-0.2982359230518341,0.7364180088043213,0.6062116026878357,0.03740813955664635,-0.29637449979782104,0.7357883453369141,0.6077650189399719,0.03980027884244919,-0.2943582832813263,0.7343165874481201,0.6103674173355103,0.042403724044561386,-0.2922933101654053,0.7322342991828918,0.6136770844459534,0.04506529122591019,-0.2902829349040985,0.729778528213501,0.6173558831214905,0.04762592911720276,-0.28842729330062866,0.7271925210952759,0.6210737228393555,0.04992028325796127,-0.28682368993759155,0.7247273921966553,0.6245081424713135,0.05177634581923485,-0.2855653464794159,0.7226414680480957,0.6273443698883057,0.053018923848867416,-0.28474342823028564,0.7211973667144775,0.6292730569839478,0.05345669388771057,-0.28443968296051025,0.7206659317016602,0.6299818158149719],"type":"quaternion"},{"name":"RightShoulder.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.7424899935722351,-0.3365141749382019,0.15872572362422943,0.5570215582847595,0.7706384658813477,-0.29461580514907837,0.1134469136595726,0.5535771250724792,0.792729377746582,-0.26476162672042847,0.07531577348709106,0.5438832640647888,0.8052125573158264,-0.24948501586914062,0.049882061779499054,0.5356321334838867,0.8118306398391724,-0.2506449818611145,0.03463618829846382,0.526220977306366,0.8098974227905273,-0.2615886926651001,0.023995596915483475,0.5244632959365845,0.8054531812667847,-0.2740171551704407,0.014576938934624195,0.5253068804740906,0.7999240159988403,-0.2873668968677521,0.006618672050535679,0.526780903339386,0.7945358753204346,-0.2992432713508606,0.0025140191428363323,0.5283558964729309,0.7898308634757996,-0.30900585651397705,0.0029378177132457495,0.5297867655754089,0.7851012349128723,-0.3183375895023346,0.006274326238781214,0.5312606692314148,0.7799766659736633,-0.32801687717437744,0.011751588433980942,0.5328257083892822,0.7734723091125488,-0.33987492322921753,0.018292047083377838,0.534687876701355,0.7645613551139832,-0.35283026099205017,0.028052084147930145,0.5386742353439331,0.7546719908714294,-0.366115003824234,0.04119393974542618,0.5428932905197144,0.7437317371368408,-0.379083514213562,0.05902450531721115,0.5474257469177246,0.733363151550293,-0.39022111892700195,0.08275922387838364,0.550506055355072,0.72343510389328,-0.39966875314712524,0.11246884614229202,0.5515952706336975,0.7115046977996826,-0.4087696969509125,0.1480087786912918,0.55205237865448,0.6970264911651611,-0.41871047019958496,0.1876320093870163,0.5510261654853821,0.6778138279914856,-0.4360411763191223,0.23191510140895844,0.5446575880050659,0.6514043211936951,-0.46177735924720764,0.28039342164993286,0.5327417254447937,0.6212153434753418,-0.48870667815208435,0.328596293926239,0.5169930458068848,0.5912277698516846,-0.5118638277053833,0.37127330899238586,0.5006009340286255,0.5668554306030273,-0.5268723368644714,0.4043157696723938,0.4874517321586609,0.5488991737365723,-0.5351802706718445,0.43040284514427185,0.47649261355400085,0.5340917110443115,-0.5413447022438049,0.45427563786506653,0.46403202414512634,0.5219647884368896,-0.5456667542457581,0.4760204255580902,0.45078277587890625,0.5119590163230896,-0.548292875289917,0.49583500623703003,0.43751630187034607,0.5037686824798584,-0.5496041178703308,0.5136021971702576,0.424694299697876,0.49706560373306274,-0.5499687194824219,0.5292108654975891,0.4127906262874603,0.491507887840271,-0.5497477650642395,0.5425536036491394,0.40228471159935,0.4866873621940613,-0.5492681264877319,0.5535714626312256,0.39369863271713257,0.48192906379699707,-0.5487134456634521,0.562427282333374,0.3877287209033966,0.4763675928115845,-0.5488401651382446,0.5693094730377197,0.38436344265937805,0.47011011838912964,-0.5496374368667603,0.5750405788421631,0.3823917508125305,0.46400755643844604,-0.5504677295684814,0.5802656412124634,0.38075464963912964,0.45815035700798035,-0.5513063073158264,0.5849751830101013,0.3794254958629608,0.45260924100875854,-0.5521459579467773,0.5891461372375488,0.3784000873565674,0.4475051164627075,-0.5529413223266602,0.5927897095680237,0.3776182532310486,0.4429550766944885,-0.553652286529541,0.5959144234657288,0.37702253460884094,0.43906620144844055,-0.5542985796928406,0.5984744429588318,0.3765663504600525,0.43589356541633606,-0.5548193454742432,0.6005010604858398,0.3762592077255249,0.43352317810058594,-0.5552042722702026,0.6019752025604248,0.3760741353034973,0.43204036355018616,-0.5554429888725281,0.6028772592544556,0.3759830892086029,0.4315468966960907,-0.5555368661880493,0.6031520962715149,0.3759702742099762],"type":"quaternion"},{"name":"RightArm.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.25998809933662415,0.2597474157810211,-0.47408345341682434,0.8001139760017395,0.35635697841644287,0.21161724627017975,-0.47645050287246704,0.7753856182098389,0.3999500274658203,0.1925021857023239,-0.49931326508522034,0.7440895438194275,0.4171464145183563,0.17275933921337128,-0.5143734812736511,0.729083776473999,0.41689059138298035,0.1423196643590927,-0.5329434275627136,0.7224394679069519,0.39450496435165405,0.15010161697864532,-0.5861889719963074,0.6915330290794373,0.37233713269233704,0.16087907552719116,-0.6104856133460999,0.6802870035171509,0.3592527210712433,0.17382268607616425,-0.6242843270301819,0.6715595722198486,0.33691421151161194,0.17750310897827148,-0.6252342462539673,0.681222140789032,0.32716792821884155,0.18739184737205505,-0.6202109456062317,0.6878836750984192,0.32620149850845337,0.19771777093410492,-0.6271529793739319,0.6791019439697266,0.3234960734844208,0.20917540788650513,-0.6377078294754028,0.6670267581939697,0.30943453311920166,0.22120316326618195,-0.6342384219169617,0.6730982065200806,0.2961992025375366,0.23088252544403076,-0.6299657225608826,0.6797811985015869,0.28286880254745483,0.23871053755283356,-0.6248020529747009,0.6874772310256958,0.26870858669281006,0.24576710164546967,-0.6184008121490479,0.6964012384414673,0.25432318449020386,0.25198283791542053,-0.6107679009437561,0.7062485218048096,0.24034303426742554,0.2573438882827759,-0.6019519567489624,0.7167030572891235,0.2274118810892105,0.26189708709716797,-0.5920464396476746,0.7274439930915833,0.21379205584526062,0.2608036398887634,-0.5725433230400085,0.747307538986206,0.20167681574821472,0.2597233057022095,-0.5530067086219788,0.7655415534973145,0.18900534510612488,0.25741010904312134,-0.5318785309791565,0.7842973470687866,0.17394305765628815,0.25228187441825867,-0.5072724223136902,0.8054640889167786,0.15403319895267487,0.2429346740245819,-0.4783996343612671,0.8296929597854614,0.13382600247859955,0.22069470584392548,-0.4404926598072052,0.859855055809021,0.1309906393289566,0.17898249626159668,-0.3833913505077362,0.8965588808059692,0.14631125330924988,0.12804584205150604,-0.31794455647468567,0.9279593229293823,0.1763473004102707,0.07509947568178177,-0.2491634041070938,0.9493046402931213,0.2189195305109024,0.02344309352338314,-0.17744266986846924,0.9591865539550781,0.27078455686569214,-0.021959569305181503,-0.11044946312904358,0.956030547618866,0.32747477293014526,-0.057910218834877014,-0.05494676157832146,0.9414815902709961,0.3834959864616394,-0.08395186066627502,-0.014037040993571281,0.9196118116378784,0.40712210536003113,-0.09976722300052643,-0.009624489583075047,0.9078576564788818,0.4226624369621277,-0.11405110359191895,-0.016572339460253716,0.8989294767379761,0.43348902463912964,-0.12681448459625244,-0.02487732656300068,0.8918444514274597,0.44084063172340393,-0.13841477036476135,-0.03417219594120979,0.8861902952194214,0.4452002942562103,-0.1489754170179367,-0.04383298382163048,0.8818626403808594,0.4471096396446228,-0.15852554142475128,-0.05351288989186287,0.8786916732788086,0.44709187746047974,-0.1670408993959427,-0.06297089159488678,0.876493513584137,0.445607990026474,-0.17447057366371155,-0.07197067141532898,0.8751078844070435,0.44307488203048706,-0.18076112866401672,-0.08025844395160675,0.8743961453437805,0.4399294853210449,-0.18587692081928253,-0.08759322017431259,0.8742077946662903,0.43663322925567627,-0.1898074895143509,-0.09373605251312256,0.8743787407875061,0.43372073769569397,-0.1925705075263977,-0.09849759191274643,0.8747006058692932,0.43175533413887024,-0.19420339167118073,-0.10169951617717743,0.8749455213546753,0.43128007650375366,-0.19474861025810242,-0.10313595831394196,0.8748905658721924],"type":"quaternion"},{"name":"RightForeArm.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[-0.036353569477796555,0.344988614320755,-0.07526534050703049,0.9348777532577515,0.024000395089387894,0.2355983704328537,-0.07299806922674179,0.9688078761100769,0.0526142343878746,0.11470197141170502,-0.08339028805494308,0.9884944558143616,0.06575445830821991,0.004695727489888668,-0.14667104184627533,0.9869862794876099,0.06721771508455276,-0.04796989634633064,-0.2314491719007492,0.9693358540534973,0.105837881565094,-0.052841875702142715,-0.20258140563964844,0.9720940589904785,0.11924094706773758,-0.07528316974639893,-0.2201659083366394,0.9652155637741089,0.11996572464704514,-0.08724603056907654,-0.2263181060552597,0.9626923203468323,0.11696494370698929,-0.09614251554012299,-0.259861558675766,0.9537021517753601,0.11153300851583481,-0.09846565872430801,-0.2834123969078064,0.9473870992660522,0.1013060212135315,-0.07886527478694916,-0.2569805085659027,0.9578509330749512,0.0832882970571518,-0.052138518542051315,-0.2212572693824768,0.970252513885498,0.07197810709476471,-0.021078137680888176,-0.22145874798297882,0.972281277179718,0.0597241073846817,0.011379149742424488,-0.22496265172958374,0.9724686741828918,0.04669801518321037,0.04436779022216797,-0.23009996116161346,0.9710328578948975,0.032809577882289886,0.07695311307907104,-0.23508679866790771,0.9683676958084106,0.017057733610272408,0.10829345881938934,-0.23791465163230896,0.9650794267654419,0.0021871854551136494,0.13645868003368378,-0.23716452717781067,0.9618353843688965,-0.012226623483002186,0.1621679961681366,-0.23166431486606598,0.9591056704521179,-0.029696661978960037,0.18661242723464966,-0.2226378321647644,0.9564132690429688,-0.047471120953559875,0.20752547681331635,-0.2125433087348938,0.9536797404289246,-0.06250527501106262,0.22299420833587646,-0.20365586876869202,0.951257586479187,-0.07157494127750397,0.2314363718032837,-0.19815298914909363,0.9497629404067993,-0.07201079279184341,0.23156337440013885,-0.19722263514995575,0.9498926997184753,-0.06779436022043228,0.22367741167545319,-0.20754554867744446,0.9498932957649231,-0.0676867812871933,0.2099785953760147,-0.25026029348373413,0.9427074193954468,-0.07084211707115173,0.19206896424293518,-0.3169536888599396,0.9260838627815247,-0.07529972493648529,0.17076388001441956,-0.3939914107322693,0.8999668955802917,-0.08072671294212341,0.14747346937656403,-0.4690413177013397,0.8670265674591064,-0.08641133457422256,0.12442053854465485,-0.5334473848342896,0.8321577310562134,-0.09128005057573318,0.10377541929483414,-0.585608720779419,0.7987246513366699,-0.09397777169942856,0.08790639042854309,-0.625149667263031,0.7698237895965576,-0.08823328465223312,0.08279617130756378,-0.6269392967224121,0.7696147561073303,-0.08809422701597214,0.08696679770946503,-0.618959367275238,0.7756065726280212,-0.08853710442781448,0.09263543784618378,-0.6127362847328186,0.7798295617103577,-0.0885457843542099,0.09866398572921753,-0.6083324551582336,0.7825322151184082,-0.08818040788173676,0.10504509508609772,-0.6050816178321838,0.7842614054679871,-0.08751018345355988,0.11163435876369476,-0.6026729345321655,0.7852802872657776,-0.08661448955535889,0.11822861433029175,-0.6009727716445923,0.7857173085212708,-0.08557390421628952,0.12461905926465988,-0.5998772978782654,0.785680890083313,-0.0844704881310463,0.13060088455677032,-0.5992748141288757,0.785288393497467,-0.08338478952646255,0.13596102595329285,-0.5990675091743469,0.784652590751648,-0.08239720016717911,0.14048556983470917,-0.5991539359092712,0.7838935256004333,-0.08158756047487259,0.14393769204616547,-0.5994881987571716,0.7830960154533386,-0.08103626221418381,0.14607742428779602,-0.6000242233276367,0.7823461294174194,-0.08082569390535355,0.14668120443820953,-0.6006652116775513,0.7817628383636475],"type":"quaternion"},{"name":"Head.quaternion","times":[0.02222220040857792,0.05555550381541252,0.08888904005289078,0.12222234159708023,0.15555565059185028,0.18888895213603973,0.2222222536802292,0.25555554032325745,0.2888888418674469,0.32222214341163635,0.3555556833744049,0.38888898491859436,0.4222222864627838,0.45555558800697327,0.4888888895511627,0.5222222208976746,0.555555522441864,0.5888890624046326,0.622222363948822,0.6555556654930115,0.6888889670372009,0.7222222685813904,0.7555555701255798,0.7888888716697693,0.8222221732139587,0.8555557131767273,0.8888890147209167,0.9222223162651062,0.9555556178092957,0.9888889193534851,1.0222221612930298,1.0555554628372192,1.0888887643814087,1.1222220659255981,1.1555553674697876,1.188888669013977,1.2222224473953247,1.2555557489395142,1.2888890504837036,1.322222352027893,1.3555556535720825,1.388888955116272,1.4222222566604614,1.4555555582046509,1.4888888597488403,1.5222221612930298],"values":[0.12393227219581604,-0.051071785390377045,-0.010252664797008038,0.9909225106239319,0.11579429358243942,-0.0940844863653183,-0.013384814374148846,0.9887166619300842,0.11779821664094925,-0.13421128690242767,-0.014790776185691357,0.9838151335716248,0.12347623705863953,-0.17079712450504303,-0.00881241075694561,0.9774990081787109,0.13224871456623077,-0.2058200091123581,-0.0038522579707205296,0.9696048498153687,0.13141703605651855,-0.23604798316955566,-0.0027247231919318438,0.962810218334198,0.13018271327018738,-0.256271630525589,-0.002011143835261464,0.957796037197113,0.12743300199508667,-0.26169976592063904,-0.0024781343527138233,0.9566963911056519,0.12463895976543427,-0.2542972266674042,-0.004478096030652523,0.9590505957603455,0.12358112633228302,-0.23422731459140778,-0.007297883741557598,0.9642676115036011,0.12489014863967896,-0.20429874956607819,-0.011225518770515919,0.9708442091941833,0.1261853724718094,-0.16601420938968658,-0.015571119263768196,0.9778926968574524,0.12823215126991272,-0.11906438320875168,-0.02172386460006237,0.9843314290046692,0.12839387357234955,-0.04231734201312065,-0.03230183199048042,0.9902933239936829,0.12910296022891998,0.043740011751651764,-0.04369893670082092,0.9897018074989319,0.13188478350639343,0.1183779314160347,-0.05331640690565109,0.9827260375022888,0.13534694910049438,0.1647193282842636,-0.05766962096095085,0.9753066301345825,0.13636524975299835,0.19875454902648926,-0.05946097522974014,0.9686927199363708,0.13692812621593475,0.2264447659254074,-0.060748390853405,0.9624360203742981,0.1370408535003662,0.24935965240001678,-0.06181945279240608,0.9566702246665955,0.13667964935302734,0.266231894493103,-0.06270407140254974,0.9521068930625916,0.13561582565307617,0.2774667739868164,-0.06300197541713715,0.9490264654159546,0.13412944972515106,0.2852359414100647,-0.06324191391468048,0.9469161629676819,0.13294440507888794,0.292428195476532,-0.06313929706811905,0.9448941946029663,0.132033109664917,0.30008795857429504,-0.06257512420415878,0.9426552057266235,0.1313748061656952,0.3062928318977356,-0.062205974012613297,0.9407740235328674,0.13039764761924744,0.3113256096839905,-0.062109604477882385,0.9392631649971008,0.12811395525932312,0.3150181174278259,-0.06173566356301308,0.9383704662322998,0.1252845972776413,0.31715384125709534,-0.06126847490668297,0.9380636215209961,0.12186379730701447,0.31761911511421204,-0.06132727116346359,0.9383529424667358,0.1188468486070633,0.3164311945438385,-0.06172327324748039,0.9391149878501892,0.1154809519648552,0.3146807849407196,-0.062143828719854355,0.9400948286056519,0.1113353967666626,0.31358322501182556,-0.0606185682117939,0.9410607814788818,0.10683222860097885,0.31303471326828003,-0.0596255324780941,0.9418285489082336,0.10207483172416687,0.31263571977615356,-0.05865276977419853,0.942549467086792,0.09717263281345367,0.3119608759880066,-0.05718515068292618,0.9433810710906982,0.09225735813379288,0.31105977296829224,-0.05549939349293709,0.9442723393440247,0.0874912366271019,0.309946209192276,-0.05413742735981941,0.9451707601547241,0.08297418802976608,0.30868902802467346,-0.05310104787349701,0.9460479021072388,0.07877769321203232,0.30736666917800903,-0.052309270948171616,0.9468809366226196,0.07497553527355194,0.3060545325279236,-0.051687806844711304,0.9476485252380371,0.07171361148357391,0.3048206865787506,-0.05120016261935234,0.9483248591423035,0.06900389492511749,0.3037479817867279,-0.050825443118810654,0.9488900899887085,0.06694668531417847,0.3029029965400696,-0.050571128726005554,0.9493210911750793,0.06564662605524063,0.30235159397125244,-0.050445880740880966,0.949594259262085,0.06521730870008469,0.3022273778915405,-0.050466589629650116,0.9496622681617737],"type":"quaternion"}],"uuid":"950a5329-2a8d-4fb3-b235-dc0380a45cf0","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/one-hand-sword-combo.json b/public/anims/baked/sword_shield/one-hand-sword-combo.json new file mode 100644 index 0000000..3b7b7fb --- /dev/null +++ b/public/anims/baked/sword_shield/one-hand-sword-combo.json @@ -0,0 +1 @@ +{"name":"sword_shield/one-hand-sword-combo","duration":4.566666603088379,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[-0.5590595006942749,56.298553466796875,0.05576476454734802,-0.5830438137054443,56.26823425292969,0.00898239016532898,-0.6239629983901978,56.23336410522461,-0.10380056500434875,-0.6637053489685059,56.21349334716797,-0.2244238555431366,-0.7141396999359131,56.19441604614258,-0.34516286849975586,-0.7737370729446411,56.16643524169922,-0.47111305594444275,-0.8478822708129883,56.144195556640625,-0.6016842722892761,-0.9338604211807251,56.12373733520508,-0.7418932318687439,-1.0166754722595215,56.114463806152344,-0.9054592847824097,-1.0991685390472412,56.251434326171875,-1.1213624477386475,-1.1531919240951538,56.45637512207031,-1.414699673652649,-1.1159791946411133,56.69541931152344,-1.7275384664535522,-1.0078744888305664,56.96617126464844,-1.982285499572754,-0.8618897795677185,57.32954788208008,-2.14268159866333,-0.6719604134559631,57.75862503051758,-2.3482024669647217,-0.5176432132720947,58.20808792114258,-2.5481650829315186,-0.40313923358917236,58.6833610534668,-2.7450201511383057,-0.3385993242263794,59.2278938293457,-2.976700782775879,-0.2878805696964264,59.768836975097656,-3.184596538543701,-0.21636344492435455,60.222198486328125,-3.2928733825683594,0.004610493779182434,60.5987434387207,-3.3430166244506836,0.13491761684417725,60.84749221801758,-3.324899673461914,0.17532069981098175,60.92778778076172,-3.1922411918640137,0.3529963493347168,60.8262825012207,-2.9726598262786865,0.767279326915741,60.4658203125,-2.6867713928222656,1.461098551750183,59.92803955078125,-2.3271420001983643,2.2526755332946777,59.47617721557617,-1.4827978610992432,2.860767126083374,58.94089889526367,-0.3461868464946747,3.0225493907928467,58.44818115234375,1.087772011756897,3.643036127090454,58.190547943115234,2.194953203201294,3.7966196537017822,57.942588806152344,3.587484121322632,3.8630590438842773,58.05365753173828,5.100348949432373,3.885833263397217,58.403289794921875,6.357968807220459,3.9838387966156006,58.75883865356445,7.494500637054443,4.196979522705078,58.88621520996094,8.604361534118652,4.463954448699951,58.8189582824707,9.721515655517578,4.72943639755249,58.56892395019531,10.74023151397705,5.084789276123047,58.08723831176758,11.687664985656738,5.3352861404418945,57.59356689453125,12.65117359161377,5.611867427825928,57.179622650146484,13.510286331176758,5.841495513916016,56.64195251464844,14.642765045166016,5.978096008300781,56.11042785644531,15.836421012878418,5.943517208099365,55.81796646118164,17.01910400390625,5.851102352142334,55.78116226196289,18.2487735748291,6.1079559326171875,56.62559127807617,19.939271926879883,6.114113807678223,57.44379806518555,21.347911834716797,6.280303478240967,58.80095291137695,22.926664352416992,6.436324596405029,60.08711242675781,24.182016372680664,6.539061069488525,61.123321533203125,25.172183990478516,6.656832695007324,62.06179428100586,26.139326095581055,6.783809661865234,62.93651580810547,27.133575439453125,6.9023895263671875,63.661842346191406,28.099763870239258,7.024378776550293,64.26797485351562,29.130386352539062,7.1341962814331055,64.673583984375,30.177654266357422,7.208714485168457,64.83621215820312,31.20856475830078,7.218499183654785,64.82466888427734,32.2325325012207,7.086217403411865,64.62474060058594,33.297210693359375,6.8209333419799805,64.31747436523438,34.4244384765625,6.549182891845703,64.10235595703125,35.65305709838867,6.353745460510254,63.850685119628906,36.959754943847656,6.304308891296387,63.559444427490234,38.285362243652344,6.309323310852051,63.00397491455078,39.72880935668945,6.3337931632995605,62.22782516479492,41.120811462402344,6.348611831665039,61.52271270751953,42.3232307434082,6.335160732269287,60.8929443359375,43.32312774658203,6.271069049835205,60.21820831298828,44.127685546875,6.164546012878418,59.48382568359375,44.99279022216797,6.00084924697876,58.852012634277344,46.03312683105469,5.795962333679199,58.26277542114258,47.10599899291992,5.390137195587158,57.72060775756836,48.302940368652344,4.80778169631958,57.41838455200195,49.96641159057617,4.043550968170166,57.47618865966797,51.99249267578125,3.0694143772125244,57.99717330932617,54.1519775390625,2.237022638320923,58.36261749267578,55.99559783935547,1.6294550895690918,58.663978576660156,57.72461700439453,1.2281360626220703,58.93765640258789,59.26199722290039,0.8294920921325684,59.60144805908203,61.03555679321289,0.502044677734375,60.298973083496094,62.706275939941406,0.2059178352355957,61.077850341796875,64.24629974365234,-0.043511390686035156,61.78984832763672,65.85041046142578,-0.26275634765625,62.29792022705078,67.51805114746094,-0.45856142044067383,62.59745407104492,69.37371063232422,-0.5314559936523438,62.61785125732422,71.58712005615234,-0.4283785820007324,62.40882873535156,74.14940643310547,-0.22312498092651367,62.18868637084961,76.63622283935547,0.1340789794921875,61.9525146484375,78.8487319946289,0.6770553588867188,61.100486755371094,81.03330993652344,1.2173471450805664,59.97623062133789,83.0361557006836,1.6553888320922852,59.060874938964844,84.85073852539062,1.649247169494629,58.67424011230469,86.39253997802734,1.4826478958129883,58.479610443115234,87.64776611328125,1.284482479095459,58.373958587646484,88.84265899658203,1.1425180435180664,58.283843994140625,89.95478820800781,0.9942507743835449,58.07050704956055,90.91831970214844,0.7870941162109375,57.71174621582031,91.76636505126953,0.6352939605712891,57.54385757446289,92.40302276611328,0.597902774810791,57.55157470703125,92.93280792236328,0.6329336166381836,57.60364532470703,93.31060791015625,0.6623029708862305,57.6735725402832,93.48802947998047,0.6506428718566895,57.74392318725586,93.59590148925781,0.6533102989196777,57.89592361450195,93.6672134399414,0.7182722091674805,58.005130767822266,93.54376220703125,0.8324069976806641,58.12457275390625,93.31049346923828,0.9628877639770508,58.180320739746094,92.86903381347656,1.1438016891479492,58.122440338134766,92.28075408935547,1.3387675285339355,57.98112487792969,91.61113739013672,1.5098953247070312,57.74934005737305,90.81010437011719,1.61976957321167,57.47880554199219,89.92535400390625,1.6188569068908691,57.27084732055664,89.01678466796875,1.5332651138305664,57.2153205871582,88.04105377197266,1.3398313522338867,57.41921615600586,87.03388214111328,1.0124025344848633,57.76841354370117,85.96669006347656,0.6637454032897949,58.12934112548828,84.88141632080078,0.31711244583129883,58.395938873291016,83.94519805908203,-0.057080745697021484,58.66661834716797,83.24140167236328,-0.4317793846130371,58.96745300292969,82.543212890625,-0.7296295166015625,59.16238021850586,81.85116577148438,-0.9078497886657715,59.221153259277344,81.17950439453125,-0.9629931449890137,59.133670806884766,80.5527114868164,-0.9370722770690918,58.794185638427734,80.05147552490234,-0.8627104759216309,58.24051284790039,79.65845489501953,-0.7553143501281738,57.56953811645508,79.29008483886719,-0.6282835006713867,56.878231048583984,78.93815612792969,-0.4929952621459961,56.29975128173828,78.60285949707031,-0.35947132110595703,55.9033317565918,78.29269409179688,-0.24126100540161133,55.704933166503906,77.9816665649414,-0.11040925979614258,55.682125091552734,77.67256164550781,-0.06344938278198242,55.80465316772461,77.37322998046875,-0.07973432540893555,55.998016357421875,77.09217834472656,-0.12400293350219727,56.17729187011719,76.83585357666016,-0.18832969665527344,56.31276321411133,76.60865020751953,-0.2649092674255371,56.390777587890625,76.41412353515625,-0.3459205627441406,56.39649963378906,76.2556381225586,-0.42384862899780273,56.37015151977539,76.13335418701172,-0.4909353256225586,56.34054946899414,76.04352569580078,-0.5392560958862305,56.31110763549805,75.98343658447266,-0.5604925155639648,56.27611541748047,75.96683502197266],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.28931355476379395,-0.33466553688049316,0.2663405239582062,0.8563640713691711,0.28641682863235474,-0.34268906712532043,0.2679421603679657,0.8536607027053833,0.2814655900001526,-0.3521296977996826,0.26980918645858765,0.8508729934692383,0.2749410569667816,-0.36244997382164,0.27200600504875183,0.8479682803153992,0.2678745687007904,-0.3712531328201294,0.2740928828716278,0.8457466959953308,0.26103153824806213,-0.3774826228618622,0.275804728269577,0.844571590423584,0.25438329577445984,-0.38342028856277466,0.2774813175201416,0.8433755040168762,0.24853171408176422,-0.3886300027370453,0.27895668148994446,0.8422481417655945,0.24288898706436157,-0.39298877120018005,0.2797199785709381,0.8416184186935425,0.23709207773208618,-0.39601486921310425,0.2803242802619934,0.8416519165039062,0.2314048707485199,-0.39343148469924927,0.280307412147522,0.8444473147392273,0.22797580063343048,-0.382693886756897,0.2783285677433014,0.8509439826011658,0.2255682349205017,-0.36953556537628174,0.27533188462257385,0.8583442568778992,0.2225915789604187,-0.36145004630088806,0.2728957235813141,0.8633278012275696,0.2199268639087677,-0.3567512035369873,0.27047863602638245,0.8667191863059998,0.21791449189186096,-0.3537074625492096,0.26791226863861084,0.8692682981491089,0.21699833869934082,-0.3511776030063629,0.2649453282356262,0.8714298605918884,0.21696333587169647,-0.34727591276168823,0.26177477836608887,0.8739567399024963,0.21805857121944427,-0.3426741063594818,0.2581656873226166,0.8765702843666077,0.2212531864643097,-0.3371150493621826,0.2538761496543884,0.8791742920875549,0.22673405706882477,-0.33009594678878784,0.2471817582845688,0.8823432326316833,0.2337186634540558,-0.3220924437046051,0.23978011310100555,0.8855154514312744,0.2449071854352951,-0.30957964062690735,0.2296198457479477,0.8896379470825195,0.25752463936805725,-0.2894015908241272,0.21539141237735748,0.8964008092880249,0.2710700035095215,-0.263532429933548,0.19953763484954834,0.9040223956108093,0.29164639115333557,-0.22415946424007416,0.17970344424247742,0.9123604893684387,0.31639453768730164,-0.16931647062301636,0.151546910405159,0.9210103154182434,0.3369017541408539,-0.1219068244099617,0.12196594476699829,0.9256134629249573,0.35609951615333557,-0.0794818326830864,0.10080576688051224,0.9255884289741516,0.37489446997642517,-0.03866861015558243,0.0867081806063652,0.9221932888031006,0.39382651448249817,0.0070755621418356895,0.07835585623979568,0.9158116579055786,0.409453421831131,0.04949987307190895,0.07117198407649994,0.9082027077674866,0.4251145124435425,0.0915907770395279,0.06490539014339447,0.8981514573097229,0.4404236376285553,0.1351131796836853,0.06104803830385208,0.8854629397392273,0.45609214901924133,0.17787589132785797,0.0566561259329319,0.870132327079773,0.47029805183410645,0.2233329564332962,0.05025660991668701,0.8523006439208984,0.4818311929702759,0.27066996693611145,0.04407717287540436,0.8322461843490601,0.49171167612075806,0.32270774245262146,0.03909916803240776,0.8078060746192932,0.5005301237106323,0.36513328552246094,0.035797808319330215,0.7841338515281677,0.5093001127243042,0.39059194922447205,0.03254401683807373,0.7661541700363159,0.5162283778190613,0.40695011615753174,0.03254189342260361,0.7528883814811707,0.5198119878768921,0.41944196820259094,0.03328792750835419,0.7434754967689514,0.5190837979316711,0.4339611828327179,0.027854744344949722,0.7358354926109314,0.5123883485794067,0.44890135526657104,0.016488196328282356,0.7318974137306213,0.499032199382782,0.45928528904914856,-0.0044795977883040905,0.7348495125770569,0.4877471923828125,0.4612392783164978,-0.020773597061634064,0.7408977150917053,0.4725467562675476,0.46257683634757996,-0.036158397793769836,0.7492762207984924,0.4553796350955963,0.4605802297592163,-0.05224943906068802,0.7601087093353271,0.44065359234809875,0.4583767056465149,-0.06586562842130661,0.7690103650093079,0.42608460783958435,0.4588955044746399,-0.07780459523200989,0.7757662534713745,0.4147455096244812,0.45767495036125183,-0.0867004245519638,0.7816666960716248,0.4054122269153595,0.45504528284072876,-0.09127480536699295,0.7875555157661438,0.39886462688446045,0.45031270384788513,-0.09272219240665436,0.7934280633926392,0.3952375650405884,0.43850743770599365,-0.08836094290018082,0.8023034334182739,0.3904210329055786,0.42445316910743713,-0.07870731502771378,0.8131519556045532,0.3855946958065033,0.4088069498538971,-0.06420916318893433,0.8246640563011169,0.38004374504089355,0.39021527767181396,-0.0426216758787632,0.8375453352928162,0.3668314814567566,0.36372166872024536,-0.014246123842895031,0.8561181426048279,0.3438691794872284,0.3199540972709656,0.02079314924776554,0.8825820088386536,0.31217655539512634,0.26456916332244873,0.055793751031160355,0.9107337594032288,0.2847016453742981,0.21044398844242096,0.08356261253356934,0.9314910769462585,0.27148059010505676,0.16117791831493378,0.10857218503952026,0.942619800567627,0.2709077298641205,0.1110241487622261,0.13033196330070496,0.9472572207450867,0.2757686674594879,0.05488036945462227,0.1514018326997757,0.9476377367973328,0.2882617712020874,0.007804058957844973,0.16862590610980988,0.9425548315048218,0.3064255714416504,-0.025215549394488335,0.1812686324119568,0.9341355562210083,0.3175475597381592,-0.07063020765781403,0.19471652805805206,0.92534339427948,0.32744941115379333,-0.11632676422595978,0.20585182309150696,0.9148060083389282,0.3340810537338257,-0.1473412811756134,0.21292734146118164,0.9062793850898743,0.3412227928638458,-0.17818784713745117,0.2155357450246811,0.8974187970161438,0.34999555349349976,-0.21880292892456055,0.21376271545886993,0.8854004144668579,0.35596102476119995,-0.25858253240585327,0.21300078928470612,0.87238609790802,0.35232165455818176,-0.29382890462875366,0.21792596578598022,0.8614187836647034,0.3416656255722046,-0.32345694303512573,0.2233634889125824,0.8536679744720459,0.3281574249267578,-0.3440249264240265,0.2303355485200882,0.8490613698959351,0.3117934465408325,-0.35816359519958496,0.2386574149131775,0.8470810651779175,0.29567503929138184,-0.36701250076293945,0.24840600788593292,0.8462698459625244,0.28261542320251465,-0.3665550947189331,0.25502729415893555,0.8489564061164856,0.27287033200263977,-0.358834832906723,0.25809893012046814,0.8544965386390686,0.26556241512298584,-0.3488253653049469,0.2586572468280792,0.8607519865036011,0.26213982701301575,-0.33910125494003296,0.25563496351242065,0.8665701746940613,0.2612076699733734,-0.32478296756744385,0.24946171045303345,0.8741026520729065,0.26210886240005493,-0.29823192954063416,0.23644503951072693,0.8868204355239868,0.2644706070423126,-0.25808852910995483,0.217321515083313,0.9034472703933716,0.267517626285553,-0.21051830053329468,0.19266732037067413,0.9203236699104309,0.27082303166389465,-0.15962161123752594,0.16332517564296722,0.9351474642753601,0.2727128267288208,-0.12234916538000107,0.137055441737175,0.9443908929824829,0.2822928726673126,-0.08674536645412445,0.119700588285923,0.947870135307312,0.29416725039482117,-0.05090134218335152,0.10400675982236862,0.948713481426239,0.3123658001422882,-0.00975467637181282,0.09132494032382965,0.9455115795135498,0.3328399360179901,0.03583741560578346,0.07872746884822845,0.939007580280304,0.35276633501052856,0.07676809281110764,0.06944524496793747,0.9299677014350891,0.37332648038864136,0.11395247280597687,0.06332380324602127,0.9184945821762085,0.3934278190135956,0.1527591496706009,0.05922437459230423,0.9046390056610107,0.4120885729789734,0.19420798122882843,0.05729448050260544,0.8883600831031799,0.4281103014945984,0.23253269493579865,0.055231597274541855,0.8715501427650452,0.4425550401210785,0.26541852951049805,0.05544678866863251,0.8547652959823608,0.4534033536911011,0.2879897356033325,0.05814490467309952,0.841490626335144,0.45897695422172546,0.29538238048553467,0.06267800182104111,0.8355602025985718,0.45909860730171204,0.2896754741668701,0.06832920759916306,0.8370470404624939,0.45466721057891846,0.26926860213279724,0.07601764053106308,0.8455728888511658,0.44539839029312134,0.23687852919101715,0.08724156022071838,0.8590097427368164,0.43353402614593506,0.19417470693588257,0.10088800638914108,0.8741658926010132,0.41994184255599976,0.14155329763889313,0.11856784671545029,0.8885680437088013,0.4078109860420227,0.0894024521112442,0.13545218110084534,0.898526668548584,0.39443090558052063,0.03299520164728165,0.1528542935848236,0.9055225849151611,0.37990501523017883,-0.024937273934483528,0.16996751725673676,0.9089341759681702,0.3646472096443176,-0.08141876757144928,0.1861226111650467,0.9087143540382385,0.34937402606010437,-0.13346801698207855,0.2007167488336563,0.9054484367370605,0.3350992500782013,-0.17830979824066162,0.21310435235500336,0.9002781510353088,0.3229130506515503,-0.21346347033977509,0.2229008823633194,0.8946930766105652,0.3135809600353241,-0.23647715151309967,0.23042255640029907,0.8903095126152039,0.3080368936061859,-0.2518453896045685,0.23555462062358856,0.8866798877716064,0.30332306027412415,-0.26491522789001465,0.24006713926792145,0.8832796216011047,0.2993185520172119,-0.27600181102752686,0.2441023886203766,0.8801394701004028,0.29607465863227844,-0.2854764759540558,0.2474897801876068,0.8772638440132141,0.2934889793395996,-0.29353028535842896,0.2503327429294586,0.8746643662452698,0.2913535535335541,-0.30032068490982056,0.25288453698158264,0.8723359704017639,0.28953588008880615,-0.306080162525177,0.2552773356437683,0.8702398538589478,0.2879895567893982,-0.31105291843414307,0.25743040442466736,0.8683534264564514,0.28665849566459656,-0.31552305817604065,0.25928428769111633,0.8666278123855591,0.2854446470737457,-0.3197631239891052,0.2609480917453766,0.8649734258651733,0.28428030014038086,-0.3238932490348816,0.2624678313732147,0.8633588552474976,0.2834722101688385,-0.32726746797561646,0.2636549770832062,0.8619893789291382,0.28306418657302856,-0.3298761546611786,0.264480322599411,0.8608754873275757,0.28295668959617615,-0.33180174231529236,0.26509594917297363,0.8599810004234314,0.2830962836742401,-0.3331412672996521,0.2655221223831177,0.8592854142189026,0.2834526300430298,-0.3340027332305908,0.2657833993434906,0.8587526082992554,0.28398776054382324,-0.33446651697158813,0.26590752601623535,0.8583567142486572,0.2846572697162628,-0.3346068859100342,0.2659326195716858,0.8580724596977234,0.28541627526283264,-0.33451470732688904,0.26589447259902954,0.8578680753707886,0.28621846437454224,-0.33428478240966797,0.26583048701286316,0.8577102422714233,0.2870182991027832,-0.33401182293891907,0.26577821373939514,0.8575654625892639,0.28776636719703674,-0.3338046371936798,0.2657778561115265,0.857395589351654,0.28841742873191833,-0.33377110958099365,0.2658638656139374,0.8571631908416748,0.28892752528190613,-0.3340141475200653,0.26607105135917664,0.8568323254585266,0.28925901651382446,-0.33463647961616516,0.26642245054244995,0.856368362903595],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998807907104,0.9999998211860657,0.9999997615814209,0.9999998211860657,0.9999998211860657,0.9999997615814209],"interpolation":2300,"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,4.566666603088379],"values":[8.3056001663208,-6.711591720581055,5.71929931640625,8.305609703063965,-6.711594581604004,5.719291687011719],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.6468847990036011,-0.06061564385890961,-0.448291152715683,0.6139225363731384,0.6482616662979126,-0.05424141511321068,-0.45162469148635864,0.6106143593788147,0.6508926749229431,-0.04587846249341965,-0.4576449394226074,0.60398268699646,0.6549796462059021,-0.03650427609682083,-0.4650115370750427,0.5945026278495789,0.6598841547966003,-0.027681199833750725,-0.4721916615962982,0.5837993025779724,0.6651186943054199,-0.020121702924370766,-0.47799721360206604,0.5733506679534912,0.6711938381195068,-0.012458978220820427,-0.4819668233394623,0.5630732774734497,0.6778212785720825,-0.0050193811766803265,-0.48315271735191345,0.5541629195213318,0.6855608820915222,0.002961542923003435,-0.48181527853012085,0.5457574725151062,0.695499062538147,0.007072797976434231,-0.4782731533050537,0.5361769199371338,0.70730060338974,0.0054937792010605335,-0.46833738684654236,0.5294861793518066,0.7197636365890503,-0.0007350030355155468,-0.4496290385723114,0.5289360284805298,0.7321625351905823,-0.007982338778674603,-0.42840901017189026,0.5294713973999023,0.7448391318321228,-0.012856807559728622,-0.4129314124584198,0.523962676525116,0.7581589818000793,-0.015241075307130814,-0.40192922949790955,0.5132402181625366,0.7700322270393372,-0.020334133878350258,-0.392084002494812,0.5028984546661377,0.7816500067710876,-0.025759296491742134,-0.38325026631355286,0.49140501022338867,0.7781476974487305,-0.013460882939398289,-0.38358408212661743,0.4971601366996765,0.7618076205253601,0.0027116399724036455,-0.3883962035179138,0.5184497833251953,0.7499949932098389,0.012537244707345963,-0.3908836543560028,0.5334418416023254,0.7446998357772827,0.02483159862458706,-0.3939865827560425,0.5381264090538025,0.737696647644043,0.042588215321302414,-0.40107014775276184,0.5414172410964966,0.7274109721183777,0.061863306909799576,-0.40563881397247314,0.550002932548523,0.7152422666549683,0.07437393069267273,-0.3994998633861542,0.5685918927192688,0.7007265686988831,0.08641088008880615,-0.38988593220710754,0.591188907623291,0.6775673627853394,0.08645759522914886,-0.36656835675239563,0.6317081451416016,0.6760634183883667,0.06854185461997986,-0.33130112290382385,0.6545836329460144,0.6987981796264648,0.032492496073246,-0.2942754030227661,0.6511738300323486,0.7010397911071777,0.002074055839329958,-0.2671792805194855,0.661176323890686,0.6808992624282837,-0.01181059330701828,-0.25262925028800964,0.6873247623443604,0.6702515482902527,-0.029282134026288986,-0.2365177720785141,0.7028263211250305,0.6698158383369446,-0.04983195289969444,-0.21798807382583618,0.7080570459365845,0.6732216477394104,-0.08215559273958206,-0.18838879466056824,0.7103046774864197,0.6801860928535461,-0.1198774054646492,-0.15174803137779236,0.707070529460907,0.6835033893585205,-0.15198346972465515,-0.11565176397562027,0.7045202851295471,0.6850354075431824,-0.1770416498184204,-0.07910935580730438,0.7022282481193542,0.6853695511817932,-0.19804036617279053,-0.03888857364654541,0.6996687650680542,0.6823017001152039,-0.22015488147735596,0.0071221827529370785,0.6970980167388916,0.6720539927482605,-0.23802624642848969,0.04402143508195877,0.6998208165168762,0.6522840261459351,-0.2534075677394867,0.06366109848022461,0.7115175724029541,0.6259850263595581,-0.2667465806007385,0.06842155009508133,0.7295940518379211,0.6058617234230042,-0.2748996317386627,0.0683920681476593,0.7434273362159729,0.5948197841644287,-0.27640077471733093,0.07328193634748459,0.7512800097465515,0.5921973586082458,-0.2692156732082367,0.07968369871377945,0.755298376083374,0.6092278361320496,-0.24850885570049286,0.08208198100328445,0.7485634684562683,0.6253519058227539,-0.22266988456249237,0.08189317584037781,0.7434020042419434,0.6553272604942322,-0.18937478959560394,0.08671411126852036,0.7260606288909912,0.6833246946334839,-0.15162278711795807,0.09307374060153961,0.7081066966056824,0.7050536274909973,-0.11628133803606033,0.10272146016359329,0.6919727921485901,0.725274920463562,-0.08260700106620789,0.11625709384679794,0.6735255122184753,0.7435319423675537,-0.049869097769260406,0.13187122344970703,0.6536690592765808,0.7595457434654236,-0.02108030766248703,0.14872980117797852,0.6328706741333008,0.7748403549194336,0.006838604342192411,0.1631435602903366,0.6107043027877808,0.7887147665023804,0.032161224633455276,0.1715104579925537,0.589473307132721,0.8010599613189697,0.04812260717153549,0.17240546643733978,0.5711947679519653,0.8120501041412354,0.05261339247226715,0.16657181084156036,0.5568305850028992,0.8233454823493958,0.04877171292901039,0.16043958067893982,0.5422016978263855,0.8415704369544983,0.04275494068861008,0.15211093425750732,0.5165205001831055,0.8743936419487,0.0342339351773262,0.12874357402324677,0.4665713608264923,0.9125217199325562,0.025284793227910995,0.09791234880685806,0.3963305652141571,0.9431277513504028,0.015796417370438576,0.07151881605386734,0.32426154613494873,0.961471438407898,0.0014385776594281197,0.050178516656160355,0.2702826261520386,0.9708435535430908,-0.013939996249973774,0.02716655656695366,0.23776130378246307,0.977417528629303,-0.03387577086687088,-0.00042397237848490477,0.20858395099639893,0.9788419008255005,-0.05263010784983635,-0.020565854385495186,0.19666127860546112,0.975688099861145,-0.06483474373817444,-0.030484884977340698,0.207122802734375,0.9760733842849731,-0.07286591827869415,-0.04946068301796913,0.19880880415439606,0.9774028658866882,-0.08452507108449936,-0.06531209498643875,0.18241062760353088,0.9786666631698608,-0.09796920418739319,-0.06320154666900635,0.16917207837104797,0.9811913967132568,-0.10586128383874893,-0.055112000554800034,0.1517217457294464,0.9843056201934814,-0.12402892112731934,-0.04159857705235481,0.11844354122877121,0.9882962703704834,-0.129177063703537,-0.044275909662246704,0.06799595803022385,0.977654755115509,-0.14483636617660522,-0.08859823644161224,0.1239512711763382,0.965475857257843,-0.14319385588169098,-0.13664959371089935,0.1693481057882309,0.9543758034706116,-0.13725627958774567,-0.1745465248823166,0.19965235888957977,0.9475107192993164,-0.1274653673171997,-0.20741845667362213,0.2072525918483734,0.9339051842689514,-0.11729412525892258,-0.24287743866443634,0.2346779704093933,0.9162881970405579,-0.0963701456785202,-0.2695635259151459,0.2801147997379303,0.8949077129364014,-0.06914844363927841,-0.28407612442970276,0.3371341824531555,0.8673478960990906,-0.02547219954431057,-0.290824294090271,0.4030880928039551,0.8415985107421875,0.011601484380662441,-0.2937825620174408,0.4530663788318634,0.8124635219573975,0.04044179245829582,-0.29194095730781555,0.5030287504196167,0.7898662090301514,0.05451590195298195,-0.2781628668308258,0.5438425540924072,0.7763984203338623,0.05128249526023865,-0.2508901059627533,0.5758730173110962,0.7647883296012878,0.0425214022397995,-0.21449412405490875,0.6060388088226318,0.7532473206520081,0.0273487139493227,-0.1715417504310608,0.6343847513198853,0.7462988495826721,0.008873158134520054,-0.14139866828918457,0.6503582000732422,0.7511498332023621,-0.01353335753083229,-0.11434277892112732,0.650012731552124,0.7550995945930481,-0.02818142995238304,-0.08415006101131439,0.6495762467384338,0.7360278367996216,-0.043323397636413574,-0.04711826518177986,0.6739183664321899,0.7179001569747925,-0.05621183291077614,-0.003313317894935608,0.6938650012016296,0.7012354731559753,-0.06877859681844711,0.03465626761317253,0.7087575793266296,0.6840723752975464,-0.08155489712953568,0.06960570067167282,0.721490740776062,0.6646395921707153,-0.09555978327989578,0.10548444837331772,0.733481764793396,0.64298415184021,-0.11042207479476929,0.14350439608097076,0.744167149066925,0.6258465647697449,-0.12160149961709976,0.1783711165189743,0.7494751214981079,0.613971471786499,-0.1315649449825287,0.20696771144866943,0.7502626776695251,0.607146143913269,-0.14015571773052216,0.22577975690364838,0.7488346695899963,0.6063515543937683,-0.14665555953979492,0.23048418760299683,0.7467978000640869,0.6105480790138245,-0.1519399881362915,0.2229110598564148,0.7446179986000061,0.6209917068481445,-0.15620416402816772,0.20119889080524445,0.7412749528884888,0.6349354386329651,-0.1616688370704651,0.1677084118127823,0.7366098761558533,0.6509794592857361,-0.16626715660095215,0.12380775809288025,0.7302414774894714,0.6663386225700378,-0.170421302318573,0.06898319721221924,0.722627580165863,0.6756777763366699,-0.17246869206428528,0.014299428090453148,0.7165958285331726,0.6817436814308167,-0.17156201601028442,-0.04506592079997063,0.7097613215446472,0.6840308904647827,-0.1667947769165039,-0.10645737498998642,0.7021026015281677,0.6835840344429016,-0.15788374841213226,-0.16666127741336823,0.6928272843360901,0.6825512051582336,-0.14531655609607697,-0.22243201732635498,0.6808310747146606,0.6823233366012573,-0.13104356825351715,-0.2707865238189697,0.6662861108779907,0.6851155161857605,-0.11732782423496246,-0.309243768453598,0.6490139961242676,0.6903886795043945,-0.10790880024433136,-0.33530884981155396,0.6318917274475098,0.6945196390151978,-0.10235103964805603,-0.35289615392684937,0.6185718178749084,0.697417676448822,-0.09860949218273163,-0.3677740693092346,0.6071466207504272,0.7010279297828674,-0.09539926797151566,-0.3804706335067749,0.5955677032470703,0.7053378820419312,-0.0914248675107956,-0.3916543424129486,0.5837351679801941,0.7082173824310303,-0.08669044822454453,-0.40166664123535156,0.5740877389907837,0.7087753415107727,-0.08304428309202194,-0.4101346731185913,0.5679177045822144,0.7066924571990967,-0.08037776499986649,-0.4172338843345642,0.5657216310501099,0.6999795436859131,-0.07788475602865219,-0.42317628860473633,0.5699862837791443,0.6894323825836182,-0.07502390444278717,-0.42834123969078064,0.5792911052703857,0.6770423054695129,-0.07160207629203796,-0.43313220143318176,0.5906634330749512,0.6645486950874329,-0.06790702044963837,-0.43765631318092346,0.601847767829895,0.6539938449859619,-0.06486175209283829,-0.44121065735816956,0.6110795140266418,0.6463053822517395,-0.06291183084249496,-0.44379258155822754,0.6175593733787537,0.6417917013168335,-0.0621035173535347,-0.4455762505531311,0.6210542321205139,0.6399831771850586,-0.06261597573757172,-0.44661644101142883,0.6221209764480591,0.640896201133728,-0.06329892575740814,-0.4473375678062439,0.6205919981002808,0.6433412432670593,-0.06391488015651703,-0.44780394434928894,0.6176557540893555,0.6458470821380615,-0.0643070787191391,-0.44800618290901184,0.6148468255996704,0.6478654146194458,-0.06435869634151459,-0.4480213522911072,0.6127032041549683,0.6490821242332458,-0.0640447586774826,-0.44792041182518005,0.6115211248397827,0.6491544246673584,-0.06333033740520477,-0.4477711021900177,0.6116281151771545,0.6486841440200806,-0.06248868256807327,-0.4476735293865204,0.6122846603393555,0.6481070518493652,-0.06173058971762657,-0.4476962089538574,0.6129556894302368,0.6474347114562988,-0.06110000237822533,-0.4479577839374542,0.6135380864143372,0.6466274261474609,-0.06048927828669548,-0.44828736782073975,0.6142088770866394],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999996423721313,0.9999997615814209,0.9999992251396179,1,1,0.9999992251396179],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,4.566666603088379],"values":[0.0000010013253586294013,25.812700271606445,-0.000004021821041533258,1.568666903040139e-7,25.81269645690918,-0.000006432595000660513],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.4074153006076813,0.21364398300647736,0.06289565563201904,0.8856710195541382,0.4101026654243469,0.21005591750144958,0.06290005892515182,0.8852885961532593,0.41111087799072266,0.2059529572725296,0.06609835475683212,0.8855519890785217,0.4107498228549957,0.20153199136257172,0.07176853716373444,0.886294960975647,0.4089035391807556,0.19777309894561768,0.07947900891304016,0.8873369693756104,0.40579044818878174,0.19483186304569244,0.08825359493494034,0.8885864615440369,0.40196916460990906,0.19073031842708588,0.09607932716608047,0.8903996348381042,0.39783141016960144,0.185256689786911,0.10194827616214752,0.8927578926086426,0.39248836040496826,0.1787581443786621,0.1076117530465126,0.8957779407501221,0.3843042850494385,0.16344711184501648,0.10789791494607925,0.9021936058998108,0.3779529929161072,0.1324038803577423,0.10182591527700424,0.9106329083442688,0.3714376389980316,0.09313733130693436,0.09318020194768906,0.919063150882721,0.3623995780944824,0.05494704470038414,0.08627202361822128,0.9263932704925537,0.346121221780777,0.02806166373193264,0.08555327355861664,0.9338593482971191,0.3179827034473419,0.014633212238550186,0.09062451869249344,0.9436419010162354,0.2870866656303406,0.00864367838948965,0.08845371752977371,0.9537727236747742,0.2534118890762329,0.005505331791937351,0.08229965716600418,0.9638355374336243,0.2579544186592102,0.012615310028195381,0.09321820735931396,0.9615668058395386,0.30019378662109375,0.02695241943001747,0.09632330387830734,0.9486196041107178,0.3278547525405884,0.03147120401263237,0.0848398208618164,0.9403845071792603,0.3412964642047882,0.009193404577672482,0.07302042096853256,0.9370700120925903,0.35945749282836914,-0.026058683171868324,0.06669054925441742,0.9304104447364807,0.3753540515899658,-0.04709906503558159,0.06931664794683456,0.9230851531028748,0.3871402144432068,-0.034023407846689224,0.06960646063089371,0.9187599420547485,0.3993551433086395,-0.011676318943500519,0.07876423001289368,0.9133319854736328,0.42155173420906067,-0.017354771494865417,0.07358056306838989,0.9036475419998169,0.3899095356464386,-0.034675706177949905,0.07378357648849487,0.9172372221946716,0.30599093437194824,-0.04136594012379646,0.05502770096063614,0.949542224407196,0.2601921558380127,-0.012143868021667004,0.05214618518948555,0.9640712141990662,0.28608325123786926,0.0017635294934734702,0.08405713737010956,0.9545091390609741,0.29238030314445496,0.013568535447120667,0.11849669367074966,0.9488351941108704,0.28217896819114685,0.028878889977931976,0.13961075246334076,0.9487096071243286,0.2553858757019043,0.04985155537724495,0.13029152154922485,0.9567220211029053,0.21557965874671936,0.07236635684967041,0.11076200008392334,0.9674814343452454,0.18081115186214447,0.0921366810798645,0.09878291934728622,0.9741971492767334,0.1508726328611374,0.10981376469135284,0.09836918115615845,0.9774977564811707,0.1240040510892868,0.12438667565584183,0.10004802793264389,0.9793576598167419,0.10151560604572296,0.137960284948349,0.10140012204647064,0.9799895882606506,0.100645050406456,0.1474432349205017,0.10084058344364166,0.9787554740905762,0.13192118704319,0.15320701897144318,0.09739121049642563,0.9744943976402283,0.19563591480255127,0.15423911809921265,0.09992916136980057,0.9633021950721741,0.2528550326824188,0.15461157262325287,0.10614433884620667,0.9491538405418396,0.2916434109210968,0.15932410955429077,0.10683014988899231,0.9370951056480408,0.3224318027496338,0.16645339131355286,0.1076396107673645,0.925605058670044,0.33766794204711914,0.18166762590408325,0.11002407222986221,0.9169906973838806,0.3452269434928894,0.18956774473190308,0.1110309511423111,0.9124442934989929,0.3312785029411316,0.19538982212543488,0.11042557656764984,0.916451632976532,0.31639420986175537,0.19528084993362427,0.1088668629527092,0.9219046235084534,0.301932692527771,0.18979328870773315,0.10664939880371094,0.9281384944915771,0.28495100140571594,0.1817384660243988,0.10444667935371399,0.9353421330451965,0.2641885280609131,0.16846950352191925,0.10120780020952225,0.9442348480224609,0.24352124333381653,0.15436309576034546,0.0964982807636261,0.9526581764221191,0.21255595982074738,0.16293586790561676,0.09485580772161484,0.9587879180908203,0.18101230263710022,0.18634158372879028,0.0963105782866478,0.9608515501022339,0.16272935271263123,0.22113069891929626,0.10193286836147308,0.9561538100242615,0.1620500534772873,0.24945709109306335,0.10927348583936691,0.9484567642211914,0.1799641102552414,0.2505539655685425,0.11515011638402939,0.9442331194877625,0.20682275295257568,0.23122049868106842,0.1194014847278595,0.9431355595588684,0.22060590982437134,0.20929017663002014,0.11969161778688431,0.9450950026512146,0.22224868834018707,0.18849526345729828,0.11690851300954819,0.9494248032569885,0.209407776594162,0.1676456332206726,0.108859121799469,0.9571797251701355,0.19942401349544525,0.1456216275691986,0.10247752815485,0.963598906993866,0.20211286842823029,0.122922882437706,0.10066850483417511,0.9663882255554199,0.19613279402256012,0.09933948516845703,0.09455093741416931,0.9709396362304688,0.1935977041721344,0.07611630111932755,0.08931084722280502,0.9740378856658936,0.20937834680080414,0.05447093024849892,0.08918140083551407,0.9722347259521484,0.20955784618854523,0.03230104222893715,0.10078421980142593,0.9720518589019775,0.1958904266357422,0.00743694044649601,0.10284709930419922,0.9751893281936646,0.18500028550624847,-0.022109240293502808,0.09168809652328491,0.9782021641731262,0.16301530599594116,-0.057570960372686386,0.08276958018541336,0.9814585447311401,0.10939353704452515,-0.1182728111743927,0.06524365395307541,0.9847780466079712,0.03220051899552345,-0.1591663956642151,0.09428355097770691,0.982211709022522,0.18502934277057648,-0.1909462809562683,0.05527309700846672,0.9624180793762207,0.3128984272480011,-0.22313053905963898,0.02833111211657524,0.9227701425552368,0.38525038957595825,-0.2467990517616272,-0.00681311683729291,0.8891715407371521,0.40715596079826355,-0.23870332539081573,-0.01727244071662426,0.8814456462860107,0.44022881984710693,-0.21592889726161957,-0.026632938534021378,0.8711280226707458,0.48291143774986267,-0.1844659447669983,-0.03008451499044895,0.8554904460906982,0.5187453031539917,-0.15606842935085297,-0.028958482667803764,0.8400639295578003,0.5383954644203186,-0.11201107501983643,-0.007244381587952375,0.8351834416389465,0.5316133499145508,-0.06180492788553238,0.02671874687075615,0.8443065285682678,0.501296877861023,-0.010072256438434124,0.06132792681455612,0.8630405068397522,0.44665753841400146,0.03928423672914505,0.08897080272436142,0.8894031643867493,0.3875059187412262,0.08287027478218079,0.1046067327260971,0.9121562838554382,0.36903247237205505,0.11271447688341141,0.11216825246810913,0.9157122373580933,0.3836852014064789,0.13692736625671387,0.11757026612758636,0.9056565761566162,0.40749987959861755,0.15810728073120117,0.1231527030467987,0.8909429907798767,0.39548665285110474,0.18305523693561554,0.12792621552944183,0.8909074068069458,0.3898361027240753,0.2072553038597107,0.13282541930675507,0.8873728513717651,0.41368070244789124,0.2183297723531723,0.13251830637454987,0.8738645911216736,0.42321908473968506,0.22208842635154724,0.12550778687000275,0.8693734407424927,0.42620405554771423,0.22720485925674438,0.11861769109964371,0.8675585985183716,0.4250634014606476,0.23164166510105133,0.11120975762605667,0.8679260611534119,0.4216702878475189,0.23623026907444,0.10409458726644516,0.8692259788513184,0.41698047518730164,0.2399827241897583,0.0971948429942131,0.8712570071220398,0.4016793966293335,0.24456484615802765,0.09071577340364456,0.877845287322998,0.3774784505367279,0.24976585805416107,0.08513660728931427,0.8876253962516785,0.35447537899017334,0.2538233697414398,0.0804852694272995,0.8963498473167419,0.34067806601524353,0.2566768229007721,0.07724776118993759,0.901159405708313,0.3406507074832916,0.25825580954551697,0.07552281767129898,0.900864839553833,0.35101383924484253,0.2587878108024597,0.07534105330705643,0.8967396020889282,0.3692929446697235,0.25779828429222107,0.07588823139667511,0.8896088004112244,0.3903476893901825,0.2560370862483978,0.07642526179552078,0.8810408115386963,0.4120243787765503,0.25362157821655273,0.07701928168535233,0.8717683553695679,0.4321519434452057,0.2511666417121887,0.07781358808279037,0.8626152276992798,0.4539353549480438,0.2485044151544571,0.07880137860774994,0.8520438075065613,0.4754208028316498,0.2461923509836197,0.08020687103271484,0.8407920002937317,0.4941677749156952,0.24480809271335602,0.08234623819589615,0.8301122188568115,0.5067121982574463,0.2448391616344452,0.08537902683019638,0.8221964240074158,0.5094826221466064,0.24668042361736298,0.08922084420919418,0.8195217251777649,0.4990083575248718,0.2506488263607025,0.09365303069353104,0.8242542147636414,0.47987717390060425,0.24909593164920807,0.09392348676919937,0.8359709978103638,0.4603050649166107,0.2377449870109558,0.0884912982583046,0.8507443070411682,0.4456241726875305,0.22173067927360535,0.08105896413326263,0.8635300397872925,0.43124446272850037,0.20713885128498077,0.0751628577709198,0.8749127388000488,0.41051042079925537,0.20257419347763062,0.07393009215593338,0.885990560054779,0.3899468183517456,0.20458441972732544,0.07642107456922531,0.8945650458335876,0.37486031651496887,0.20655235648155212,0.07849754393100739,0.9003632664680481,0.3672550916671753,0.20836833119392395,0.07976934313774109,0.9029635787010193,0.3746173679828644,0.20912647247314453,0.0783715471625328,0.8998810052871704,0.39381152391433716,0.20916907489299774,0.07502973079681396,0.8919256925582886,0.41718536615371704,0.2087743878364563,0.07133694738149643,0.8816352486610413,0.4398788809776306,0.208375945687294,0.06812529265880585,0.8708875179290771,0.4569840729236603,0.20803479850292206,0.0656580701470375,0.8623086214065552,0.4670546054840088,0.20776419341564178,0.06378266215324402,0.857103168964386,0.46963950991630554,0.20766454935073853,0.06247889995574951,0.8558099269866943,0.4659879207611084,0.20764373242855072,0.061602141708135605,0.8578721284866333,0.4567136764526367,0.20803295075893402,0.06136883422732353,0.8627681136131287,0.4444657862186432,0.20871275663375854,0.06162574514746666,0.8689599633216858,0.4325424134731293,0.2093791663646698,0.062005624175071716,0.8747701048851013,0.422319233417511,0.2100280374288559,0.06238530948758125,0.8795696496963501,0.4146568179130554,0.21059025824069977,0.0626249685883522,0.8830569386482239,0.41048553586006165,0.21108198165893555,0.062620609998703,0.8848868608474731,0.4083709418773651,0.2114628106355667,0.06248325854539871,0.8857835531234741,0.40718528628349304,0.21167676150798798,0.06230601295828819,0.8862906098365784,0.4068085253238678,0.21179170906543732,0.06214802339673042,0.8864472508430481,0.4077579379081726,0.2136022299528122,0.06290263682603836,0.8855229020118713],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,4.566666603088379],"values":[1,1.000000238418579,1.0000001192092896,0.9999999403953552,1,1.000000238418579],"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,4.566666603088379],"values":[-1.5642922335246112e-7,22.742706298828125,0.0000012213664604132646,-0.000002781349394354038,22.742704391479492,0.0000037791955946886446],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[-0.45238855481147766,-0.04891098290681839,0.047231048345565796,0.8892252445220947,-0.45548176765441895,-0.05111099034547806,0.049412790685892105,0.8874020576477051,-0.4573431611061096,-0.05198245123028755,0.050949838012456894,0.8863064646720886,-0.4587189257144928,-0.05232798680663109,0.05159153416752815,0.8855377435684204,-0.45906561613082886,-0.05203423649072647,0.05106918141245842,0.8854056596755981,-0.4581993818283081,-0.0512784942984581,0.04964112862944603,0.8859794735908508,-0.45716825127601624,-0.0510985404253006,0.04912828281521797,0.8865509629249573,-0.4560466408729553,-0.05180143192410469,0.049956630915403366,0.8870413899421692,-0.4541754126548767,-0.05294926464557648,0.051219552755355835,0.8878612518310547,-0.4498220384120941,-0.05385338515043259,0.05162873491644859,0.8899968862533569,-0.4434318542480469,-0.05260981246829033,0.04800115153193474,0.893474280834198,-0.43484389781951904,-0.04929189756512642,0.04000202938914299,0.898265540599823,-0.42578259110450745,-0.04494596645236015,0.029748382046818733,0.9032187461853027,-0.416096955537796,-0.041263747960329056,0.02051643095910549,0.9081518054008484,-0.40116870403289795,-0.03768615424633026,0.014743523672223091,0.9151098728179932,-0.38509273529052734,-0.03721819818019867,0.014339402318000793,0.9220156073570251,-0.3683568239212036,-0.03873767331242561,0.01767226681113243,0.9287089705467224,-0.33850008249282837,-0.03026374615728855,0.006015083286911249,0.9404603242874146,-0.33406856656074524,-0.02190868742763996,-0.007620215881615877,0.9422633051872253,-0.34030500054359436,-0.01940801925957203,-0.011169569566845894,0.9400484561920166,-0.3666735291481018,-0.018424471840262413,-0.012211431749165058,0.9300870299339294,-0.4098191261291504,-0.018338635563850403,-0.0109186302870512,0.9119170904159546,-0.4488763213157654,-0.01987222582101822,-0.007029097061604261,0.893345296382904,-0.4814854860305786,-0.026652801781892776,0.005915733985602856,0.8760287761688232,-0.5104317665100098,-0.031193388625979424,0.01559089682996273,0.8592109084129333,-0.5198137760162354,-0.028150472790002823,0.010449989698827267,0.8537517189979553,-0.4845525324344635,-0.029752174392342567,0.009969217702746391,0.8741992115974426,-0.4283292293548584,-0.03892217203974724,0.02258697710931301,0.9025015234947205,-0.3167971670627594,-0.013990526087582111,-0.024642733857035637,0.9480699300765991,-0.3310082256793976,0.004981599282473326,-0.05645935982465744,0.9419241547584534,-0.33565187454223633,0.021109171211719513,-0.080954410135746,0.9382636547088623,-0.3345664143562317,0.02968096174299717,-0.09435567259788513,0.937166690826416,-0.32456204295158386,0.027745896950364113,-0.09035055339336395,0.9411303997039795,-0.308047890663147,0.02164842002093792,-0.08020765334367752,0.9477365612983704,-0.29309746623039246,0.018657300621271133,-0.0750228613615036,0.9529519081115723,-0.2790035009384155,0.01988896168768406,-0.07659988105297089,0.9570235013961792,-0.2650052011013031,0.021282631903886795,-0.07812963426113129,0.9608408212661743,-0.2517498731613159,0.02203107811510563,-0.07842661440372467,0.9643577337265015,-0.25126051902770996,0.021718701347708702,-0.07528327405452728,0.96474289894104,-0.2690589725971222,0.02275198884308338,-0.07246019691228867,0.9601245522499084,-0.3060183823108673,0.026533151045441628,-0.07327140867710114,0.9488309025764465,-0.3404993712902069,0.03054473176598549,-0.07637524604797363,0.9366397857666016,-0.36576640605926514,0.031239161267876625,-0.0739348828792572,0.9272392988204956,-0.3877544701099396,0.03056276962161064,-0.07008207589387894,0.9185863733291626,-0.40951013565063477,0.03007734753191471,-0.0684967115521431,0.9092331528663635,-0.4268278479576111,0.027297265827655792,-0.06415598839521408,0.9016411900520325,-0.43858009576797485,0.02283492684364319,-0.05784663185477257,0.896537721157074,-0.4479284882545471,0.017790358513593674,-0.05135050043463707,0.89241623878479,-0.4547557532787323,0.012455844320356846,-0.044437557458877563,0.889419674873352,-0.459746778011322,0.007508276961743832,-0.03721083700656891,0.8872383236885071,-0.4634936451911926,0.0020996718667447567,-0.029272103682160378,0.8856140971183777,-0.4672389328479767,-0.003930677659809589,-0.019947491586208344,0.8838973045349121,-0.4673466980457306,-0.012235412374138832,-0.004729586187750101,0.8839768171310425,-0.46946582198143005,-0.020277151837944984,0.011086777783930302,0.8826481699943542,-0.478310763835907,-0.02398882992565632,0.019795449450612068,0.8776397109031677,-0.4936230480670929,-0.022207675501704216,0.01840139366686344,0.86919766664505,-0.5165289044380188,-0.017671853303909302,0.011135286651551723,0.8560150265693665,-0.5457350611686707,-0.012237031012773514,0.002284480258822441,0.8378652930259705,-0.5742567777633667,-0.007046493701636791,-0.005628311540931463,0.8186256289482117,-0.6013652086257935,-0.0029639911372214556,-0.011571105569601059,0.7988849878311157,-0.6216185092926025,-0.0021911233197897673,-0.012114163488149643,0.7832234501838684,-0.641053318977356,-0.0009475317201577127,-0.012680616229772568,0.76739102602005,-0.6635688543319702,0.00146106060128659,-0.013908295892179012,0.7479845285415649,-0.6809779405593872,0.001210897695273161,-0.011033858172595501,0.7322198748588562,-0.6953961253166199,-0.00014517625095322728,-0.0070061879232525826,0.7185924649238586,-0.7126277685165405,-0.0009304046398028731,-0.003859884338453412,0.7015311121940613,-0.7265066504478455,0.0006740716053172946,-0.004614601843059063,0.6871436238288879,-0.7360203862190247,-0.0009306115680374205,-0.00013852723350282758,0.6769587397575378,-0.744647741317749,-0.0064260996878147125,0.010042463429272175,0.667351245880127,-0.7494728565216064,-0.012717085890471935,0.0192255899310112,0.6616337299346924,-0.7468062043190002,-0.009898616001009941,0.0086244260892272,0.6649121642112732,-0.7150892615318298,0.00877700001001358,-0.028666546568274498,0.6983899474143982,-0.7176574468612671,-0.0034063011407852173,-0.011064430698752403,0.6963001489639282,-0.6778335571289062,-0.018591666594147682,0.006945224013179541,0.7349474430084229,-0.5823605060577393,-0.03928104043006897,0.032091256231069565,0.811346709728241,-0.48364540934562683,-0.04574856534600258,0.030932096764445305,0.8735201358795166,-0.46174702048301697,-0.03189849853515625,0.008977288380265236,0.8863924741744995,-0.46089208126068115,-0.01626979373395443,-0.017163945361971855,0.8871410489082336,-0.47288817167282104,0.0017494854982942343,-0.046329379081726074,0.8799018859863281,-0.44725891947746277,0.012636709026992321,-0.064307302236557,0.8920001983642578,-0.41254377365112305,0.016148095950484276,-0.06980440020561218,0.9081157445907593,-0.35917872190475464,0.012707875110208988,-0.06399151682853699,0.930985689163208,-0.3000471591949463,0.003678402630612254,-0.04821005463600159,0.9526982307434082,-0.25601357221603394,-0.006728868931531906,-0.028231339529156685,0.9662374258041382,-0.27049532532691956,-0.012443087063729763,-0.013460892252624035,0.9625467658042908,-0.3214040994644165,-0.01395240519195795,-0.005118860863149166,0.9468255639076233,-0.3850356638431549,-0.01313871517777443,-0.0013451854465529323,0.9228072166442871,-0.42258283495903015,-0.015501084737479687,-0.001191573217511177,0.9061909914016724,-0.4665580093860626,-0.013401619158685207,-0.007342859636992216,0.8843585848808289,-0.4791865944862366,-0.009159316308796406,-0.013235860504209995,0.8775655031204224,-0.484353631734848,-0.009376171976327896,-0.011130922473967075,0.8747512698173523,-0.4876272678375244,-0.010707711800932884,-0.007891491055488586,0.872950553894043,-0.4892895221710205,-0.012980137951672077,-0.003787766210734844,0.8720166087150574,-0.48881053924560547,-0.016480501741170883,0.001773250289261341,0.8722324967384338,-0.4872437119483948,-0.020909732207655907,0.009413950145244598,0.8729649186134338,-0.48025503754615784,-0.02616974338889122,0.01836560294032097,0.8765460252761841,-0.469157874584198,-0.030928760766983032,0.026267828419804573,0.8821815848350525,-0.4587549567222595,-0.034192442893981934,0.03194873034954071,0.8873297572135925,-0.45293691754341125,-0.035571567714214325,0.03421121463179588,0.8901755213737488,-0.4546626806259155,-0.034472472965717316,0.03253437951207161,0.8894014954566956,-0.46268945932388306,-0.0306295994669199,0.02620939537882805,0.8856034278869629,-0.47430580854415894,-0.025092341005802155,0.016732659190893173,0.8798434138298035,-0.48805442452430725,-0.018562445417046547,0.005648262798786163,0.8725975155830383,-0.5024121403694153,-0.011889004148542881,-0.005550685338675976,0.8645287156105042,-0.5148949027061462,-0.006344352383166552,-0.014880786649882793,0.8571006655693054,-0.5287020206451416,-0.0011396931950002909,-0.023351984098553658,0.8484854698181152,-0.5421664714813232,0.0029415423050522804,-0.029713094234466553,0.8397404551506042,-0.5541379451751709,0.005389920901507139,-0.033447857946157455,0.8317351341247559,-0.5631160736083984,0.005981659982353449,-0.03402038663625717,0.8256555795669556,-0.5662668347358704,0.004511084407567978,-0.03134288266301155,0.8236135244369507,-0.5618603825569153,0.000863276538439095,-0.025570591911673546,0.826836347579956,-0.5517156720161438,-0.004969610366970301,-0.017018884420394897,0.8338438272476196,-0.5395259261131287,-0.011417563073337078,-0.008253414183855057,0.8418511748313904,-0.5297474265098572,-0.016535766422748566,-0.0010559773072600365,0.8479936122894287,-0.5211766362190247,-0.019734973087906837,0.0053627947345376015,0.8532037734985352,-0.5088855624198914,-0.023467333987355232,0.012217182666063309,0.8604274988174438,-0.4957716763019562,-0.026821041479706764,0.018403222784399986,0.8678435683250427,-0.4845961034297943,-0.030162567272782326,0.022866683080792427,0.8739187121391296,-0.4765987992286682,-0.03320588544011116,0.02567383460700512,0.8781182765960693,-0.47601357102394104,-0.034713443368673325,0.027806667611002922,0.8783125281333923,-0.4813334047794342,-0.03506126627326012,0.02946949377655983,0.8753401637077332,-0.48839759826660156,-0.03488932177424431,0.030801020562648773,0.8713792562484741,-0.49507442116737366,-0.03476943075656891,0.03172336146235466,0.8675748109817505,-0.499175488948822,-0.03483733534812927,0.03238386660814285,0.8651944994926453,-0.5003000497817993,-0.035049062222242355,0.03297780081629753,0.8645136952400208,-0.49816325306892395,-0.03556971997022629,0.03377151861786842,0.8656949400901794,-0.4932889938354492,-0.03625694662332535,0.034437522292137146,0.868427038192749,-0.48632052540779114,-0.03741014748811722,0.035804592072963715,0.8722447156906128,-0.4783852994441986,-0.038893986493349075,0.037568192929029465,0.8764835596084595,-0.4708181619644165,-0.040383774787187576,0.03940587490797043,0.8804240822792053,-0.46424680948257446,-0.04184272885322571,0.04121183976531029,0.8837565779685974,-0.4590778350830078,-0.04316709190607071,0.04294629395008087,0.886306881904602,-0.45575666427612305,-0.04433179646730423,0.04452615603804588,0.8878840208053589,-0.45369046926498413,-0.045298367738723755,0.04591886326670647,0.8888219594955444,-0.4523663818836212,-0.046018216758966446,0.047087471932172775,0.8893985748291016,-0.4517156481742859,-0.046557940542697906,0.04793357104063034,0.8896559476852417,-0.4523082375526428,-0.04895296320319176,0.04732098802924156,0.8892589807510376],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998211860657,0.9999998211860657,0.9999998807907104,0.9999999403953552,0.9999998807907104,0.9999998807907104],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,4.566666603088379],"values":[-4.204877086522174e-7,13.41511058807373,-9.384015697833092e-7,0.0000016509295619471231,13.415115356445312,-0.00001768080983310938],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[-0.3069814443588257,-0.005662640556693077,-0.001829215558245778,0.9516969323158264,-0.30698150396347046,-0.005662635900080204,-0.001829217653721571,0.9516968727111816],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999999403953552,0.9999999403953552,1.000000238418579,1,1,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,4.566666603088379],"values":[-11.139495849609375,-4.020097732543945,0.4203008711338043,-11.139490127563477,-4.0200958251953125,0.420298308134079],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.7078313231468201,-0.2818664610385895,0.46817681193351746,0.44758984446525574,0.7060065269470215,-0.2827876806259155,0.4682903587818146,0.4497666656970978,0.7001987099647522,-0.28526464104652405,0.46814626455307007,0.45736753940582275,0.692777693271637,-0.28843799233436584,0.4695630967617035,0.4651593267917633,0.6848514676094055,-0.2922333776950836,0.4724424183368683,0.47156792879104614,0.6772722601890564,-0.29644662141799927,0.4760778844356537,0.476205438375473,0.6716672778129578,-0.3008246421813965,0.47719964385032654,0.4802584648132324,0.6674270033836365,-0.3046424984931946,0.4770566523075104,0.4838915765285492,0.6649407148361206,-0.308312326669693,0.4758265018463135,0.48619604110717773,0.6669649481773376,-0.3116513192653656,0.4718390107154846,0.4851796627044678,0.670831561088562,-0.31593599915504456,0.46889036893844604,0.4799076318740845,0.675010621547699,-0.31918108463287354,0.47161635756492615,0.46910783648490906,0.680610179901123,-0.3215409219264984,0.47547227144241333,0.45531025528907776,0.6895543932914734,-0.3228948414325714,0.4743526875972748,0.4418633282184601,0.699940025806427,-0.32194432616233826,0.47051018476486206,0.4301813840866089,0.7104812860488892,-0.31918519735336304,0.46696099638938904,0.41867008805274963,0.7203617095947266,-0.31396573781967163,0.46602222323417664,0.40660524368286133,0.7287205457687378,-0.3057692348957062,0.47036445140838623,0.3927198350429535,0.7352486252784729,-0.2938821017742157,0.4794284403324127,0.37840619683265686,0.7402702569961548,-0.279187947511673,0.4917464852333069,0.36364737153053284,0.7451010942459106,-0.2610950469970703,0.5068191885948181,0.3461041748523712,0.7494379878044128,-0.2425084114074707,0.522558331489563,0.3262900114059448,0.7532516121864319,-0.21968016028404236,0.542083203792572,0.30082958936691284,0.7573407292366028,-0.1940373182296753,0.5632714033126831,0.2674134373664856,0.7595145106315613,-0.1731635183095932,0.5799481868743896,0.23835299909114838,0.7577608823776245,-0.14524702727794647,0.5998328328132629,0.21190166473388672,0.7469139695167542,-0.10952124744653702,0.626045823097229,0.195425882935524,0.7327810525894165,-0.0869094654917717,0.6497349143028259,0.1825464963912964,0.7027127146720886,-0.08559510856866837,0.6741122603416443,0.21081043779850006,0.6712199449539185,-0.0851781964302063,0.7024242281913757,0.22092705965042114,0.6363365054130554,-0.08716792613267899,0.7329770922660828,0.22410309314727783,0.6177992224693298,-0.07404585182666779,0.7520486116409302,0.21740344166755676,0.6367721557617188,-0.06436153501272202,0.736624002456665,0.21854948997497559,0.6892469525337219,-0.06296263635158539,0.6854718327522278,0.22605930268764496,0.7558253407478333,-0.06407033652067184,0.6084651947021484,0.23322337865829468,0.8071612119674683,-0.06801103055477142,0.5388633608818054,0.23128251731395721,0.8187212944030762,-0.0667574554681778,0.5250003933906555,0.2227407693862915,0.8131390810012817,-0.057676926255226135,0.5367254614830017,0.2177245318889618,0.8090664744377136,-0.04556208476424217,0.547211766242981,0.20951107144355774,0.8040586113929749,-0.03646901994943619,0.5565799474716187,0.20586062967777252,0.7993981838226318,-0.03300352394580841,0.5648527145385742,0.20202656090259552,0.7963692545890808,-0.03215697407722473,0.5709129571914673,0.19702894985675812,0.8018206357955933,-0.021435674279928207,0.5731526017189026,0.1676912009716034,0.8074038028717041,-0.00956697016954422,0.5736434459686279,0.1376257687807083,0.7971813678741455,0.0010261657880619168,0.5819096565246582,0.16087868809700012,0.7734711170196533,-0.04050315544009209,0.5891968011856079,0.23010684549808502,0.7500633597373962,-0.07454647868871689,0.5898155570030212,0.28976789116859436,0.7264847755432129,-0.09946344792842865,0.5872232913970947,0.34277650713920593,0.7059716582298279,-0.12009894847869873,0.5853298902511597,0.38022270798683167,0.6935592293739319,-0.13742482662200928,0.579328179359436,0.4055475890636444,0.6886518597602844,-0.15329690277576447,0.5706266760826111,0.4202902615070343,0.6899547576904297,-0.1684991717338562,0.5608733892440796,0.4254310727119446,0.6937041282653809,-0.17905190587043762,0.5494024157524109,0.42996758222579956,0.6993701457977295,-0.18546441197395325,0.5353018045425415,0.4358169734477997,0.6999914646148682,-0.2011832445859909,0.520261824131012,0.44594281911849976,0.700995147228241,-0.21544088423252106,0.5067506432533264,0.4532051980495453,0.697606086730957,-0.22729796171188354,0.49214038252830505,0.4684862196445465,0.6941450238227844,-0.23226283490657806,0.47558918595314026,0.48788487911224365,0.6969572305679321,-0.2299680858850479,0.4608480632305145,0.4989834129810333,0.7042404413223267,-0.22231975197792053,0.444960355758667,0.5065863132476807,0.7084018588066101,-0.21246761083602905,0.42539992928504944,0.5215930938720703,0.7058565020561218,-0.20702619850635529,0.4035816490650177,0.5440852642059326,0.7011922597885132,-0.20221608877182007,0.38105374574661255,0.567658543586731,0.7016341686248779,-0.1870218813419342,0.3560117185115814,0.5882075428962708,0.7042720913887024,-0.17938819527626038,0.3141823709011078,0.6108275055885315,0.6970039010047913,-0.18588893115520477,0.2572140097618103,0.6430177688598633,0.6889363527297974,-0.18576835095882416,0.19818326830863953,0.6719973087310791,0.6820439100265503,-0.1812431365251541,0.14194397628307343,0.6941317915916443,0.6735635995864868,-0.18121710419654846,0.0951293408870697,0.710227370262146,0.666612982749939,-0.17304910719394684,0.05549516901373863,0.7229118347167969,0.6671510338783264,-0.1523236781358719,0.018628794699907303,0.7289444804191589,0.6743783950805664,-0.13177111744880676,-0.011678550392389297,0.7264391779899597,0.6903746128082275,-0.1190139576792717,-0.03246689215302467,0.7128565311431885,0.7053083181381226,-0.1108039915561676,-0.047372397035360336,0.69858318567276,0.7181994318962097,-0.11105292290449142,-0.05598943680524826,0.6846327781677246,0.7299246788024902,-0.11803197115659714,-0.05969177186489105,0.6706082224845886,0.7467522025108337,-0.1341727077960968,-0.05661863461136818,0.6489631533622742,0.7642244100570679,-0.1506342589855194,-0.05080198124051094,0.6250516176223755,0.7830899357795715,-0.16681818664073944,-0.042146217077970505,0.5976332426071167,0.8003964424133301,-0.18155533075332642,-0.026248332113027573,0.5707138180732727,0.8149315118789673,-0.19013556838035583,-0.0014441445237025619,0.5474787950515747,0.8279067873954773,-0.19807082414627075,0.031085342168807983,0.5238053202629089,0.8430086374282837,-0.20670418441295624,0.07049352675676346,0.4915693700313568,0.861158549785614,-0.21377474069595337,0.11540903151035309,0.4465278089046478,0.8816911578178406,-0.21344146132469177,0.16503944993019104,0.387072890996933,0.9014168977737427,-0.20392262935638428,0.21672135591506958,0.31447580456733704,0.9111107587814331,-0.19531171023845673,0.26161903142929077,0.25156715512275696,0.9127421975135803,-0.18946322798728943,0.29772016406059265,0.20583516359329224,0.9127086400985718,-0.17313392460346222,0.33023351430892944,0.16713328659534454,0.9104411602020264,-0.14372093975543976,0.3613303303718567,0.14100223779678345,0.904035747051239,-0.11225160211324692,0.39038798213005066,0.1331024169921875,0.89385986328125,-0.09060920029878616,0.4192637503147125,0.13047005236148834,0.8814768195152283,-0.07765576243400574,0.4476233720779419,0.12884698808193207,0.8685898184776306,-0.06606335937976837,0.47462767362594604,0.12615849077701569,0.8558136224746704,-0.05520593374967575,0.49933192133903503,0.12330057471990585,0.8427655100822449,-0.0457441620528698,0.5210629105567932,0.1270720660686493,0.82975172996521,-0.040414586663246155,0.53939288854599,0.13760119676589966,0.8186472654342651,-0.03817719966173172,0.553619384765625,0.1478675752878189,0.8108132481575012,-0.03940730541944504,0.5630548000335693,0.1549137532711029,0.8070830702781677,-0.045239001512527466,0.5669772028923035,0.15845270454883575,0.8063535690307617,-0.05788761377334595,0.5655392408370972,0.1631205976009369,0.8064261078834534,-0.08038230985403061,0.5601671934127808,0.17154687643051147,0.8042210340499878,-0.1142684742808342,0.5510299801826477,0.19114719331264496,0.8008021712303162,-0.15393251180648804,0.5391573905944824,0.21054689586162567,0.797406792640686,-0.1877879500389099,0.5257717370986938,0.22900262475013733,0.7925962209701538,-0.21995985507965088,0.5120006799697876,0.24751606583595276,0.7858598232269287,-0.24981385469436646,0.4982302784919739,0.26792529225349426,0.7780194282531738,-0.2743787467479706,0.48573949933052063,0.28889307379722595,0.7699618339538574,-0.2927148640155792,0.47587695717811584,0.3082497715950012,0.760438084602356,-0.3067095875740051,0.46851617097854614,0.3288704454898834,0.7502201199531555,-0.31280118227005005,0.46397432684898376,0.35221174359321594,0.7482112646102905,-0.3123854100704193,0.4595964848995209,0.3624449670314789,0.7524722814559937,-0.30799904465675354,0.45311498641967773,0.3655254542827606,0.7556570172309875,-0.3027269244194031,0.4510044753551483,0.36596983671188354,0.7559951543807983,-0.29515334963798523,0.4584848880767822,0.3621428608894348,0.7527869939804077,-0.28514543175697327,0.47329431772232056,0.35776573419570923,0.7477678656578064,-0.275863915681839,0.49006426334381104,0.3529580235481262,0.7404472231864929,-0.26865553855895996,0.5092450976371765,0.3467443585395813,0.7314218878746033,-0.2611404061317444,0.5309819579124451,0.3389482796192169,0.7306259274482727,-0.2588702440261841,0.5372835993766785,0.33241280913352966,0.7301891446113586,-0.2612944543361664,0.5344414710998535,0.33603766560554504,0.7218827605247498,-0.26502782106399536,0.5335450172424316,0.35210129618644714,0.7119023203849792,-0.2708593010902405,0.5297770500183105,0.37305042147636414,0.7016416192054749,-0.27785438299179077,0.5250612497329712,0.39345496892929077,0.693010151386261,-0.28318172693252563,0.5192427635192871,0.41222813725471497,0.6880621910095215,-0.28581395745277405,0.5139841437339783,0.42508962750434875,0.6862645149230957,-0.28657087683677673,0.5093297362327576,0.4330143928527832,0.6874294281005859,-0.2856059670448303,0.5053393840789795,0.4364655911922455,0.6905441284179688,-0.2834593951702118,0.5014939308166504,0.4373825192451477,0.694342851638794,-0.28162816166877747,0.49721387028694153,0.437438040971756,0.6982241272926331,-0.2803858816623688,0.49250513315200806,0.43738487362861633,0.7017671465873718,-0.2798301577568054,0.4875067174434662,0.4376702606678009,0.7045559287071228,-0.28006699681282043,0.4823700785636902,0.43872836232185364,0.7067611217498779,-0.28062087297439575,0.4776657819747925,0.43997278809547424,0.708420991897583,-0.281124085187912,0.4738194942474365,0.4411395490169525,0.7093071937561035,-0.2814939618110657,0.4710545241832733,0.44243884086608887,0.7092418074607849,-0.2814998924732208,0.4695685803890228,0.4441163241863251],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,4.566666603088379],"values":[1,0.9999998807907104,1,1,1,1.0000001192092896],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,4.566666603088379],"values":[-1.9388730265745835e-7,25.527099609375,-1.977549857201666e-7,-0.00000276505011242989,25.527109146118164,0.0000019160281681251945],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.3567383885383606,-0.09673290699720383,-0.002930391812697053,0.9291780591011047,0.35382357239723206,-0.1126331090927124,-0.01136942021548748,0.9284359812736511,0.36075976490974426,-0.12428764253854752,-0.0170123390853405,0.92418372631073,0.36750146746635437,-0.13871575891971588,-0.024256067350506783,0.9192999005317688,0.3740426301956177,-0.15312619507312775,-0.03194025158882141,0.9141249060630798,0.38078463077545166,-0.16576476395130157,-0.038956183940172195,0.9088495373725891,0.38723722100257874,-0.17541904747486115,-0.0446036234498024,0.9040387272834778,0.3935116231441498,-0.1828429251909256,-0.04909574240446091,0.8996147513389587,0.39950868487358093,-0.1879943609237671,-0.05286987125873566,0.8956872820854187,0.40112289786338806,-0.18765074014663696,-0.05441201850771904,0.8949452638626099,0.4021947383880615,-0.18260160088539124,-0.05358857661485672,0.8955581188201904,0.4011467397212982,-0.17426902055740356,-0.05117044597864151,0.8978269696235657,0.39622217416763306,-0.16363254189491272,-0.04792362079024315,0.9021838903427124,0.38468748331069946,-0.15110507607460022,-0.04452560842037201,0.9095055460929871,0.3703756034374237,-0.13777947425842285,-0.0412963405251503,0.9176782369613647,0.3536081612110138,-0.125375896692276,-0.039137646555900574,0.9261265397071838,0.33347126841545105,-0.11485705524682999,-0.038478679955005646,0.9349460601806641,0.3083062469959259,-0.10703746229410172,-0.039099566638469696,0.9444370865821838,0.2790287137031555,-0.10312096029520035,-0.038554124534130096,0.9539510607719421,0.24640892446041107,-0.10138460993766785,-0.03721021115779877,0.9631298780441284,0.20744626224040985,-0.09945690631866455,-0.037190768867731094,0.9724665880203247,0.16752737760543823,-0.09698145091533661,-0.039664190262556076,0.9802836179733276,0.1230921819806099,-0.09262879937887192,-0.044614411890506744,0.9870551228523254,0.07405229657888412,-0.08217901736497879,-0.0480923093855381,0.9926983714103699,0.04235280677676201,-0.06593011319637299,-0.04997844994068146,0.9956714510917664,0.02126184105873108,-0.03563695028424263,-0.04907624050974846,0.9979326725006104,0.03928991034626961,0.018633488565683365,-0.04144666716456413,0.9981940388679504,0.07097763568162918,0.0407567098736763,-0.03583257645368576,0.9960005283355713,0.17741362750530243,0.015389818698167801,-0.01891426369547844,0.9838342070579529,0.21852533519268036,-0.007347007282078266,-0.02006409503519535,0.9755973219871521,0.25221288204193115,-0.03399886563420296,-0.026058899238705635,0.9667231440544128,0.2562958002090454,-0.061417870223522186,-0.03986990451812744,0.9638209342956543,0.25592896342277527,-0.07212084531784058,-0.0483705997467041,0.9627872705459595,0.25263869762420654,-0.05622229725122452,-0.04662642627954483,0.9647998213768005,0.2446797490119934,-0.012769799679517746,-0.03513409197330475,0.9688830971717834,0.2320927530527115,0.014091064222157001,-0.03518695384263992,0.971954882144928,0.22109004855155945,0.001754110213369131,-0.049111176282167435,0.9740145206451416,0.22667178511619568,0.01561790518462658,-0.05043080449104309,0.9725393652915955,0.22199620306491852,0.04669129103422165,-0.04599549621343613,0.972842276096344,0.2168937474489212,0.06489209830760956,-0.045392461121082306,0.9729777574539185,0.2138395607471466,0.07751500606536865,-0.04592043161392212,0.9727051854133606,0.20552216470241547,0.08132023364305496,-0.04876188933849335,0.9740482568740845,0.13131532073020935,0.047123540192842484,-0.07917241007089615,0.9870498776435852,0.05803268030285835,-0.02171865478157997,-0.10526445508003235,0.9925119280815125,0.09653341770172119,-0.12295536696910858,-0.10099086165428162,0.9825294613838196,0.27418652176856995,-0.059042494744062424,-0.08969072252511978,0.9556627869606018,0.4113140106201172,0.011435012333095074,-0.051882095634937286,0.9099441170692444,0.5042073726654053,0.018848422914743423,-0.03698055446147919,0.8625845909118652,0.5520215630531311,0.011529880575835705,-0.04216308519244194,0.8326833844184875,0.565683126449585,0.005272923037409782,-0.05392254516482353,0.8228409290313721,0.556046187877655,0.00021026529429946095,-0.06920355558395386,0.828265368938446,0.5271709561347961,0.0009920364245772362,-0.08299240469932556,0.845696210861206,0.4800255596637726,0.008640613406896591,-0.08892673999071121,0.872692883014679,0.41606974601745605,0.026413876563310623,-0.08511628955602646,0.9049549698829651,0.36637330055236816,0.04060002788901329,-0.08240190148353577,0.9259222745895386,0.31503233313560486,0.04592921957373619,-0.0835832878947258,0.9442769289016724,0.26559537649154663,0.0008351196884177625,-0.09176672250032425,0.9597069025039673,0.2284448742866516,-0.07103745639324188,-0.09020503610372543,0.9667624831199646,0.20962047576904297,-0.11431841552257538,-0.08740997314453125,0.967134952545166,0.21215850114822388,-0.11449981480836868,-0.08246282488107681,0.9669945240020752,0.24937239289283752,-0.09349949657917023,-0.0674852579832077,0.9615181088447571,0.2866468131542206,-0.08336372673511505,-0.05631760507822037,0.9527394771575928,0.29230812191963196,-0.12239574640989304,-0.06953591108322144,0.9459070563316345,0.270976185798645,-0.15845105051994324,-0.07922448962926865,0.9461441040039062,0.2602631449699402,-0.1622026562690735,-0.079165980219841,0.948517918586731,0.2839089632034302,-0.13863788545131683,-0.07150769978761673,0.9460771083831787,0.30871695280075073,-0.12749822437763214,-0.0680341124534607,0.9401113986968994,0.32659733295440674,-0.11951944231987,-0.06249936297535896,0.9354908466339111,0.35368812084198,-0.1084718406200409,-0.053321029990911484,0.9275211095809937,0.37816253304481506,-0.1019497662782669,-0.04595819115638733,0.9189598560333252,0.3884955048561096,-0.10358139127492905,-0.03933265060186386,0.9147650599479675,0.3908975422382355,-0.11153791099786758,-0.03222193568944931,0.9130827784538269,0.38624662160873413,-0.12713171541690826,-0.02666885033249855,0.9132030606269836,0.38904979825019836,-0.1449848860502243,-0.02113999053835869,0.9094904065132141,0.39752858877182007,-0.16156314313411713,-0.014566756784915924,0.9031368494033813,0.4075220823287964,-0.17722637951374054,-0.008574413135647774,0.8957918882369995,0.4038148522377014,-0.19082947075366974,-0.005578439217060804,0.8946991562843323,0.40135592222213745,-0.18906408548355103,0.0023274461273103952,0.8961935043334961,0.3939107060432434,-0.17489801347255707,0.011039861477911472,0.9022877216339111,0.3838033676147461,-0.16215664148330688,0.014920946210622787,0.9089431166648865,0.3736519515514374,-0.15835040807724,0.013295207172632217,0.9138559103012085,0.36496415734291077,-0.16131922602653503,0.008022913709282875,0.9169039726257324,0.3581696152687073,-0.161213681101799,0.004370548762381077,0.919622540473938,0.34864315390586853,-0.15137651562690735,0.004974874667823315,0.9249369502067566,0.32060670852661133,-0.1366511434316635,0.0032116698566824198,0.9372979402542114,0.2673236131668091,-0.1206228956580162,-0.004633898846805096,0.9560161232948303,0.2226051688194275,-0.11913938075304031,-0.015185493044555187,0.9674823880195618,0.1901460587978363,-0.11422975361347198,-0.022992435842752457,0.974816620349884,0.1640634387731552,-0.09486735612154007,-0.026995673775672913,0.98150634765625,0.1429484635591507,-0.057655781507492065,-0.02792292833328247,0.9876547455787659,0.14496023952960968,-0.021063009276986122,-0.0266091488301754,0.9888553619384766,0.14712826907634735,-0.0077429404482245445,-0.0313967801630497,0.9885886907577515,0.14293190836906433,-0.010285167954862118,-0.04075835272669792,0.9888394474983215,0.1368648260831833,-0.011585609056055546,-0.049273744225502014,0.9892956614494324,0.13384243845939636,-0.008944837376475334,-0.05467277020215988,0.9894528985023499,0.14002926647663116,-0.008217869326472282,-0.05870193615555763,0.9883715510368347,0.15290409326553345,-0.006194110028445721,-0.06101562827825546,0.9863362312316895,0.1635362207889557,0.0015295185148715973,-0.06131836771965027,0.9846286773681641,0.16930699348449707,0.007473996374756098,-0.06174950674176216,0.9835986495018005,0.17299054563045502,0.00865661446005106,-0.062428493052721024,0.982904851436615,0.17869319021701813,0.003951869439333677,-0.06275624781847,0.9818934798240662,0.19469770789146423,-0.006528810597956181,-0.059484101831912994,0.9790361523628235,0.23138533532619476,-0.016843898221850395,-0.0594438798725605,0.9708983302116394,0.2633671760559082,-0.023876570165157318,-0.062418755143880844,0.9623780846595764,0.28537821769714355,-0.021164586767554283,-0.05785961076617241,0.9564327597618103,0.2986561059951782,-0.016729319468140602,-0.04889464005827904,0.9529606699943542,0.3067416846752167,-0.011380727402865887,-0.036747708916664124,0.951015055179596,0.3074107766151428,-0.00626463582739234,-0.021142415702342987,0.9513213634490967,0.2996385991573334,-0.003124979790300131,-0.006300308275967836,0.9540268778800964,0.2948206663131714,-0.0011732111452147365,0.001167746027931571,0.9555512070655823,0.2986961007118225,-0.0006311123142950237,0.006032190751284361,0.9543290138244629,0.2931147813796997,0.003951838240027428,0.011496301740407944,0.956000030040741,0.28419944643974304,0.007650123443454504,0.016190804541110992,0.9585979580879211,0.2734736502170563,0.0068495264276862144,0.018996452912688255,0.9616674780845642,0.2543008029460907,-0.0014347103424370289,0.01611381024122238,0.9669898748397827,0.23260946571826935,-0.01659000851213932,0.007674161344766617,0.9723984599113464,0.21150749921798706,-0.03447548300027847,-0.004133791197091341,0.9767594337463379,0.18900667130947113,-0.05494391918182373,-0.02052745595574379,0.9802225828170776,0.1612078696489334,-0.07865284383296967,-0.03553113713860512,0.9831395149230957,0.14159844815731049,-0.08682574331760406,-0.03945555165410042,0.9853194355964661,0.14603020250797272,-0.09176018089056015,-0.03337904065847397,0.9844496846199036,0.1740097552537918,-0.10770132392644882,-0.023255271837115288,0.9785603284835815,0.2130267173051834,-0.12395196408033371,-0.013907493092119694,0.9690521359443665,0.2537163197994232,-0.13683398067951202,-0.010231111198663712,0.957496702671051,0.28991201519966125,-0.1449042409658432,-0.007828762754797935,0.9459875822067261,0.31284499168395996,-0.14803043007850647,-0.006876298226416111,0.9381725788116455,0.324916273355484,-0.14729323983192444,-0.007301495876163244,0.9341738820075989,0.32899266481399536,-0.14388053119182587,-0.008557520806789398,0.9332679510116577,0.32882261276245117,-0.13889549672603607,-0.009250075556337833,0.9340761303901672,0.32836025953292847,-0.13290947675704956,-0.009832954034209251,0.935103178024292,0.32901209592819214,-0.12659341096878052,-0.01012119185179472,0.9357471466064453,0.3314115107059479,-0.12049375474452972,-0.010045109316706657,0.9357065558433533,0.33619287610054016,-0.1150960698723793,-0.00952152069658041,0.9346852898597717,0.3413317799568176,-0.11055989563465118,-0.008919344283640385,0.933375358581543,0.3455675542354584,-0.10704250633716583,-0.008436408825218678,0.9322305917739868,0.34888169169425964,-0.10495221614837646,-0.00811457447707653,0.9312361478805542,0.3512074947357178,-0.09866474568843842,-0.0049941400066018105,0.9310712218284607],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998807907104,0.9999998211860657,0.9999998807907104,1,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,4.566666603088379],"values":[-0.000004553026428766316,23.10919761657715,-0.0000010369468554927153,-0.000002052032868959941,23.109195709228516,-0.0000021325608940969687],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[-0.5544148683547974,0.15885058045387268,-0.20567473769187927,0.7906254529953003,-0.5594161748886108,0.1622900664806366,-0.1992979645729065,0.7880328893661499,-0.5618118047714233,0.1586054563522339,-0.1933312714099884,0.7885650992393494,-0.5638331174850464,0.15403079986572266,-0.18585936725139618,0.7898246645927429,-0.5658848881721497,0.14943447709083557,-0.1783820241689682,0.7909636497497559,-0.5683822631835938,0.1453530639410019,-0.17177505791187286,0.791395902633667,-0.5708690881729126,0.14199450612068176,-0.1662207841873169,0.7914018034934998,-0.5735162496566772,0.13914881646633148,-0.16146019101142883,0.790978729724884,-0.5763280391693115,0.13674284517765045,-0.15722423791885376,0.79020756483078,-0.5776524543762207,0.13533608615398407,-0.15441489219665527,0.7900366187095642,-0.5797309279441833,0.13504666090011597,-0.15352970361709595,0.7887351512908936,-0.5816490650177002,0.13593614101409912,-0.15460573136806488,0.7869579792022705,-0.5815412998199463,0.13789422810077667,-0.15716585516929626,0.7861894369125366,-0.577020525932312,0.14015869796276093,-0.15998603403568268,0.7885476350784302,-0.570275604724884,0.14158767461776733,-0.16146379709243774,0.792885959148407,-0.5624755620956421,0.1423218697309494,-0.16171643137931824,0.7982566356658936,-0.5534599423408508,0.14208944141864777,-0.1604876071214676,0.804820716381073,-0.5428355932235718,0.14080263674259186,-0.15766726434230804,0.8128008246421814,-0.5304537415504456,0.1394696682691574,-0.15496046841144562,0.8216777443885803,-0.5162915587425232,0.13867662847042084,-0.1534309983253479,0.8310660719871521,-0.4981830418109894,0.13786853849887848,-0.15192951261997223,0.8424508571624756,-0.480500191450119,0.13698087632656097,-0.15052971243858337,0.8530513644218445,-0.46217072010040283,0.13626007735729218,-0.15005318820476532,0.863316535949707,-0.4416775405406952,0.13728384673595428,-0.15285681188106537,0.8733320832252502,-0.4277513921260834,0.14012625813484192,-0.15826064348220825,0.8788327574729919,-0.4163588881492615,0.1413017064332962,-0.16049271821975708,0.883697509765625,-0.42242175340652466,0.1249108538031578,-0.13770164549350739,0.8871276378631592,-0.4362609088420868,0.10445180535316467,-0.11465337127447128,0.8863525390625,-0.5021703839302063,0.06067265197634697,-0.03516414016485214,0.8619207143783569,-0.49403589963912964,0.03635115921497345,0.007171029690653086,0.8686516284942627,-0.47803977131843567,0.01855630986392498,0.032526057213544846,0.8775395750999451,-0.4766281843185425,-0.020141562446951866,0.09078783541917801,0.8741724491119385,-0.4675597846508026,-0.048083916306495667,0.13120563328266144,0.8728464245796204,-0.4568401575088501,-0.054307110607624054,0.13553999364376068,0.8774831891059875,-0.4477328062057495,-0.04130582511425018,0.10756557434797287,0.886712372303009,-0.4334191679954529,-0.02136087976396084,0.06756485253572464,0.898402214050293,-0.42790135741233826,-0.012764516286551952,0.04729146137833595,0.9024971127510071,-0.43904849886894226,-0.010037270374596119,0.0386037603020668,0.8975775837898254,-0.43931758403778076,0.0009460784494876862,0.019588688388466835,0.89811772108078,-0.43497708439826965,0.0131871048361063,0.005388995632529259,0.9003288745880127,-0.41798120737075806,0.027591438964009285,-0.003585095051676035,0.9080294966697693,-0.3675544559955597,0.036798108369112015,0.0023948820307850838,0.9292706251144409,-0.2420388162136078,0.035945091396570206,0.0245407372713089,0.9692899584770203,-0.09170939773321152,0.02867465279996395,0.03911972418427467,0.9946038126945496,-0.0370059609413147,0.010577280074357986,0.060286566615104675,0.9974388480186462,-0.08362096548080444,-0.004706903826445341,0.08902259171009064,0.9925020933151245,-0.1572791337966919,-0.02749144472181797,0.12934209406375885,0.9786613583564758,-0.234464630484581,-0.041139088571071625,0.15204738080501556,0.9592786431312561,-0.28558239340782166,-0.04035281762480736,0.14971189200878143,0.9457275867462158,-0.3166700005531311,-0.02881397306919098,0.13023561239242554,0.9391105771064758,-0.3547137379646301,-0.0018943512113764882,0.08485444635152817,0.9311145544052124,-0.38794246315956116,0.028646791353821754,0.03293118625879288,0.920649528503418,-0.41202306747436523,0.048439525067806244,-0.0004824821080546826,0.9098848104476929,-0.4211569130420685,0.058234166353940964,-0.016212064772844315,0.904971182346344,-0.4262833893299103,0.0648648589849472,-0.026309246197342873,0.9018774032592773,-0.424177348613739,0.06984159350395203,-0.03327058255672455,0.9022687077522278,-0.3852742910385132,0.050867464393377304,-0.0023088729940354824,0.9213961958885193,-0.33490830659866333,0.030228037387132645,0.02913244441151619,0.9413150548934937,-0.30231863260269165,0.01805783621966839,0.04834572970867157,0.9518088102340698,-0.2858907878398895,0.009807205758988857,0.06749133765697479,0.9558322429656982,-0.3153086006641388,0.009676587767899036,0.08241462707519531,0.9453542828559875,-0.3523511588573456,0.009750420227646828,0.08867760002613068,0.9316060543060303,-0.3694050908088684,0.01428394578397274,0.0763167068362236,0.9260192513465881,-0.3690972626209259,0.022816205397248268,0.05535125732421875,0.9274604916572571,-0.3744916617870331,0.027513757348060608,0.04379894584417343,0.9257864952087402,-0.3815751075744629,0.033960964530706406,0.02933668904006481,0.9232478141784668,-0.3892074227333069,0.03532501682639122,0.02514636144042015,0.9201290011405945,-0.39610251784324646,0.03806978464126587,0.018745476379990578,0.9172252416610718,-0.4116983115673065,0.04281065985560417,0.009459709748625755,0.9102649688720703,-0.42847952246665955,0.04605216532945633,0.0029430354479700327,0.9023723602294922,-0.44259458780288696,0.04820063337683678,-0.0023081700783222914,0.8954225182533264,-0.4578096866607666,0.05023607239127159,-0.007331145461648703,0.887599527835846,-0.4729306399822235,0.05116764456033707,-0.01002407819032669,0.8795555830001831,-0.48905402421951294,0.05209515988826752,-0.01248841267079115,0.8706068396568298,-0.5074355006217957,0.054151419550180435,-0.016959184780716896,0.8598193526268005,-0.5251283645629883,0.0566616915166378,-0.022089960053563118,0.8488473296165466,-0.5400160551071167,0.058721430599689484,-0.025642884895205498,0.8392121195793152,-0.5585383772850037,0.057544827461242676,-0.02291017398238182,0.8271629810333252,-0.5743418335914612,0.054819803684949875,-0.01752145029604435,0.8165900111198425,-0.5858684778213501,0.0519840344786644,-0.013129601255059242,0.8086305856704712,-0.59698086977005,0.04724915698170662,-0.005879115778952837,0.8008413314819336,-0.61078280210495,0.042906999588012695,0.0000932353432290256,0.79063481092453,-0.630271852016449,0.043041180819272995,-0.00041155717917717993,0.775180459022522,-0.6531417369842529,0.045678503811359406,-0.004647612571716309,0.7558424472808838,-0.668132483959198,0.04789283871650696,-0.008035014383494854,0.7424558997154236,-0.6701783537864685,0.04977543652057648,-0.011261314153671265,0.7404435276985168,-0.6725324392318726,0.05461821332573891,-0.021486014127731323,0.7377366423606873,-0.6774637699127197,0.06190600246191025,-0.03500301018357277,0.7321102023124695,-0.682876467704773,0.05928388610482216,-0.02970239147543907,0.7275183200836182,-0.6806302666664124,0.05058339610695839,-0.01335844025015831,0.7307567000389099,-0.6768175363540649,0.0428895503282547,0.0018011892680078745,0.7348981499671936,-0.6682496666908264,0.03620108962059021,0.015038889832794666,0.7429035902023315,-0.6524023413658142,0.03680282086133957,0.015455720946192741,0.7568209171295166,-0.6385368704795837,0.04169607162475586,0.00837810430675745,0.7684151530265808,-0.6295764446258545,0.05069546401500702,-0.005005871411412954,0.775266706943512,-0.6238305568695068,0.05302977189421654,-0.007981862872838974,0.7797176837921143,-0.6205982565879822,0.05077620968222618,-0.0035895390901714563,0.7824747562408447,-0.6185325980186462,0.049703121185302734,-0.0013566201087087393,0.784184455871582,-0.6187900304794312,0.04870351403951645,0.0005072066560387611,0.7840450406074524,-0.6188516616821289,0.04873832315206528,0.0005655474378727376,0.783994197845459,-0.6175787448883057,0.04883323982357979,0.0005853396723978221,0.7849913835525513,-0.6162222027778625,0.051728591322898865,-0.0036561342421919107,0.7858632206916809,-0.6149678230285645,0.05213508754968643,-0.0034537063911557198,0.786819338798523,-0.6038852334022522,0.05141787230968475,-0.0007209888426586986,0.7954108119010925,-0.5835362076759338,0.05317831411957741,-0.002012006239965558,0.8103416562080383,-0.5555483102798462,0.05516531318426132,-0.003727605566382408,0.8296439051628113,-0.5234475135803223,0.05636243149638176,-0.00451274449005723,0.8501797914505005,-0.48906755447387695,0.056785907596349716,-0.004659839905798435,0.8703830242156982,-0.45402759313583374,0.05634164810180664,-0.0028096341993659735,0.8892000317573547,-0.42710787057876587,0.05369395390152931,0.003821283346042037,0.9025969505310059,-0.4151870012283325,0.05018846318125725,0.011631612665951252,0.9082761406898499,-0.41294875741004944,0.05173405259847641,0.00932453852146864,0.909235954284668,-0.42117440700531006,0.05898916721343994,-0.0031320282723754644,0.9050539135932922,-0.43218994140625,0.0692107304930687,-0.020772481337189674,0.8988828063011169,-0.44050246477127075,0.07991013675928116,-0.038950636982917786,0.8933391571044922,-0.4476906955242157,0.08948536217212677,-0.0554833859205246,0.8879679441452026,-0.4534582793712616,0.09923483431339264,-0.0727984756231308,0.8827391862869263,-0.45575177669525146,0.10850602388381958,-0.09014365077018738,0.8788577318191528,-0.4520333707332611,0.11895230412483215,-0.10981419682502747,0.8771870136260986,-0.44950178265571594,0.13314345479011536,-0.13675758242607117,0.8726502060890198,-0.45613959431648254,0.14870119094848633,-0.16634495556354523,0.861483633518219,-0.4717097580432892,0.1621723473072052,-0.19226272404193878,0.8451184034347534,-0.49079862236976624,0.17179957032203674,-0.2110973745584488,0.8276711702346802,-0.5110393166542053,0.17581769824028015,-0.21944473683834076,0.8122628927230835,-0.527005672454834,0.17521359026432037,-0.2193937450647354,0.8021419048309326,-0.5365012288093567,0.17360952496528625,-0.2172871232032776,0.7967512607574463,-0.5409854650497437,0.17174021899700165,-0.21439561247825623,0.794905424118042,-0.5427093505859375,0.1697697937488556,-0.21107718348503113,0.7950416803359985,-0.5431313514709473,0.16812178492546082,-0.20817533135414124,0.7958683967590332,-0.5436893701553345,0.16672861576080322,-0.20581264793872833,0.7963947653770447,-0.5449239611625671,0.16568243503570557,-0.20411930978298187,0.7962051033973694,-0.547021746635437,0.16502691805362701,-0.20311714708805084,0.795158326625824,-0.5501666069030762,0.1647467464208603,-0.20287950336933136,0.7931047677993774,-0.5533661842346191,0.16469953954219818,-0.20300894975662231,0.7908523082733154,-0.5559727549552917,0.1646965593099594,-0.20319639146327972,0.788974404335022,-0.5579084157943726,0.16469046473503113,-0.20328234136104584,0.7875858545303345,-0.5576359033584595,0.15968339145183563,-0.20726493000984192,0.787771999835968],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,4.566666603088379],"values":[1,1.0000001192092896,1.0000001192092896,0.9999998807907104,1,0.9999998807907104],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,4.566666603088379],"values":[0.0000018676674926609849,13.547399520874023,-0.0000017681613826425746,-0.000004401627393235685,13.547396659851074,2.3412897576235991e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[-0.30788561701774597,0.014501920901238918,0.004691835027188063,0.9513012766838074,-0.30788561701774597,0.01450191717594862,0.00469182850793004,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998807907104,1,1.0000001192092896,1,0.9999998807907104,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,4.566666603088379],"values":[2.833998918533325,7.754303455352783,-6.1396002769470215,2.834010362625122,7.7543044090271,-6.139601707458496],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[-0.33200064301490784,-0.09571805596351624,-0.11373619735240936,0.9314922094345093,-0.3320006728172302,-0.09571804106235504,-0.11373621225357056,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998807907104,0.9999999403953552,0.9999998211860657,1,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,4.566666603088379],"values":[-6.044280098649324e-7,10.288596153259277,8.044303285714705e-7,-0.000007160939276218414,10.288607597351074,6.256158826545288e-7],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[-1.1888912965218879e-8,-4.909816198050976e-8,1.3140379451215267e-8,1,-1.3751559002628255e-8,-1.3533280274202752e-8,1.2674718163907528e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,4.566666603088379],"values":[1,1,0.9999999403953552,1,1,1],"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,4.566666603088379],"values":[5.372934097636062e-9,10.28860855102539,5.039065058554115e-7,0.000010092572665598709,10.288619995117188,0.000003821388872893294],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.04367886111140251,0.029373330995440483,-0.01944381557404995,0.9984244108200073,0.04341297224164009,0.028782863169908524,-0.019375041127204895,0.9984545111656189,0.04262734577059746,0.027011901140213013,-0.018704615533351898,0.9985506534576416,0.04137972369790077,0.024229731410741806,-0.01767599582672119,0.9986932873725891,0.039720695465803146,0.02057851292192936,-0.016344867646694183,0.9988651871681213,0.03770057111978531,0.01621830277144909,-0.014756211079657078,0.9990484714508057,0.035367418080568314,0.011353052221238613,-0.012986887246370316,0.9992254972457886,0.032768964767456055,0.006153289694339037,-0.011105554178357124,0.9993823170661926,0.029948702082037926,0.0007976909982971847,-0.009155578911304474,0.9995091557502747,0.02694941684603691,-0.004540456924587488,-0.007283944170922041,0.9995999932289124,0.023810183629393578,-0.009754051454365253,-0.0056037516333162785,0.999653160572052,0.020565101876854897,-0.014691772870719433,-0.00415816530585289,0.9996719360351562,0.017246341332793236,-0.01909070648252964,-0.002969916444271803,0.9996646046638489,0.013883770443499088,-0.020740913227200508,-0.0019177153008058667,0.9996866583824158,0.01051254104822874,-0.02183810994029045,-0.0009671381558291614,0.9997058510780334,0.007168478332459927,-0.02237893082201481,-0.00045185661292634904,0.9997237324714661,0.0038891579024493694,-0.02252739481627941,-0.00041408545803278685,0.9997385740280151,0.0007133639301173389,-0.022749660536646843,-0.001074545201845467,0.9997403621673584,-0.002318765502423048,-0.02283434383571148,-0.0020624888129532337,0.9997344613075256,-0.00516577810049057,-0.022571345791220665,-0.0029478014912456274,0.9997275471687317,-0.007786814589053392,-0.021352043375372887,-0.003596132155507803,0.9997352361679077,-0.009410509839653969,-0.019838383421301842,-0.0040960246697068214,0.999750554561615,-0.011505460366606712,-0.018385102972388268,-0.0044752308167517185,0.9997547268867493,-0.013652325607836246,-0.01632639579474926,-0.004990107845515013,0.9997611045837402,-0.015994494780898094,-0.012796101160347462,-0.00644097151234746,0.9997695088386536,-0.018905669450759888,-0.00822566170245409,-0.009949452243745327,0.9997379183769226,-0.02182173915207386,-0.002392303664237261,-0.011851356364786625,0.9996888041496277,-0.023355793207883835,0.006275085732340813,-0.013839979656040668,0.9996117353439331,-0.02342873439192772,0.01737356185913086,-0.013224879279732704,0.999487042427063,-0.023655233904719353,0.02893567830324173,-0.015059836208820343,0.9991878867149353,-0.021985888481140137,0.034541040658950806,-0.017524881288409233,0.9990077018737793,-0.018907522782683372,0.04012465849518776,-0.02007637545466423,0.998814046382904,-0.015773290768265724,0.04317502677440643,-0.021943194791674614,0.9987019896507263,-0.01248252671211958,0.04536593705415726,-0.02222348377108574,0.9986452460289001,-0.009151865728199482,0.05362476408481598,-0.02122688852250576,0.9982935786247253,-0.00529019720852375,0.060811854898929596,-0.019065748900175095,0.9979531168937683,-0.001538699259981513,0.06344494968652725,-0.016684778034687042,0.9978446960449219,0.000026761435947264545,0.06603600829839706,-0.014123370870947838,0.9977173209190369,-0.0004123724647797644,0.06773106753826141,-0.013855953700840473,0.9976072907447815,-0.0008813245221972466,0.06961052864789963,-0.016598012298345566,0.9974358081817627,-0.001340006827376783,0.07059647887945175,-0.022873859852552414,0.997241735458374,-0.0013146242126822472,0.0702938660979271,-0.032513368874788284,0.9969954490661621,-0.0010896804742515087,0.06712295860052109,-0.03956678882241249,0.9969592690467834,-0.0011032824404537678,0.06113211438059807,-0.04377828538417816,0.9971685409545898,-0.0017112914938479662,0.05223693326115608,-0.037178702652454376,0.9979409575462341,-0.0021293519530445337,0.045452021062374115,-0.03370387852191925,0.9983955025672913,-0.0026184222660958767,0.03928675502538681,-0.028305428102612495,0.9988235831260681,-0.003092869883403182,0.033840399235486984,-0.021714724600315094,0.9991865158081055,-0.003532383358106017,0.028640367090702057,-0.01672985590994358,0.9994434714317322,-0.004238648805767298,0.022566620260477066,-0.012909285724163055,0.9996529817581177,-0.005216158926486969,0.01835460029542446,-0.010312971659004688,0.9997648000717163,-0.0061988262459635735,0.015216683968901634,-0.008688761852681637,0.9998273253440857,-0.007503044325858355,0.013771120458841324,-0.006734530441462994,0.999854326248169,-0.009642443619668484,0.013007006607949734,-0.005479572340846062,0.9998539090156555,-0.011257519014179707,0.012319508008658886,-0.004316166043281555,0.9998514652252197,-0.012450975365936756,0.011873762123286724,-0.00356812565587461,0.9998456239700317,-0.012775439769029617,0.011330196633934975,-0.0035319568123668432,0.999847948551178,-0.011520914733409882,0.009421630762517452,-0.0037193766329437494,0.9998823404312134,-0.009097286500036716,0.007102493662387133,-0.0037493896670639515,0.9999263882637024,-0.005651798564940691,0.004374222364276648,-0.003681872272863984,0.9999676942825317,-0.004307296592742205,-0.00019292203069198877,-0.002838502638041973,0.9999866485595703,-0.004857865162193775,-0.006550888530910015,-0.0016978781204670668,0.9999653100967407,-0.005850361660122871,-0.013504804112017155,0.002836924046278,0.9998877048492432,-0.006331014912575483,-0.01704534702003002,0.010289606638252735,0.9997817277908325,-0.005767982453107834,-0.01993316225707531,0.017479397356510162,0.9996318817138672,-0.0046098134480416775,-0.02251163311302662,0.01813078671693802,0.9995715618133545,-0.0020751000847667456,-0.026189642027020454,0.0188105721026659,0.9994778633117676,-0.0012563789496198297,-0.031578850001096725,0.019839361310005188,0.9993035793304443,-0.0014773714356124401,-0.037834055721759796,0.021215323358774185,0.9990577101707458,-0.00220368430018425,-0.04518584534525871,0.02432815358042717,0.9986799359321594,-0.0031552237924188375,-0.0513678640127182,0.029048379510641098,0.998252272605896,-0.004164467100054026,-0.05437808483839035,0.03204210102558136,0.997997522354126,-0.005092276725918055,-0.053131744265556335,0.03017488867044449,0.99811851978302,-0.005325869657099247,-0.05209553986787796,0.029140716418623924,0.9982026219367981,-0.005204909481108189,-0.051156654953956604,0.02873498760163784,0.9982635974884033,-0.0045935166999697685,-0.04933681711554527,0.0275970920920372,0.9983903169631958,-0.00461367703974247,-0.04649604856967926,0.02370322123169899,0.9986265301704407,-0.00453190365806222,-0.043674442917108536,0.019469719380140305,0.9988458156585693,-0.004792540334165096,-0.04121333360671997,0.01518583670258522,0.9990234971046448,-0.00576462596654892,-0.0379786416888237,0.011066948063671589,0.9992006421089172,-0.00704175466671586,-0.0346161387860775,0.007970266044139862,0.9993440508842468,-0.009064444340765476,-0.031908124685287476,0.006175777409225702,0.9994305968284607,-0.011415958404541016,-0.028854135423898697,0.005393030121922493,0.9995039105415344,-0.013524268753826618,-0.023927584290504456,0.004168778192251921,0.9996135234832764,-0.015173988416790962,-0.016720661893486977,0.002996206982061267,0.9997406005859375,-0.015502859838306904,-0.007261960301548243,0.0008548717014491558,0.9998530745506287,-0.014560632407665253,0.005099596921354532,0.00003679217115859501,0.9998810291290283,-0.012220347300171852,0.01734025776386261,0.0010973288444802165,0.9997743368148804,-0.009200411848723888,0.028307966887950897,0.001718053943477571,0.9995554089546204,-0.006167043931782246,0.03277601674199104,0.0008309216937050223,0.9994433522224426,-0.0020627870690077543,0.03729374706745148,-0.00037864703335799277,0.9993021488189697,0.0015836125239729881,0.04155900329351425,-0.002116649877279997,0.9991325736045837,0.005482208915054798,0.04516002908349037,-0.004148940090090036,0.9989561438560486,0.009228832088410854,0.04804425686597824,-0.0068224575370550156,0.998779296875,0.012424962595105171,0.0502132922410965,-0.01072248537093401,0.9986037015914917,0.014449232257902622,0.050815828144550323,-0.012657055631279945,0.9985233545303345,0.01489467266947031,0.0507982112467289,-0.013319700956344604,0.9985090494155884,0.015146545134484768,0.0505271852016449,-0.014501168392598629,0.9985025525093079,0.015121783129870892,0.04991699382662773,-0.01557929441332817,0.9985173940658569,0.014917800202965736,0.04887163266539574,-0.015505867078900337,0.9985733032226562,0.014590349048376083,0.04726829007267952,-0.014184480533003807,0.9986749291419983,0.014272776432335377,0.04560372605919838,-0.013017207384109497,0.9987727999687195,0.014029163867235184,0.043770939111709595,-0.01208573579788208,0.9988699555397034,0.01394760049879551,0.04196568578481674,-0.011811693198978901,0.9989519119262695,0.013969596475362778,0.04030103608965874,-0.011925823986530304,0.9990187883377075,0.013753469102084637,0.03864430636167526,-0.012282904237508774,0.9990828633308411,0.013817437924444675,0.037055812776088715,-0.012692008167505264,0.9991371035575867,0.01396895106881857,0.03559398651123047,-0.013047217391431332,0.9991834759712219,0.01415255106985569,0.0342881940305233,-0.01231356244534254,0.9992359280586243,0.014387748204171658,0.03310053050518036,-0.010787594132125378,0.9992902278900146,0.014684495516121387,0.03198782727122307,-0.008587468415498734,0.9993435144424438,0.01508435420691967,0.030891764909029007,-0.0065421536564826965,0.9993875026702881,0.015630587935447693,0.029732659459114075,-0.005607254337519407,0.9994199872016907,0.01630515791475773,0.028565580025315285,-0.005535975098609924,0.9994435906410217,0.017079047858715057,0.027470925822854042,-0.005790004041045904,0.9994599223136902,0.01793545112013817,0.026482265442609787,-0.006046888418495655,0.9994701147079468,0.018874596804380417,0.02555653266608715,-0.0063365427777171135,0.9994751214981079,0.019904514774680138,0.024674123153090477,-0.006872288417071104,0.9994738101959229,0.02106388844549656,0.023852746933698654,-0.007706390228122473,0.9994638562202454,0.022369248792529106,0.023052461445331573,-0.0086721982806921,0.9994463920593262,0.02379174716770649,0.02228604257106781,-0.009614745154976845,0.9994222521781921,0.025308700278401375,0.021609948948025703,-0.010538394562900066,0.9993905425071716,0.02689730003476143,0.021074827760457993,-0.011451068334281445,0.9993504285812378,0.028533723205327988,0.020723512396216393,-0.012334045954048634,0.9993019104003906,0.03019322082400322,0.020581485703587532,-0.013135301880538464,0.999245822429657,0.031857192516326904,0.020745158195495605,-0.013951212167739868,0.9991797208786011,0.033503223210573196,0.02123454585671425,-0.014772269874811172,0.999103844165802,0.03510677441954613,0.02199552021920681,-0.01556189451366663,0.9990202784538269,0.03664407506585121,0.022947775200009346,-0.016302572563290596,0.9989318251609802,0.038092076778411865,0.024007774889469147,-0.01699097640812397,0.9988413453102112,0.03942679986357689,0.0251097921282053,-0.01761724054813385,0.9987515211105347,0.04062420874834061,0.02619103156030178,-0.018173182383179665,0.9986658692359924,0.04165980964899063,0.027190018445253372,-0.018650362268090248,0.9985876679420471,0.04250859469175339,0.028059212490916252,-0.01904110237956047,0.9985204935073853,0.043145615607500076,0.028750330209732056,-0.01933162473142147,0.9984678626060486,0.04354577511548996,0.02920658513903618,-0.019508643075823784,0.9984338879585266,0.04368295520544052,0.029365791007876396,-0.01952981948852539,0.9984228014945984],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,4.566666603088379],"values":[1.0000001192092896,1,0.9999998211860657,1.0000001192092896,0.9999998211860657,1],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,4.566666603088379],"values":[4.366398811340332,7.562898635864258,-0.17680057883262634,4.366397380828857,7.562900543212891,-0.1767866015434265],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.5297933220863342,0.513465404510498,-0.444655179977417,0.507891833782196,0.5307596921920776,0.5131548047065735,-0.44424429535865784,0.5075562596321106,0.5334756374359131,0.5123382210731506,-0.44303593039512634,0.5065889954566956,0.5376607179641724,0.5110822319984436,-0.44115862250328064,0.5050691962242126,0.5429902076721191,0.509513258934021,-0.43866750597953796,0.5031189322471619,0.5491943955421448,0.5076991319656372,-0.4356914460659027,0.5007994174957275,0.5559855699539185,0.5057331919670105,-0.4323347806930542,0.49819740653038025,0.5630824565887451,0.5037139654159546,-0.42870399355888367,0.49540212750434875,0.5699883699417114,0.5019699931144714,-0.4246339201927185,0.4927731454372406,0.5762569308280945,0.5007767081260681,-0.4200385808944702,0.49063029885292053,0.5816943645477295,0.5001636743545532,-0.41514426469802856,0.4890022873878479,0.5861168503761292,0.5001699328422546,-0.4101760685443878,0.48790642619132996,0.5893487334251404,0.5008451342582703,-0.40535470843315125,0.4873497486114502,0.5912582874298096,0.5022023320198059,-0.40084367990493774,0.4873713552951813,0.5914543867111206,0.5045966506004333,-0.3971345126628876,0.4876968264579773,0.5896553993225098,0.508280873298645,-0.39453965425491333,0.4881553053855896,0.5862547755241394,0.5125817656517029,-0.39314574003219604,0.4888781011104584,0.5821317434310913,0.5160887241363525,-0.3926243782043457,0.4905316233634949,0.5769476294517517,0.5188927054405212,-0.39369624853134155,0.4928337335586548,0.5703635811805725,0.5212000608444214,-0.3969082832336426,0.49547937512397766,0.5608994960784912,0.5250940322875977,-0.4026211202144623,0.4975583851337433,0.5494411587715149,0.5292034149169922,-0.40953513979911804,0.5003389120101929,0.5373977422714233,0.5318418145179749,-0.41796377301216125,0.5036409497261047,0.5254172086715698,0.5343208909034729,-0.4291771352291107,0.5042271614074707,0.5128564834594727,0.5370794534683228,-0.4453693628311157,0.500170111656189,0.49981948733329773,0.539271891117096,-0.46538129448890686,0.49273377656936646,0.48538148403167725,0.5426366925239563,-0.4894883930683136,0.47994938492774963,0.4692435562610626,0.5477549433708191,-0.5190999507904053,0.45859599113464355,0.45188698172569275,0.5554500222206116,-0.5514095425605774,0.42804333567619324,0.44043534994125366,0.5645340085029602,-0.5758544206619263,0.39460092782974243,0.43288907408714294,0.572709858417511,-0.593981146812439,0.3630385100841522,0.4305141568183899,0.5788766741752625,-0.6034751534461975,0.3396722674369812,0.4328635334968567,0.5850821733474731,-0.604945182800293,0.3230316638946533,0.43277716636657715,0.5948202610015869,-0.6023166179656982,0.31001219153404236,0.42029979825019836,0.6168057918548584,-0.6012067198753357,0.2853933572769165,0.4038407504558563,0.6413381099700928,-0.5981186628341675,0.2604847252368927,0.3971341550350189,0.6593033671379089,-0.5878772735595703,0.24900583922863007,0.39331668615341187,0.6699571013450623,-0.57965487241745,0.2458856999874115,0.38960015773773193,0.6753815412521362,-0.5762187242507935,0.24503785371780396,0.3844580054283142,0.6768355369567871,-0.579992949962616,0.24019566178321838,0.3858133852481842,0.670695424079895,-0.5883083939552307,0.23496615886688232,0.3974194824695587,0.6604201793670654,-0.5945999622344971,0.2288098931312561,0.4163835942745209,0.649499237537384,-0.5924615859985352,0.23187243938446045,0.4410020112991333,0.6366241574287415,-0.5830672979354858,0.2454782873392105,0.4614906907081604,0.6276290416717529,-0.5684967041015625,0.26442354917526245,0.47830936312675476,0.6199969053268433,-0.5524798035621643,0.28563985228538513,0.49201712012290955,0.6146303415298462,-0.5349278450012207,0.30659592151641846,0.49966076016426086,0.6120659708976746,-0.5178669691085815,0.3279149830341339,0.5055290460586548,0.6072544455528259,-0.5024499893188477,0.3510364592075348,0.5129315853118896,0.59733647108078,-0.4886552095413208,0.3759077489376068,0.5194185972213745,0.5873140096664429,-0.4803988039493561,0.3930440843105316,0.5258929133415222,0.575943648815155,-0.47666341066360474,0.40560778975486755,0.5297795534133911,0.5667092204093933,-0.47607627511024475,0.41415682435035706,0.5328297019004822,0.5555064678192139,-0.4798275828361511,0.4210352301597595,0.5373682379722595,0.5448706150054932,-0.4829989969730377,0.4255155324935913,0.5434696674346924,0.5357820987701416,-0.48462048172950745,0.42745906114578247,0.5521619915962219,0.5283437967300415,-0.48338058590888977,0.42698153853416443,0.566278338432312,0.5213564038276672,-0.4756273627281189,0.42578741908073425,0.584522008895874,0.5160126686096191,-0.46282389760017395,0.42173340916633606,0.6064110398292542,0.5124461650848389,-0.4446393847465515,0.41468116641044617,0.6234152913093567,0.5026043653488159,-0.4282952845096588,0.4186949133872986,0.632626473903656,0.4855337142944336,-0.4168393015861511,0.43621760606765747,0.6347944736480713,0.4715171158313751,-0.40127161145210266,0.462264746427536,0.6303433179855347,0.47180086374282837,-0.38437798619270325,0.4820009469985962,0.6229584217071533,0.4802621901035309,-0.36126014590263367,0.5007616877555847,0.6253896951675415,0.4827248752117157,-0.3418408930301666,0.5089294910430908,0.6306062340736389,0.48163700103759766,-0.32272711396217346,0.5159542560577393,0.6344133019447327,0.4733688533306122,-0.3043123483657837,0.5299394130706787,0.6391491293907166,0.46393921971321106,-0.27985644340515137,0.5458288788795471,0.6407442092895508,0.45242705941200256,-0.25538283586502075,0.5652753710746765,0.6337482929229736,0.44869473576545715,-0.2294967919588089,0.5868282914161682,0.6207557916641235,0.451889306306839,-0.2066078931093216,0.6064417362213135,0.6069430708885193,0.4597412049770355,-0.19308781623840332,0.6188499927520752,0.597840428352356,0.4651414155960083,-0.18523497879505157,0.6260339021682739,0.5948330163955688,0.4678645431995392,-0.18536411225795746,0.6268306970596313,0.5966486930847168,0.46977996826171875,-0.196046382188797,0.6203893423080444,0.5978968739509583,0.4731431305408478,-0.2136905938386917,0.6107301712036133,0.5981650948524475,0.47909098863601685,-0.23493099212646484,0.5978946089744568,0.5956762433052063,0.4868047833442688,-0.258038192987442,0.5844717025756836,0.5892683863639832,0.49784204363822937,-0.2863028049468994,0.5682840347290039,0.578527569770813,0.5112122297286987,-0.3155895471572876,0.5516985058784485,0.5662805438041687,0.5214878916740417,-0.3457946181297302,0.5364726185798645,0.5520046353340149,0.5323487520217896,-0.3755338191986084,0.5204518437385559,0.5338200926780701,0.5477726459503174,-0.4054720103740692,0.5005735158920288,0.5115464329719543,0.5695001482963562,-0.4318114221096039,0.47699978947639465,0.4881707727909088,0.5950649976730347,-0.4536512494087219,0.4492078125476837,0.46063870191574097,0.6219043731689453,-0.47506946325302124,0.41875550150871277,0.44158339500427246,0.6404584050178528,-0.48693498969078064,0.39712926745414734,0.4264526069164276,0.6546677350997925,-0.49277693033218384,0.38303956389427185,0.41567620635032654,0.666713297367096,-0.49188077449798584,0.37518003582954407,0.4100208580493927,0.6835864782333374,-0.4840856194496155,0.360906720161438,0.4055384397506714,0.6999990940093994,-0.47457069158554077,0.3468754291534424,0.4007810950279236,0.7128324508666992,-0.46612972021102905,0.33759090304374695,0.4049098789691925,0.724777340888977,-0.4523472487926483,0.32577258348464966,0.41252830624580383,0.7299298048019409,-0.4413345754146576,0.31976044178009033,0.42219361662864685,0.7316192388534546,-0.4301531910896301,0.318518728017807,0.4319283962249756,0.7276835441589355,-0.4261649549007416,0.3198406994342804,0.4412599205970764,0.7213616371154785,-0.4249457120895386,0.3230298161506653,0.45096686482429504,0.7139009833335876,-0.42476892471313477,0.32641342282295227,0.45886704325675964,0.7067683935165405,-0.42432236671447754,0.33146658539772034,0.46421322226524353,0.7006877064704895,-0.42304685711860657,0.33848822116851807,0.4696963131427765,0.6948011517524719,-0.4212905466556549,0.34518271684646606,0.47582805156707764,0.6880949139595032,-0.42027196288108826,0.35140374302864075,0.4848432242870331,0.6798251271247864,-0.42039814591407776,0.35500746965408325,0.49336329102516174,0.668917179107666,-0.42379140853881836,0.3599214553833008,0.5014995336532593,0.6571734547615051,-0.42773815989494324,0.3655974566936493,0.5075763463973999,0.6451152563095093,-0.43200984597206116,0.37357738614082336,0.5098661780357361,0.635101318359375,-0.43417271971702576,0.3849376440048218,0.5101128816604614,0.626460611820221,-0.4354812800884247,0.39709973335266113,0.508284330368042,0.6183706521987915,-0.4369582533836365,0.41028326749801636,0.5078102350234985,0.6114355325698853,-0.4364736080169678,0.4216233789920807,0.5111530423164368,0.6065137982368469,-0.4318423271179199,0.42939016222953796,0.5192076563835144,0.6028789281845093,-0.42395609617233276,0.432691365480423,0.5226627588272095,0.5960983037948608,-0.42158323526382446,0.440179705619812,0.5235323309898376,0.587356686592102,-0.422661155462265,0.4497596025466919,0.5245396494865417,0.5787314772605896,-0.4237438440322876,0.4586602449417114,0.5252657532691956,0.5700798630714417,-0.4253256916999817,0.46712207794189453,0.5266928672790527,0.5623438954353333,-0.42612287402153015,0.47411322593688965,0.5279242396354675,0.5549442768096924,-0.4272787868976593,0.4803808033466339,0.5286427736282349,0.5469428300857544,-0.430279940366745,0.4860551953315735,0.52830970287323,0.538313090801239,-0.435403436422348,0.491458922624588,0.5285225510597229,0.5307909846305847,-0.440177321434021,0.4951452910900116,0.5306090116500854,0.5258507132530212,-0.44252634048461914,0.49609026312828064,0.5318422317504883,0.5213194489479065,-0.44559547305107117,0.4968043863773346,0.5330438017845154,0.5181686282157898,-0.4479624032974243,0.49668431282043457,0.5337113738059998,0.5158043503761292,-0.4499262273311615,0.496653288602829,0.5339648127555847,0.514119565486908,-0.45099881291389465,0.4971545338630676,0.5336441397666931,0.5132883191108704,-0.4509994685649872,0.49835580587387085,0.532998263835907,0.5130242705345154,-0.45030391216278076,0.4999452531337738,0.5323941707611084,0.5128732323646545,-0.4494914412498474,0.5014726519584656,0.5317941308021545,0.5128589868545532,-0.4486222267150879,0.5029004216194153,0.5312095880508423,0.5129666924476624,-0.4477144479751587,0.5042154788970947,0.530717670917511,0.5131590962409973,-0.44678083062171936,0.5053645968437195,0.5302957892417908,0.5133630037307739,-0.4459359049797058,0.506345808506012,0.5299843549728394,0.5135631561279297,-0.4452259838581085,0.5070928931236267,0.5297822952270508,0.5137085318565369,-0.4447644054889679,0.5075618028640747,0.5297031402587891,0.5137718319892883,-0.4446357488632202,0.5076930522918701],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999997615814209,0.9999997019767761,0.9999998211860657,0.9999997615814209,0.9999997019767761,0.9999997019767761],"interpolation":2300,"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,4.566666603088379],"values":[-0.0000012145753771619638,17.630796432495117,0.0000019257072381151374,0.0000049210125325771514,17.630788803100586,-0.000015318148143705912],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.3846764862537384,0.15799054503440857,0.22142164409160614,0.8820632100105286,0.3852458894252777,0.15712834894657135,0.21923673152923584,0.8825143575668335,0.3868406116962433,0.15473857522010803,0.21302562952041626,0.8837592601776123,0.389278382062912,0.15110647678375244,0.2032991647720337,0.8856063485145569,0.3923712968826294,0.14651194214820862,0.19056710600852966,0.8878418803215027,0.3959384262561798,0.14123456180095673,0.17534239590168,0.8902475237846375,0.39981406927108765,0.13555815815925598,0.15814654529094696,0.8926154971122742,0.40385520458221436,0.12977421283721924,0.13951098918914795,0.8947604894638062,0.40794575214385986,0.12418337911367416,0.11997748911380768,0.896528959274292,0.41199836134910583,0.11909548193216324,0.10009760409593582,0.8978051543235779,0.4158594310283661,0.11589059978723526,0.08092068135738373,0.898377537727356,0.4195157587528229,0.11553451418876648,0.06332211196422577,0.8981361985206604,0.42307382822036743,0.11772070080041885,0.047586847096681595,0.8971543312072754,0.42658481001853943,0.122145876288414,0.033988308161497116,0.8955169320106506,0.4301222264766693,0.12850376963615417,0.02279963344335556,0.8932871222496033,0.43560436367988586,0.13114042580127716,0.0009930944070219994,0.8905335664749146,0.4423232078552246,0.13167186081409454,-0.025845443829894066,0.8867607712745667,0.450229287147522,0.1294594407081604,-0.05781271681189537,0.881584644317627,0.45895397663116455,0.1245044469833374,-0.09347503632307053,0.8747127056121826,0.4680638909339905,0.11679205298423767,-0.13159795105457306,0.8660010099411011,0.47652578353881836,0.10879495739936829,-0.1658422201871872,0.8564947247505188,0.48477551341056824,0.09698349982500076,-0.20324555039405823,0.84514981508255,0.4922431707382202,0.08095793426036835,-0.24381574988365173,0.8316828608512878,0.49754759669303894,0.06761550158262253,-0.2735447287559509,0.8203949332237244,0.500742495059967,0.05822252109646797,-0.2906193137168884,0.8132696747779846,0.4983998239040375,0.04663681611418724,-0.29500895738601685,0.8138749003410339,0.49048328399658203,0.031327031552791595,-0.2747627794742584,0.8264080286026001,0.4816462993621826,0.009802672080695629,-0.2345101684331894,0.8443493247032166,0.4789966940879822,-0.012195018120110035,-0.16224412620067596,0.8626067042350769,0.48386257886886597,-0.029477499425411224,-0.05274007096886635,0.8730558753013611,0.49765923619270325,-0.03839549794793129,0.08630289137363434,0.8622139692306519,0.5183730721473694,-0.0553245022892952,0.20288154482841492,0.8288954496383667,0.544761598110199,-0.08649280667304993,0.2750033140182495,0.787481427192688,0.5649933218955994,-0.09989757090806961,0.3307960331439972,0.7492510080337524,0.5791922211647034,-0.10523489862680435,0.3749801814556122,0.7161368131637573,0.583710253238678,-0.10804154723882675,0.4037453234195709,0.6961315274238586,0.574512779712677,-0.1140238419175148,0.4103167653083801,0.6989805102348328,0.5598836541175842,-0.11255840957164764,0.4127520024776459,0.7095749974250793,0.5492168664932251,-0.11230775713920593,0.4072142541408539,0.7210578322410583,0.5427852272987366,-0.10838586837053299,0.3997438848018646,0.7306445837020874,0.5396522879600525,-0.11665929853916168,0.3772975504398346,0.7435137033462524,0.5409314632415771,-0.11972668766975403,0.368314653635025,0.7465943098068237,0.5476679801940918,-0.12285244464874268,0.36997470259666443,0.7403281331062317,0.5545973181724548,-0.12471090257167816,0.3881271779537201,0.7254145741462708,0.5498268008232117,-0.13263753056526184,0.4256291389465332,0.7063552141189575,0.5324680805206299,-0.13961680233478546,0.4669557511806488,0.692052960395813,0.49685138463974,-0.14076396822929382,0.5104387402534485,0.6875874996185303,0.4517836570739746,-0.14555031061172485,0.5486586093902588,0.6882444620132446,0.40747082233428955,-0.15629921853542328,0.5786146521568298,0.6890161037445068,0.36935439705848694,-0.16501526534557343,0.5958806872367859,0.6937388181686401,0.3432779908180237,-0.16685394942760468,0.6097257137298584,0.6946614980697632,0.33190736174583435,-0.16336558759212494,0.6168695092201233,0.6947094798088074,0.32921653985977173,-0.15839365124702454,0.6192722320556641,0.6950035691261292,0.3311447203159332,-0.15495097637176514,0.6062062382698059,0.7062913179397583,0.32920312881469727,-0.15788057446479797,0.5871206521987915,0.7224876284599304,0.3225926458835602,-0.16521121561527252,0.5681965351104736,0.7387773394584656,0.3155440092086792,-0.17368094623088837,0.5403067469596863,0.7604837417602539,0.3124469518661499,-0.1807066947221756,0.4913202226161957,0.792670488357544,0.3127725422382355,-0.18447020649909973,0.40973737835884094,0.8368150591850281,0.3139713406562805,-0.17797636985778809,0.29408615827560425,0.8850196599960327,0.31471747159957886,-0.1600663810968399,0.144399493932724,0.9243811368942261,0.32026368379592896,-0.1360596865415573,-0.02111019194126129,0.9372690320014954,0.3335599899291992,-0.11037874966859818,-0.1538916379213333,0.9235104918479919,0.3425271809101105,-0.10559366643428802,-0.1949225217103958,0.9129788279533386,0.3441498577594757,-0.08487759530544281,-0.15577447414398193,0.9220038056373596,0.3346100449562073,-0.07274278253316879,-0.14505861699581146,0.9282793402671814,0.3232147991657257,-0.07536183297634125,-0.18501517176628113,0.9249984622001648,0.3075120151042938,-0.0665583461523056,-0.1907920092344284,0.9298412799835205,0.2960160970687866,-0.05301380529999733,-0.17650723457336426,0.9372348785400391,0.28052884340286255,-0.043065138161182404,-0.17959871888160706,0.9419093728065491,0.26543158292770386,-0.03172395005822182,-0.17763179540634155,0.9470937848091125,0.2540993094444275,-0.019578440114855766,-0.16907250881195068,0.9520844221115112,0.24112199246883392,-0.006666064262390137,-0.1580161154270172,0.9575211405754089,0.23227913677692413,-0.0000761865230742842,-0.16795513033866882,0.9580383896827698,0.23054225742816925,0.01260246243327856,-0.19056928157806396,0.9541356563568115,0.2343129813671112,0.030480002984404564,-0.22115987539291382,0.9461801052093506,0.2444000542163849,0.05354992300271988,-0.25623655319213867,0.9336722493171692,0.26108381152153015,0.0780617967247963,-0.28451859951019287,0.9191250205039978,0.2801724672317505,0.10188785940408707,-0.3034566342830658,0.9050062894821167,0.29701295495033264,0.12659092247486115,-0.318665474653244,0.8911848068237305,0.31081539392471313,0.1459053009748459,-0.3254898488521576,0.8810004591941833,0.3235246241092682,0.15986773371696472,-0.3293881416320801,0.8725122213363647,0.326364666223526,0.16095808148384094,-0.3220504820346832,0.8739920854568481,0.31675657629966736,0.14565549790859222,-0.2924309968948364,0.8904682993888855,0.29410359263420105,0.11521593481302261,-0.24008728563785553,0.9179251194000244,0.2676294445991516,0.06632330268621445,-0.15188488364219666,0.9491610527038574,0.246195986866951,0.0002045157307293266,-0.020726975053548813,0.9689984321594238,0.2435116022825241,-0.0744488388299942,0.13617947697639465,0.9573999643325806,0.2544500529766083,-0.13730576634407043,0.3138260841369629,0.9043868184089661,0.2703448235988617,-0.17771752178668976,0.4762324094772339,0.8176385760307312,0.28012993931770325,-0.20974501967430115,0.6030567288398743,0.7168381214141846,0.2881375551223755,-0.22134484350681305,0.6939930319786072,0.6215761303901672,0.2969590127468109,-0.22910337150096893,0.7447801828384399,0.5519325733184814,0.2963864505290985,-0.2466873973608017,0.7644756436347961,0.5166018605232239,0.2871371805667877,-0.27038487792015076,0.7589076161384583,0.5181732773780823,0.277081161737442,-0.2879582643508911,0.7460613250732422,0.5326336622238159,0.2721320688724518,-0.295697420835495,0.739596962928772,0.5399106740951538,0.27072155475616455,-0.299198180437088,0.7392506003379822,0.5391649603843689,0.27049535512924194,-0.3015172779560089,0.7392491698265076,0.537987232208252,0.2702521085739136,-0.30377158522605896,0.7378941774368286,0.5387011170387268,0.2707076370716095,-0.3052963316440582,0.7375680208206177,0.5380566716194153,0.27209848165512085,-0.3056762218475342,0.7384560704231262,0.5359171032905579,0.27244555950164795,-0.3066236078739166,0.7362746000289917,0.5381961464881897,0.27411022782325745,-0.30784571170806885,0.7333476543426514,0.5406439900398254,0.2759243845939636,-0.31108519434928894,0.7262712717056274,0.5473772883415222,0.27522170543670654,-0.31685763597488403,0.7122042179107666,0.5626895427703857,0.2721719443798065,-0.3233410716056824,0.6941595673561096,0.5826796293258667,0.2652152180671692,-0.3302277624607086,0.671181857585907,0.6083793640136719,0.2531651556491852,-0.33724910020828247,0.6414406895637512,0.6408777832984924,0.24237416684627533,-0.33838799595832825,0.608354926109314,0.6757608652114868,0.24495837092399597,-0.32354432344436646,0.5640605688095093,0.719131588935852,0.2515343427658081,-0.30144914984703064,0.5098507404327393,0.7654483318328857,0.25809943675994873,-0.2754742205142975,0.44012507796287537,0.8147321939468384,0.2638297975063324,-0.2473839968442917,0.3570168912410736,0.8612397909164429,0.27096065878868103,-0.21756067872047424,0.2761686444282532,0.8960907459259033,0.28191491961479187,-0.1832171380519867,0.22344352304935455,0.9148926138877869,0.29558777809143066,-0.14257360994815826,0.2099771499633789,0.9209833145141602,0.3082965016365051,-0.10079766064882278,0.20334723591804504,0.9238197803497314,0.32272040843963623,-0.05740240588784218,0.20214711129665375,0.9228721857070923,0.33757880330085754,-0.015506777912378311,0.205296590924263,0.9185060858726501,0.3517823815345764,0.021513311192393303,0.21003057062625885,0.9119613766670227,0.3646419048309326,0.05049668624997139,0.21366088092327118,0.9048953056335449,0.3747314214706421,0.0725596621632576,0.21631339192390442,0.898621141910553,0.38069549202919006,0.09074094891548157,0.21889203786849976,0.893825113773346,0.38485318422317505,0.10593345761299133,0.22106681764125824,0.8898289799690247,0.38781270384788513,0.11854974180459976,0.2227591872215271,0.8865244388580322,0.38973307609558105,0.12887489795684814,0.2239626944065094,0.8839344382286072,0.390768438577652,0.13717509806156158,0.2246965765953064,0.8820399641990662,0.39107003808021545,0.14369888603687286,0.2250039428472519,0.8807883262634277,0.3907851278781891,0.1486796736717224,0.22494442760944366,0.8801030516624451,0.3900597095489502,0.15233825147151947,0.22459247708320618,0.8798890709877014,0.38903966546058655,0.15488643944263458,0.22403234243392944,0.8800386190414429,0.3878710865974426,0.15652775764465332,0.22335496544837952,0.8804360628128052,0.38670051097869873,0.157462477684021,0.22265663743019104,0.8809610605239868,0.3856772184371948,0.1578870415687561,0.222035750746727,0.8814901113510132,0.38494810461997986,0.15801268815994263,0.22159995138645172,0.881895899772644,0.38466086983680725,0.15804758667945862,0.221453458070755,0.8820518255233765],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,4.566666603088379],"values":[1.0000001192092896,1.0000001192092896,1.0000001192092896,1,1,0.9999998807907104],"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,4.566666603088379],"values":[5.472285806717991e-7,17.82780647277832,-0.0000025774515961529687,0.0000032402522265329026,17.82781219482422,0.000011022159924323205],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.053471680730581284,-0.28444886207580566,0.7206580638885498,0.6299853920936584,0.05384324491024017,-0.2819397449493408,0.7194636464118958,0.6324421167373657,0.054912202060222626,-0.2747347950935364,0.7160075306892395,0.6394049525260925,0.05667588859796524,-0.2632882595062256,0.710459291934967,0.6501652002334595,0.0591907873749733,-0.24803580343723297,0.7029787302017212,0.6639243960380554,0.06254180520772934,-0.22941851615905762,0.6937384009361267,0.6798402070999146,0.06681099534034729,-0.20790433883666992,0.6829366087913513,0.6970722675323486,0.07205280661582947,-0.18399924039840698,0.6708101630210876,0.7148191332817078,0.07827156782150269,-0.15825305879116058,0.6576352119445801,0.7323561310768127,0.08540253341197968,-0.13125722110271454,0.643721878528595,0.749066174030304,0.09329987317323685,-0.10363208502531052,0.6294031739234924,0.76446533203125,0.10172900557518005,-0.0760134607553482,0.6150189638137817,0.7782190442085266,0.11036820709705353,-0.04903177544474602,0.6008964776992798,0.7901507616043091,0.11881083250045776,-0.02329377271234989,0.5873318314552307,0.8002391457557678,0.12657926976680756,0.0006346735171973705,0.5745749473571777,0.8086043000221252,0.1294044852256775,0.019641373306512833,0.5630404949188232,0.8159988522529602,0.12797485291957855,0.03440352529287338,0.5525811910629272,0.8228565454483032,0.12320151180028915,0.04565221071243286,0.543056309223175,0.829353392124176,0.11693549901247025,0.05905402824282646,0.5348858833312988,0.8347070217132568,0.11090154945850372,0.078095443546772,0.5285760760307312,0.8379793167114258,0.1046687439084053,0.09951644390821457,0.5240949988365173,0.8393243551254272,0.09417650103569031,0.11811867356300354,0.520950198173523,0.8401129245758057,0.07838794589042664,0.1306573450565338,0.5185433030128479,0.8413660526275635,0.06679195165634155,0.13972726464271545,0.517764687538147,0.8413887023925781,0.05505511164665222,0.1477161943912506,0.531968355178833,0.8319606781005859,0.05268070101737976,0.143381729722023,0.5656407475471497,0.8103808164596558,0.06106816977262497,0.14530164003372192,0.6276822686195374,0.7623471021652222,0.08863013237714767,0.1457304060459137,0.6967306137084961,0.6967595219612122,0.1393677145242691,0.13587038218975067,0.757219672203064,0.6234855651855469,0.18374280631542206,0.10884807258844376,0.7980761528015137,0.5634405016899109,0.18335771560668945,0.08236531168222427,0.8161973357200623,0.5416805744171143,0.16398653388023376,0.061163902282714844,0.8237505555152893,0.539260983467102,0.14086246490478516,0.0351463258266449,0.831672728061676,0.5359505414962769,0.12994296848773956,0.012156413868069649,0.8370628356933594,0.5313123464584351,0.12929891049861908,-0.004868943709880114,0.838577151298523,0.529194176197052,0.13579897582530975,-0.019459612667560577,0.8376198410987854,0.5287466049194336,0.1545724719762802,-0.039270348846912384,0.838434636592865,0.5211454629898071,0.17417189478874207,-0.05213843286037445,0.8354320526123047,0.5186511874198914,0.19158975780010223,-0.06115882471203804,0.8310920000076294,0.5184969305992126,0.211577907204628,-0.06585308164358139,0.8258658647537231,0.5185014605522156,0.2319152057170868,-0.07162638753652573,0.8219779133796692,0.5152060389518738,0.2377646118402481,-0.0729888305068016,0.8192603588104248,0.5166750550270081,0.23596663773059845,-0.07073760777711868,0.8173672556877136,0.5207943320274353,0.21699631214141846,-0.06448621302843094,0.8171845078468323,0.5300600528717041,0.19178135693073273,-0.05511751025915146,0.8168005347251892,0.5413122177124023,0.17075860500335693,-0.048412200063467026,0.8164978623390198,0.5493897199630737,0.1401435136795044,-0.056457288563251495,0.8247390389442444,0.5449568033218384,0.11047716438770294,-0.06579261273145676,0.8320920467376709,0.5395265817642212,0.08844202756881714,-0.06676316261291504,0.8334535956382751,0.5413647294044495,0.07881679385900497,-0.06279146671295166,0.8310679197311401,0.546965479850769,0.0749983862042427,-0.05387559533119202,0.8249544501304626,0.5576045513153076,0.08016906678676605,-0.03882957994937897,0.8144291043281555,0.5733851194381714,0.09665713459253311,-0.02038407512009144,0.8036757111549377,0.5868110656738281,0.1240907534956932,-0.0020891553722321987,0.7966303825378418,0.5915886759757996,0.14969615638256073,0.013337621465325356,0.7918295860290527,0.5919620394706726,0.17455510795116425,0.025721650570631027,0.7901373505592346,0.5869853496551514,0.19993725419044495,0.03664838895201683,0.7905880808830261,0.5776265859603882,0.2247520238161087,0.04567771032452583,0.7929288148880005,0.5645033717155457,0.24279090762138367,0.046232543885707855,0.8039260506629944,0.5409418344497681,0.24157395958900452,0.04081233963370323,0.8234829902648926,0.5117148756980896,0.21682633459568024,0.036368101835250854,0.8353171944618225,0.5038936734199524,0.16512945294380188,0.04027985408902168,0.8271023631095886,0.5357345938682556,0.10857006162405014,0.039954300969839096,0.7813171148300171,0.613318681716919,0.0800882875919342,0.06346690654754639,0.697509765625,0.7092516422271729,0.08467885106801987,0.10305649787187576,0.6156107783317566,0.7766802906990051,0.07065801322460175,0.12446516007184982,0.5802170038223267,0.8017880916595459,0.036152493208646774,0.13825608789920807,0.581429123878479,0.800948441028595,0.0312790684401989,0.13851527869701385,0.5489867329597473,0.8236799836158752,0.04155908524990082,0.13524723052978516,0.5041347146034241,0.8519561290740967,0.04578894376754761,0.1330668181180954,0.466071218252182,0.8734839558601379,0.061763565987348557,0.13541316986083984,0.4163679778575897,0.8969315886497498,0.07576977461576462,0.1442756950855255,0.3640953004360199,0.9169940948486328,0.09738080948591232,0.173214390873909,0.32663267850875854,0.9240264892578125,0.11140774935483932,0.1937345266342163,0.3053821623325348,0.9256333112716675,0.1189802885055542,0.20025791227817535,0.2884467840194702,0.9287297129631042,0.12436708807945251,0.20443478226661682,0.2774456739425659,0.930463969707489,0.12795977294445038,0.20597445964813232,0.27062711119651794,0.9316446781158447,0.1261538714170456,0.1960364133119583,0.2673012614250183,0.9349892735481262,0.12284918129444122,0.18272413313388824,0.2650584578514099,0.9387565851211548,0.1153787299990654,0.16439610719680786,0.2700890898704529,0.9416547417640686,0.11008597165346146,0.1421830952167511,0.2598366141319275,0.9487623572349548,0.09885522723197937,0.10784335434436798,0.2500602602958679,0.9571141004562378,0.08912735432386398,0.08637351542711258,0.25522467494010925,0.9588828682899475,0.07805615663528442,0.07953349500894547,0.2921464443206787,0.9498590230941772,0.06865368038415909,0.09475406259298325,0.3791198134422302,0.9179196357727051,0.06597314774990082,0.10820743441581726,0.4901868402957916,0.8623546361923218,0.07918044924736023,0.11217022687196732,0.584452211856842,0.7997274398803711,0.09242517501115799,0.10057608783245087,0.6429725885391235,0.7536101937294006,0.09524035453796387,0.08686163276433945,0.6668215394020081,0.7339845895767212,0.08610203862190247,0.07256598025560379,0.6701156497001648,0.733665943145752,0.08453284204006195,0.06873290240764618,0.6693451404571533,0.7349197864532471,0.08949127048254013,0.07849092036485672,0.6568453907966614,0.7445700764656067,0.10059234499931335,0.0989827960729599,0.6398410201072693,0.7554383277893066,0.0971972718834877,0.09827972203493118,0.635160505771637,0.7599111199378967,0.08667956292629242,0.0890800878405571,0.633704662322998,0.76352459192276,0.07329016178846359,0.07579101622104645,0.6356565356254578,0.7647385597229004,0.06135514751076698,0.06250420212745667,0.644121527671814,0.7598922848701477,0.0534202940762043,0.057701341807842255,0.6516552567481995,0.754428505897522,0.0465409979224205,0.05610043182969093,0.6616379022598267,0.746272087097168,0.04130302742123604,0.05477047711610794,0.6782853007316589,0.7315895557403564,0.037005871534347534,0.05421710014343262,0.6957111358642578,0.7153160572052002,0.03293277323246002,0.05522071570158005,0.7105252742767334,0.700728178024292,0.031410180032253265,0.05643836408853531,0.7242382168769836,0.6865180730819702,0.029204757884144783,0.0625312477350235,0.7295418381690979,0.68044513463974,0.03412457928061485,0.06859772652387619,0.7359685301780701,0.6726665496826172,0.041715268045663834,0.07636228948831558,0.737816333770752,0.669369637966156,0.05062802508473396,0.08530261367559433,0.7349474430084229,0.6708297729492188,0.060641463845968246,0.09448409825563431,0.7338634729385376,0.6699550747871399,0.0728887990117073,0.10171855986118317,0.7389501333236694,0.6620372533798218,0.08786676079034805,0.10572892427444458,0.7509931325912476,0.6458407640457153,0.11394774913787842,0.10299191623926163,0.7771155834197998,0.6103277802467346,0.1487628072500229,0.09240572154521942,0.8098140358924866,0.5599392652511597,0.1807832568883896,0.08711393177509308,0.8331264853477478,0.5153919458389282,0.18813534080982208,0.07894542068243027,0.8334433436393738,0.5135610699653625,0.2035854607820511,0.043589700013399124,0.8068685531616211,0.5528255105018616,0.22372105717658997,-0.019905244931578636,0.7461463809013367,0.6267521977424622,0.23700706660747528,-0.10210439562797546,0.6525729894638062,0.7124261260032654,0.22226010262966156,-0.18388037383556366,0.5656076073646545,0.7725778222084045,0.19182640314102173,-0.2428636997938156,0.5061782002449036,0.8049866557121277,0.15484559535980225,-0.2721637487411499,0.5043295621871948,0.8047369122505188,0.11721985787153244,-0.2921811044216156,0.5216493606567383,0.792951226234436,0.08323652297258377,-0.3058297336101532,0.5536249279975891,0.7700904607772827,0.05583006143569946,-0.31470584869384766,0.5938000082969666,0.7384069561958313,0.03694477304816246,-0.3190779387950897,0.6361020803451538,0.7015686631202698,0.027566060423851013,-0.3183753490447998,0.6753106713294983,0.6647050380706787,0.02785315364599228,-0.31162527203559875,0.7072007656097412,0.6340197324752808,0.03243240714073181,-0.3028395473957062,0.7285802364349365,0.6135203242301941,0.03537556156516075,-0.2982359230518341,0.7364180088043213,0.6062116026878357,0.03740813955664635,-0.29637449979782104,0.7357883453369141,0.6077650189399719,0.03980027884244919,-0.2943582832813263,0.7343165874481201,0.6103674173355103,0.042403724044561386,-0.2922933101654053,0.7322342991828918,0.6136770844459534,0.04506529122591019,-0.2902829349040985,0.729778528213501,0.6173558831214905,0.04762592911720276,-0.28842729330062866,0.7271925210952759,0.6210737228393555,0.04992028325796127,-0.28682368993759155,0.7247273921966553,0.6245081424713135,0.05177634581923485,-0.2855653464794159,0.7226414680480957,0.6273443698883057,0.053018923848867416,-0.28474342823028564,0.7211973667144775,0.6292730569839478,0.05345669388771057,-0.28443968296051025,0.7206659317016602,0.6299818158149719],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999997615814209,1,0.9999997615814209,0.9999998807907104,1.0000001192092896,0.9999998807907104],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,4.566666603088379],"values":[-0.000005899120878893882,18.751100540161133,-0.0000021841819943801966,-0.000015859493942116387,18.751100540161133,0.000006686504093522672],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[0.0550377182662487,0.051962338387966156,-0.01394517533481121,0.9970337748527527,0.0550377294421196,0.05196233093738556,-0.013945179060101509,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998807907104,1,0.9999999403953552,0.9999998807907104,0.9999999403953552,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,4.566666603088379],"values":[-4.513200283050537,8.246394157409668,-0.08130133897066116,-4.513200283050537,8.246389389038086,-0.0812963917851448],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.4313414394855499,-0.5555616617202759,0.6031523942947388,0.3761688768863678,0.4321349561214447,-0.5550230145454407,0.6024152040481567,0.37723320722579956,0.4342501163482666,-0.553650438785553,0.6003378033638,0.380121648311615,0.4372837245464325,-0.5518169403076172,0.5971008539199829,0.38438475131988525,0.4408625066280365,-0.5498813390731812,0.5929023623466492,0.3895350992679596,0.444607675075531,-0.5482177138328552,0.5879403948783875,0.39510446786880493,0.4481429159641266,-0.5472075939178467,0.582428514957428,0.4006355404853821,0.4510898292064667,-0.5472391843795776,0.5766010880470276,0.40568289160728455,0.45306333899497986,-0.5487014651298523,0.5707129836082458,0.40981343388557434,0.4536648392677307,-0.5519769191741943,0.5650367736816406,0.412605345249176,0.45247313380241394,-0.5574311017990112,0.5598568916320801,0.41364118456840515,0.4490348696708679,-0.5653985738754272,0.5554596185684204,0.41250067949295044,0.44228261709213257,-0.5767467021942139,0.5497482419013977,0.41173553466796875,0.43219107389450073,-0.591150164604187,0.5410482883453369,0.4135447144508362,0.42017000913619995,-0.6068266034126282,0.5301046371459961,0.4173819124698639,0.4073902666568756,-0.6222253441810608,0.517976701259613,0.42257431149482727,0.3942498564720154,-0.6365380883216858,0.5059301853179932,0.42827677726745605,0.38067692518234253,-0.6497345566749573,0.4954589307308197,0.43295568227767944,0.3683342933654785,-0.6606321930885315,0.4874080717563629,0.4362664222717285,0.357305645942688,-0.6692758798599243,0.4806080460548401,0.4397936165332794,0.34768062829971313,-0.675728976726532,0.47456663846969604,0.4441792368888855,0.33855241537094116,-0.680633544921875,0.47194692492485046,0.44652706384658813,0.33071762323379517,-0.683364748954773,0.4729083180427551,0.4472093880176544,0.3279857337474823,-0.6824480295181274,0.474835604429245,0.44857701659202576,0.3322839140892029,-0.6758331060409546,0.4756094217300415,0.4545687735080719,0.3561239242553711,-0.6546655893325806,0.47030118107795715,0.4726579487323761,0.41012996435165405,-0.608887255191803,0.4598318934440613,0.4996042251586914,0.47392863035202026,-0.5524446964263916,0.4313604533672333,0.5330334901809692,0.532353937625885,-0.49819910526275635,0.38600248098373413,0.5651540756225586,0.5803208351135254,-0.4546371400356293,0.3340061604976654,0.5873438119888306,0.6204925179481506,-0.41511642932891846,0.279377281665802,0.6038342714309692,0.6518694162368774,-0.3848212659358978,0.23048211634159088,0.6114383935928345,0.6760637760162354,-0.3618369996547699,0.18296469748020172,0.6152526140213013,0.6951336860656738,-0.33910465240478516,0.13916680216789246,0.6184090971946716,0.7133923768997192,-0.31032782793045044,0.0970127284526825,0.6207709908485413,0.7289608716964722,-0.2890017628669739,0.0623881034553051,0.6174154877662659,0.7392390370368958,-0.2805536389350891,0.042186785489320755,0.6107664108276367,0.7416601181030273,-0.2775992751121521,0.031052662059664726,0.609848141670227,0.7374327778816223,-0.27610400319099426,0.02417457476258278,0.6159343719482422,0.7278987765312195,-0.2770341634750366,0.012456339783966541,0.6271047592163086,0.7114856243133545,-0.289223849773407,-0.0007663415162824094,0.640419602394104,0.6911534667015076,-0.31195732951164246,-0.014390171505510807,0.6517533659934998,0.6703193783760071,-0.3428725302219391,-0.01694755256175995,0.6578929424285889,0.6465281844139099,-0.38138547539711,-0.005550780799239874,0.660693347454071,0.623634397983551,-0.41665855050086975,0.014143303968012333,0.6612683534622192,0.6002330183982849,-0.45034050941467285,0.037208154797554016,0.6599465012550354,0.5770204067230225,-0.48133403062820435,0.06112152338027954,0.6569849252700806,0.5559436082839966,-0.5073511004447937,0.08552528917789459,0.6528453826904297,0.5344892740249634,-0.5331392288208008,0.1107090413570404,0.6463956236839294,0.5110635757446289,-0.5607331991195679,0.1367238312959671,0.6369448900222778,0.4885592460632324,-0.5822541117668152,0.15620094537734985,0.6307861804962158,0.469343900680542,-0.5987471342086792,0.17099833488464355,0.6260812878608704,0.45747610926628113,-0.6063948273658752,0.1833234429359436,0.6240139603614807,0.45036664605140686,-0.6077004671096802,0.19410821795463562,0.6246535181999207,0.45375362038612366,-0.603978157043457,0.20903140306472778,0.62098628282547,0.46690788865089417,-0.5951486229896545,0.23393356800079346,0.610794723033905,0.48724365234375,-0.583438515663147,0.2652178406715393,0.5931717157363892,0.5113871097564697,-0.573932945728302,0.30093711614608765,0.5643767714500427,0.5354017615318298,-0.5660839676856995,0.3372834026813507,0.5283311605453491,0.5560071468353271,-0.5614501237869263,0.3718157112598419,0.48721951246261597,0.564652144908905,-0.5577757358551025,0.4127674996852875,0.44685256481170654,0.5603377819061279,-0.5551238059997559,0.4577809274196625,0.410238653421402,0.5506805777549744,-0.5518648028373718,0.5020144581794739,0.3744029998779297,0.5448386669158936,-0.5481345057487488,0.5306971669197083,0.34793657064437866,0.5369653701782227,-0.5499070882797241,0.5532276630401611,0.3212624788284302,0.5164270401000977,-0.5692718625068665,0.5652227997779846,0.29959285259246826,0.48886364698410034,-0.5973488092422485,0.5725081562995911,0.27644404768943787,0.4521569013595581,-0.6248369812965393,0.583040714263916,0.25533607602119446,0.4115907549858093,-0.6490342020988464,0.5946605801582336,0.23606452345848083,0.3729305565357208,-0.6684096455574036,0.6051539182662964,0.21895234286785126,0.3458808362483978,-0.680185079574585,0.6140273809432983,0.20170556008815765,0.3278017044067383,-0.6894203424453735,0.6183496117591858,0.1867871880531311,0.31511303782463074,-0.6987021565437317,0.6158763766288757,0.1822509914636612,0.30023670196533203,-0.7116171717643738,0.6081421971321106,0.18336328864097595,0.2842482924461365,-0.7261942028999329,0.5963979363441467,0.19014312326908112,0.26716816425323486,-0.7412670850753784,0.5802190899848938,0.2061314433813095,0.2499638795852661,-0.7541237473487854,0.5643482804298401,0.22433553636074066,0.23329924046993256,-0.7650691270828247,0.5486860275268555,0.2432784140110016,0.21829485893249512,-0.7728776335716248,0.5354716777801514,0.2612999975681305,0.2073744535446167,-0.7755701541900635,0.5246505737304688,0.2832464575767517,0.202786386013031,-0.773882269859314,0.5169786214828491,0.30449479818344116,0.20214782655239105,-0.7693682909011841,0.5116240978240967,0.3247299790382385,0.21268248558044434,-0.7597801089286804,0.5054588913917542,0.3493017256259918,0.24299970269203186,-0.7431740164756775,0.4960356056690216,0.3776138722896576,0.2985217869281769,-0.7156456112861633,0.47938665747642517,0.4110043942928314,0.3747614920139313,-0.6742575168609619,0.4496091306209564,0.4503135085105896,0.46197566390037537,-0.615354061126709,0.40549972653388977,0.49344488978385925,0.539925217628479,-0.5529477000236511,0.3580877184867859,0.5239302515983582,0.6042597889900208,-0.49199506640434265,0.31039735674858093,0.5444855690002441,0.6558011770248413,-0.4370899796485901,0.2638171911239624,0.5561274290084839,0.7033695578575134,-0.38494226336479187,0.2107032984495163,0.5591912865638733,0.7424899935722351,-0.3365141749382019,0.15872572362422943,0.5570215582847595,0.7706384658813477,-0.29461580514907837,0.1134469136595726,0.5535771250724792,0.792729377746582,-0.26476162672042847,0.07531577348709106,0.5438832640647888,0.8052125573158264,-0.24948501586914062,0.049882061779499054,0.5356321334838867,0.8118306398391724,-0.2506449818611145,0.03463618829846382,0.526220977306366,0.8098974227905273,-0.2615886926651001,0.023995596915483475,0.5244632959365845,0.8054531812667847,-0.2740171551704407,0.014576938934624195,0.5253068804740906,0.7999240159988403,-0.2873668968677521,0.006618672050535679,0.526780903339386,0.7945358753204346,-0.2992432713508606,0.0025140191428363323,0.5283558964729309,0.7898308634757996,-0.30900585651397705,0.0029378177132457495,0.5297867655754089,0.7851012349128723,-0.3183375895023346,0.006274326238781214,0.5312606692314148,0.7799766659736633,-0.32801687717437744,0.011751588433980942,0.5328257083892822,0.7734723091125488,-0.33987492322921753,0.018292047083377838,0.534687876701355,0.7645613551139832,-0.35283026099205017,0.028052084147930145,0.5386742353439331,0.7546719908714294,-0.366115003824234,0.04119393974542618,0.5428932905197144,0.7437317371368408,-0.379083514213562,0.05902450531721115,0.5474257469177246,0.733363151550293,-0.39022111892700195,0.08275922387838364,0.550506055355072,0.72343510389328,-0.39966875314712524,0.11246884614229202,0.5515952706336975,0.7115046977996826,-0.4087696969509125,0.1480087786912918,0.55205237865448,0.6970264911651611,-0.41871047019958496,0.1876320093870163,0.5510261654853821,0.6778138279914856,-0.4360411763191223,0.23191510140895844,0.5446575880050659,0.6514043211936951,-0.46177735924720764,0.28039342164993286,0.5327417254447937,0.6212153434753418,-0.48870667815208435,0.328596293926239,0.5169930458068848,0.5912277698516846,-0.5118638277053833,0.37127330899238586,0.5006009340286255,0.5668554306030273,-0.5268723368644714,0.4043157696723938,0.4874517321586609,0.5488991737365723,-0.5351802706718445,0.43040284514427185,0.47649261355400085,0.5340917110443115,-0.5413447022438049,0.45427563786506653,0.46403202414512634,0.5219647884368896,-0.5456667542457581,0.4760204255580902,0.45078277587890625,0.5119590163230896,-0.548292875289917,0.49583500623703003,0.43751630187034607,0.5037686824798584,-0.5496041178703308,0.5136021971702576,0.424694299697876,0.49706560373306274,-0.5499687194824219,0.5292108654975891,0.4127906262874603,0.491507887840271,-0.5497477650642395,0.5425536036491394,0.40228471159935,0.4866873621940613,-0.5492681264877319,0.5535714626312256,0.39369863271713257,0.48192906379699707,-0.5487134456634521,0.562427282333374,0.3877287209033966,0.4763675928115845,-0.5488401651382446,0.5693094730377197,0.38436344265937805,0.47011011838912964,-0.5496374368667603,0.5750405788421631,0.3823917508125305,0.46400755643844604,-0.5504677295684814,0.5802656412124634,0.38075464963912964,0.45815035700798035,-0.5513063073158264,0.5849751830101013,0.3794254958629608,0.45260924100875854,-0.5521459579467773,0.5891461372375488,0.3784000873565674,0.4475051164627075,-0.5529413223266602,0.5927897095680237,0.3776182532310486,0.4429550766944885,-0.553652286529541,0.5959144234657288,0.37702253460884094,0.43906620144844055,-0.5542985796928406,0.5984744429588318,0.3765663504600525,0.43589356541633606,-0.5548193454742432,0.6005010604858398,0.3762592077255249,0.43352317810058594,-0.5552042722702026,0.6019752025604248,0.3760741353034973,0.43204036355018616,-0.5554429888725281,0.6028772592544556,0.3759830892086029,0.4315468966960907,-0.5555368661880493,0.6031520962715149,0.3759702742099762],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,4.566666603088379],"values":[1,0.9999997615814209,0.9999998807907104,0.9999998807907104,0.9999997615814209,1.0000001192092896],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,4.566666603088379],"values":[0.0000036241312955098692,17.92139434814453,0.0000027691862669598777,-0.0000076057613114244305,17.921405792236328,5.284617827783222e-7],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.4312799572944641,-0.19474835693836212,-0.10313624888658524,0.8748906254768372,0.4272627532482147,-0.19148211181163788,-0.10645774751901627,0.8771818280220032,0.4198952317237854,-0.18578730523586273,-0.10547026991844177,0.8820698261260986,0.40896520018577576,-0.17722724378108978,-0.10260073840618134,0.8892756104469299,0.3954869210720062,-0.16646231710910797,-0.09711282700300217,0.8980253338813782,0.38021600246429443,-0.15388675034046173,-0.08921948075294495,0.9076313376426697,0.36234965920448303,-0.13850025832653046,-0.08312789350748062,0.9179380536079407,0.34263572096824646,-0.12087555974721909,-0.07869049906730652,0.9283306002616882,0.3218511939048767,-0.10173162072896957,-0.07555397599935532,0.9382718801498413,0.29950061440467834,-0.08088354766368866,-0.07567570358514786,0.9476446509361267,0.2783931791782379,-0.06139824166893959,-0.0726112648844719,0.955748438835144,0.2558700740337372,-0.044136229902505875,-0.06230500340461731,0.9636911153793335,0.22919027507305145,-0.029424848034977913,-0.05699753388762474,0.9712658524513245,0.20728746056556702,-0.01699499785900116,-0.05996781215071678,0.9762924909591675,0.1824411153793335,-0.005910586565732956,-0.06349048018455505,0.9811468720436096,0.15676265954971313,0.006668915972113609,-0.07271439582109451,0.9849333167076111,0.1306154578924179,0.0204694215208292,-0.083048015832901,0.9877366423606873,0.10306369513273239,0.03663159906864166,-0.09819760173559189,0.9891376495361328,0.07947663962841034,0.05421503633260727,-0.11996233463287354,0.9881058931350708,0.05777770280838013,0.07393818348646164,-0.14792826771736145,0.9845365285873413,0.04009784758090973,0.09241320192813873,-0.17866218090057373,0.9787399172782898,0.024450551718473434,0.1174059733748436,-0.2209775447845459,0.9678775668144226,0.013096031732857227,0.14293445646762848,-0.2697431147098541,0.9521748423576355,0.006239222828298807,0.1664135605096817,-0.3118674159049988,0.9354177713394165,0.007600815966725349,0.18521159887313843,-0.35138237476348877,0.9176978468894958,0.01682928577065468,0.2024216502904892,-0.3812960684299469,0.9018623232841492,0.020291127264499664,0.22012896835803986,-0.4119485318660736,0.8839852809906006,0.039225511252880096,0.23161680996418,-0.4400236904621124,0.866714596748352,0.06967438757419586,0.23989610373973846,-0.45319491624832153,0.8556925654411316,0.09284162521362305,0.2536158263683319,-0.44525477290153503,0.8537023663520813,0.10310493409633636,0.2697350084781647,-0.43317198753356934,0.8537999987602234,0.11664075404405594,0.2841630280017853,-0.4373481273651123,0.8452058434486389,0.149095356464386,0.28393736481666565,-0.46340954303741455,0.8260761499404907,0.22277773916721344,0.25053879618644714,-0.47871890664100647,0.8114361763000488,0.2974887192249298,0.2046479731798172,-0.5117289423942566,0.7795853018760681,0.3580082952976227,0.1601109653711319,-0.5416311621665955,0.7435255646705627,0.4273500442504883,0.15903204679489136,-0.5541290044784546,0.6964350342750549,0.4682259261608124,0.15285612642765045,-0.5693680047988892,0.6581942439079285,0.47842374444007874,0.14333444833755493,-0.6004051566123962,0.6245635151863098,0.48455315828323364,0.13747848570346832,-0.6046533584594727,0.617010772228241,0.4785604476928711,0.1339799463748932,-0.5996912717819214,0.6272158026695251,0.44937217235565186,0.14678211510181427,-0.5849866271018982,0.6590222716331482,0.4268292784690857,0.1579200178384781,-0.5721901655197144,0.6822583675384521,0.38805240392684937,0.16686519980430603,-0.5882561802864075,0.6895840167999268,0.3250907361507416,0.1767575591802597,-0.6166524887084961,0.6948471069335938,0.23993687331676483,0.1918298453092575,-0.6474790573120117,0.6974256038665771,0.14841483533382416,0.21119210124015808,-0.6664326190948486,0.6994558572769165,0.04542350023984909,0.2282228022813797,-0.6698673367500305,0.7050736546516418,-0.062351029366254807,0.2357543259859085,-0.6539445519447327,0.7161626219749451,-0.16404664516448975,0.2383429855108261,-0.6183785796165466,0.7306773066520691,-0.2340053915977478,0.24075116217136383,-0.5655747056007385,0.7532633543014526,-0.28355836868286133,0.24166542291641235,-0.5144594311714172,0.7723496556282043,-0.3260095715522766,0.23280441761016846,-0.4624250531196594,0.7910012602806091,-0.3534505069255829,0.22142687439918518,-0.4212760329246521,0.8053380846977234,-0.3685096502304077,0.20796449482440948,-0.39914795756340027,0.813407838344574,-0.36883991956710815,0.1841285228729248,-0.39125537872314453,0.8227837681770325,-0.3441184163093567,0.1523953676223755,-0.40199601650238037,0.8347199559211731,-0.27814334630966187,0.15241408348083496,-0.47209471464157104,0.8225162029266357,-0.1266874223947525,0.15632838010787964,-0.5413559675216675,0.816361129283905,0.05661194398999214,0.15596434473991394,-0.5338743925094604,0.8291250467300415,0.21865621209144592,0.12499118596315384,-0.4671472907066345,0.8475494384765625,0.3109095096588135,0.06472335755825043,-0.3913955092430115,0.86368727684021,0.3433849811553955,0.03419315069913864,-0.3197861909866333,0.8824139833450317,0.318456768989563,0.034322261810302734,-0.2643887400627136,0.9096735119819641,0.28508302569389343,0.03104265406727791,-0.21865640580654144,0.932712972164154,0.2727741003036499,0.012899605557322502,-0.1637742668390274,0.9479482769966125,0.2791607975959778,-0.004945920780301094,-0.10321857780218124,0.954667866230011,0.291000634431839,-0.028076430782675743,-0.05091067776083946,0.9549546837806702,0.28804606199264526,-0.027430837973952293,-0.004146921914070845,0.9572146534919739,0.2750208377838135,-0.017899256199598312,0.009072146378457546,0.9612288475036621,0.2589600086212158,-0.00592392822727561,-0.007849372923374176,0.9658380150794983,0.24658434092998505,0.00672856206074357,-0.03969574347138405,0.9682846665382385,0.23065847158432007,0.019702579826116562,-0.0712408572435379,0.9702233076095581,0.21232454478740692,0.03247295692563057,-0.10188378393650055,0.9713307619094849,0.19304904341697693,0.04090438410639763,-0.12122558802366257,0.9728120565414429,0.17054125666618347,0.046911343932151794,-0.12852545082569122,0.9758054614067078,0.14339560270309448,0.05111207067966461,-0.1306895911693573,0.9796660542488098,0.1221361979842186,0.055621981620788574,-0.12857891619205475,0.9825764298439026,0.10476106405258179,0.06274358928203583,-0.1329846978187561,0.9835667014122009,0.08515280485153198,0.07284989207983017,-0.14427122473716736,0.9831722974777222,0.06120621785521507,0.0945982113480568,-0.17818838357925415,0.9775243401527405,0.037319380789995193,0.11838076263666153,-0.22618192434310913,0.9661443829536438,0.016423098742961884,0.14257559180259705,-0.27358514070510864,0.9510802626609802,0.002734482055529952,0.16345837712287903,-0.3079570531845093,0.9372493624687195,-0.00670516025274992,0.22072899341583252,-0.3261992633342743,0.9191451668739319,-0.01351859513670206,0.28569915890693665,-0.3330194056034088,0.8984939455986023,-0.02258460968732834,0.34637367725372314,-0.3466617166996002,0.8714016675949097,-0.019396739080548286,0.3844204843044281,-0.36934688687324524,0.8458295464515686,-0.0020931276958435774,0.38469237089157104,-0.3953341245651245,0.8340973258018494,0.058268800377845764,0.34967732429504395,-0.43619436025619507,0.8270822763442993,0.1452014446258545,0.30991557240486145,-0.4652828574180603,0.8163214921951294,0.25998809933662415,0.2597474157810211,-0.47408345341682434,0.8001139760017395,0.35635697841644287,0.21161724627017975,-0.47645050287246704,0.7753856182098389,0.3999500274658203,0.1925021857023239,-0.49931326508522034,0.7440895438194275,0.4171464145183563,0.17275933921337128,-0.5143734812736511,0.729083776473999,0.41689059138298035,0.1423196643590927,-0.5329434275627136,0.7224394679069519,0.39450496435165405,0.15010161697864532,-0.5861889719963074,0.6915330290794373,0.37233713269233704,0.16087907552719116,-0.6104856133460999,0.6802870035171509,0.3592527210712433,0.17382268607616425,-0.6242843270301819,0.6715595722198486,0.33691421151161194,0.17750310897827148,-0.6252342462539673,0.681222140789032,0.32716792821884155,0.18739184737205505,-0.6202109456062317,0.6878836750984192,0.32620149850845337,0.19771777093410492,-0.6271529793739319,0.6791019439697266,0.3234960734844208,0.20917540788650513,-0.6377078294754028,0.6670267581939697,0.30943453311920166,0.22120316326618195,-0.6342384219169617,0.6730982065200806,0.2961992025375366,0.23088252544403076,-0.6299657225608826,0.6797811985015869,0.28286880254745483,0.23871053755283356,-0.6248020529747009,0.6874772310256958,0.26870858669281006,0.24576710164546967,-0.6184008121490479,0.6964012384414673,0.25432318449020386,0.25198283791542053,-0.6107679009437561,0.7062485218048096,0.24034303426742554,0.2573438882827759,-0.6019519567489624,0.7167030572891235,0.2274118810892105,0.26189708709716797,-0.5920464396476746,0.7274439930915833,0.21379205584526062,0.2608036398887634,-0.5725433230400085,0.747307538986206,0.20167681574821472,0.2597233057022095,-0.5530067086219788,0.7655415534973145,0.18900534510612488,0.25741010904312134,-0.5318785309791565,0.7842973470687866,0.17394305765628815,0.25228187441825867,-0.5072724223136902,0.8054640889167786,0.15403319895267487,0.2429346740245819,-0.4783996343612671,0.8296929597854614,0.13382600247859955,0.22069470584392548,-0.4404926598072052,0.859855055809021,0.1309906393289566,0.17898249626159668,-0.3833913505077362,0.8965588808059692,0.14631125330924988,0.12804584205150604,-0.31794455647468567,0.9279593229293823,0.1763473004102707,0.07509947568178177,-0.2491634041070938,0.9493046402931213,0.2189195305109024,0.02344309352338314,-0.17744266986846924,0.9591865539550781,0.27078455686569214,-0.021959569305181503,-0.11044946312904358,0.956030547618866,0.32747477293014526,-0.057910218834877014,-0.05494676157832146,0.9414815902709961,0.3834959864616394,-0.08395186066627502,-0.014037040993571281,0.9196118116378784,0.40712210536003113,-0.09976722300052643,-0.009624489583075047,0.9078576564788818,0.4226624369621277,-0.11405110359191895,-0.016572339460253716,0.8989294767379761,0.43348902463912964,-0.12681448459625244,-0.02487732656300068,0.8918444514274597,0.44084063172340393,-0.13841477036476135,-0.03417219594120979,0.8861902952194214,0.4452002942562103,-0.1489754170179367,-0.04383298382163048,0.8818626403808594,0.4471096396446228,-0.15852554142475128,-0.05351288989186287,0.8786916732788086,0.44709187746047974,-0.1670408993959427,-0.06297089159488678,0.876493513584137,0.445607990026474,-0.17447057366371155,-0.07197067141532898,0.8751078844070435,0.44307488203048706,-0.18076112866401672,-0.08025844395160675,0.8743961453437805,0.4399294853210449,-0.18587692081928253,-0.08759322017431259,0.8742077946662903,0.43663322925567627,-0.1898074895143509,-0.09373605251312256,0.8743787407875061,0.43372073769569397,-0.1925705075263977,-0.09849759191274643,0.8747006058692932,0.43175533413887024,-0.19420339167118073,-0.10169951617717743,0.8749455213546753,0.43128007650375366,-0.19474861025810242,-0.10313595831394196,0.8748905658721924],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999999403953552,0.9999998807907104,0.9999997615814209,0.9999999403953552,0.9999998807907104,0.9999998807907104],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,4.566666603088379],"values":[-0.0000013626668078359216,17.203706741333008,4.436564893239847e-7,-8.880879249772988e-7,17.203697204589844,-0.0000012088059975212673],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[-0.08082585781812668,0.1466812789440155,-0.6006653904914856,0.7817626595497131,-0.08326415717601776,0.15126042068004608,-0.5922189950942993,0.7870604395866394,-0.08915317058563232,0.15940538048744202,-0.578701376914978,0.7948247194290161,-0.0981658473610878,0.17103257775306702,-0.5594445466995239,0.8050671815872192,-0.10993684828281403,0.18489855527877808,-0.5371185541152954,0.8156163692474365,-0.12427466362714767,0.20049090683460236,-0.5137076377868652,0.8249022364616394,-0.14041191339492798,0.2179003804922104,-0.48671653866767883,0.8342128396034241,-0.15740860998630524,0.23607802391052246,-0.458269864320755,0.8423054218292236,-0.17423470318317413,0.25435441732406616,-0.43083032965660095,0.8481340408325195,-0.1888158619403839,0.27176767587661743,-0.40387582778930664,0.8528629541397095,-0.20310594141483307,0.2911679148674011,-0.38821709156036377,0.850445032119751,-0.22244815528392792,0.31962284445762634,-0.39033573865890503,0.834263801574707,-0.23726733028888702,0.3475804924964905,-0.38175833225250244,0.8228928446769714,-0.24910254776477814,0.37398961186408997,-0.3691948354244232,0.8134954571723938,-0.26644182205200195,0.4065878987312317,-0.372387170791626,0.7905839085578918,-0.2819509208202362,0.43625715374946594,-0.37441325187683105,0.7681133151054382,-0.2973492741584778,0.46386557817459106,-0.3786087930202484,0.7436850666999817,-0.31259453296661377,0.48837003111839294,-0.3842698037624359,0.7184122204780579,-0.33365049958229065,0.5077183842658997,-0.38948819041252136,0.6922415494918823,-0.35610276460647583,0.5270524621009827,-0.39244139194488525,0.6643766164779663,-0.3777627944946289,0.5428011417388916,-0.3957836329936981,0.6371951103210449,-0.3980250358581543,0.5528674721717834,-0.40059104561805725,0.6127320528030396,-0.4155319631099701,0.5628729462623596,-0.40059176087379456,0.5916362404823303,-0.4265599250793457,0.5667192935943604,-0.4106369614601135,0.5729339122772217,-0.44662949442863464,0.5561001300811768,-0.4186379611492157,0.5621538758277893,-0.4712885022163391,0.5441395044326782,-0.4370891749858856,0.5392146706581116,-0.5029463171958923,0.5111621022224426,-0.42968103289604187,0.5487555861473083,-0.5310062170028687,0.4745074212551117,-0.4104061722755432,0.5695979595184326,-0.48399487137794495,0.47919854521751404,-0.4706929624080658,0.5608617663383484,-0.29181423783302307,0.49708911776542664,-0.5179122090339661,0.6320710778236389,-0.056994397193193436,0.5748016834259033,-0.41053858399391174,0.7055584192276001,0.05560898035764694,0.6323036551475525,-0.2482910305261612,0.7317454218864441,0.04312862455844879,0.5862776041030884,-0.14087337255477905,0.7966010570526123,0.03324923664331436,0.4465934634208679,-0.13116618990898132,0.8844457268714905,0.01556822843849659,0.2635466158390045,-0.1119396910071373,0.9580032825469971,0.019519466906785965,0.10633477568626404,-0.07365629822015762,0.9914064407348633,0.04424857720732689,0.06600184738636017,-0.06825454533100128,0.9944984316825867,0.059000164270401,0.04225325584411621,-0.07023269683122635,0.9948874711990356,0.08262443542480469,0.02598319575190544,-0.05668015033006668,0.9946283102035522,0.09595976769924164,-0.018595360219478607,-0.10632748156785965,0.9895152449607849,0.11483538150787354,-0.06703632324934006,-0.1750185787677765,0.9755447506904602,0.1482035368680954,-0.09719108045101166,-0.2511574923992157,0.951582670211792,0.15620577335357666,-0.11599520593881607,-0.3109644949436188,0.9302935004234314,0.1489028036594391,-0.10717166215181351,-0.3292768597602844,0.9262391328811646,0.1375618726015091,-0.08062661439180374,-0.35085535049438477,0.9227548837661743,0.11386403441429138,-0.04313250258564949,-0.37832698225975037,0.9176291823387146,0.07408256828784943,0.004605441354215145,-0.3984752893447876,0.9141706824302673,0.01677832193672657,0.05686607211828232,-0.41096776723861694,0.9097198843955994,-0.052960868924856186,0.10811427980661392,-0.4184597134590149,0.9002209901809692,-0.1283198446035385,0.1542869657278061,-0.4196685254573822,0.8852162957191467,-0.2062903791666031,0.19634182751178741,-0.4181023836135864,0.8626033663749695,-0.2739296853542328,0.2287738174200058,-0.39756643772125244,0.8453200459480286,-0.3236995339393616,0.2449365109205246,-0.3655601739883423,0.8376100063323975,-0.3594207763671875,0.2508293092250824,-0.3401406407356262,0.8319889903068542,-0.38286247849464417,0.255038857460022,-0.32442429661750793,0.826511025428772,-0.3921474814414978,0.26584282517433167,-0.3343799114227295,0.8147011995315552,-0.3795160949230194,0.26945069432258606,-0.3347053527832031,0.8193511366844177,-0.3003181219100952,0.21798202395439148,-0.2566007375717163,0.8924399018287659,-0.1740264594554901,0.17765730619430542,-0.17920641601085663,0.9518601298332214,-0.046890195459127426,0.14528605341911316,-0.14204122126102448,0.9780171513557434,0.06552854925394058,0.12351862341165543,-0.12939366698265076,0.9816855192184448,0.11565395444631577,0.11176848411560059,-0.10641315579414368,0.9812279343605042,0.11783566325902939,0.10892891883850098,-0.06102105602622032,0.9851526021957397,0.09586050361394882,0.09387382119894028,-0.0602748841047287,0.9891235828399658,0.0649493858218193,0.0834670439362526,-0.03823208808898926,0.9936564564704895,0.02083607017993927,0.07444647699594498,-0.019480885937809944,0.9968169927597046,0.021565988659858704,0.06797339767217636,-0.007590955123305321,0.9974251389503479,0.037107255309820175,0.06537735462188721,-0.010475031100213528,0.997115433216095,0.031141286715865135,0.07372381538152695,-0.008844980970025063,0.9967531561851501,0.013040531426668167,0.10202821344137192,-0.0043886806815862656,0.9946863651275635,-0.008908635936677456,0.1388748437166214,-0.0017896813806146383,0.9902682900428772,-0.038459885865449905,0.18737806379795074,-0.007042487617582083,0.9815093874931335,-0.07671723514795303,0.24857258796691895,-0.022255435585975647,0.9653138518333435,-0.11724092811346054,0.31276214122772217,-0.042528387159109116,0.9416080713272095,-0.15407080948352814,0.36831650137901306,-0.06848040223121643,0.9142841696739197,-0.1876469850540161,0.41592422127723694,-0.10133431106805801,0.8840401768684387,-0.21867147088050842,0.45845305919647217,-0.13515299558639526,0.8507274985313416,-0.2458455115556717,0.4930714964866638,-0.16647548973560333,0.8177569508552551,-0.2708030343055725,0.5210443139076233,-0.1966833472251892,0.7851715087890625,-0.2962498366832733,0.5466867685317993,-0.22606833279132843,0.7498418092727661,-0.32229557633399963,0.5683159828186035,-0.2549301087856293,0.7128486037254333,-0.3484598696231842,0.5826038718223572,-0.28630486130714417,0.6761494278907776,-0.37237679958343506,0.5864009261131287,-0.31792017817497253,0.6452877521514893,-0.3972666561603546,0.5739879608154297,-0.3471069633960724,0.6262858510017395,-0.4417741298675537,0.5172073841094971,-0.29434436559677124,0.6713371276855469,-0.48729872703552246,0.44635066390037537,-0.23191918432712555,0.7138098478317261,-0.4602245092391968,0.3923838436603546,-0.2204379439353943,0.7652682662010193,-0.34772437810897827,0.34227079153060913,-0.21952854096889496,0.8448347449302673,-0.2457166463136673,0.364738404750824,-0.2079223394393921,0.8737033605575562,-0.16455291211605072,0.4139679968357086,-0.13703350722789764,0.8847455382347107,-0.09779059886932373,0.4124009907245636,-0.09236623346805573,0.9010165929794312,-0.036353569477796555,0.344988614320755,-0.07526534050703049,0.9348777532577515,0.024000395089387894,0.2355983704328537,-0.07299806922674179,0.9688078761100769,0.0526142343878746,0.11470197141170502,-0.08339028805494308,0.9884944558143616,0.06575445830821991,0.004695727489888668,-0.14667104184627533,0.9869862794876099,0.06721771508455276,-0.04796989634633064,-0.2314491719007492,0.9693358540534973,0.105837881565094,-0.052841875702142715,-0.20258140563964844,0.9720940589904785,0.11924094706773758,-0.07528316974639893,-0.2201659083366394,0.9652155637741089,0.11996572464704514,-0.08724603056907654,-0.2263181060552597,0.9626923203468323,0.11696494370698929,-0.09614251554012299,-0.259861558675766,0.9537021517753601,0.11153300851583481,-0.09846565872430801,-0.2834123969078064,0.9473870992660522,0.1013060212135315,-0.07886527478694916,-0.2569805085659027,0.9578509330749512,0.0832882970571518,-0.052138518542051315,-0.2212572693824768,0.970252513885498,0.07197810709476471,-0.021078137680888176,-0.22145874798297882,0.972281277179718,0.0597241073846817,0.011379149742424488,-0.22496265172958374,0.9724686741828918,0.04669801518321037,0.04436779022216797,-0.23009996116161346,0.9710328578948975,0.032809577882289886,0.07695311307907104,-0.23508679866790771,0.9683676958084106,0.017057733610272408,0.10829345881938934,-0.23791465163230896,0.9650794267654419,0.0021871854551136494,0.13645868003368378,-0.23716452717781067,0.9618353843688965,-0.012226623483002186,0.1621679961681366,-0.23166431486606598,0.9591056704521179,-0.029696661978960037,0.18661242723464966,-0.2226378321647644,0.9564132690429688,-0.047471120953559875,0.20752547681331635,-0.2125433087348938,0.9536797404289246,-0.06250527501106262,0.22299420833587646,-0.20365586876869202,0.951257586479187,-0.07157494127750397,0.2314363718032837,-0.19815298914909363,0.9497629404067993,-0.07201079279184341,0.23156337440013885,-0.19722263514995575,0.9498926997184753,-0.06779436022043228,0.22367741167545319,-0.20754554867744446,0.9498932957649231,-0.0676867812871933,0.2099785953760147,-0.25026029348373413,0.9427074193954468,-0.07084211707115173,0.19206896424293518,-0.3169536888599396,0.9260838627815247,-0.07529972493648529,0.17076388001441956,-0.3939914107322693,0.8999668955802917,-0.08072671294212341,0.14747346937656403,-0.4690413177013397,0.8670265674591064,-0.08641133457422256,0.12442053854465485,-0.5334473848342896,0.8321577310562134,-0.09128005057573318,0.10377541929483414,-0.585608720779419,0.7987246513366699,-0.09397777169942856,0.08790639042854309,-0.625149667263031,0.7698237895965576,-0.08823328465223312,0.08279617130756378,-0.6269392967224121,0.7696147561073303,-0.08809422701597214,0.08696679770946503,-0.618959367275238,0.7756065726280212,-0.08853710442781448,0.09263543784618378,-0.6127362847328186,0.7798295617103577,-0.0885457843542099,0.09866398572921753,-0.6083324551582336,0.7825322151184082,-0.08818040788173676,0.10504509508609772,-0.6050816178321838,0.7842614054679871,-0.08751018345355988,0.11163435876369476,-0.6026729345321655,0.7852802872657776,-0.08661448955535889,0.11822861433029175,-0.6009727716445923,0.7857173085212708,-0.08557390421628952,0.12461905926465988,-0.5998772978782654,0.785680890083313,-0.0844704881310463,0.13060088455677032,-0.5992748141288757,0.785288393497467,-0.08338478952646255,0.13596102595329285,-0.5990675091743469,0.784652590751648,-0.08239720016717911,0.14048556983470917,-0.5991539359092712,0.7838935256004333,-0.08158756047487259,0.14393769204616547,-0.5994881987571716,0.7830960154533386,-0.08103626221418381,0.14607742428779602,-0.6000242233276367,0.7823461294174194,-0.08082569390535355,0.14668120443820953,-0.6006652116775513,0.7817628383636475],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999999403953552,1,0.9999998211860657,0.9999998807907104,0.9999999403953552,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,4.566666603088379],"values":[0.000003667768623927259,19.45049476623535,0.000010885413757932838,0.00001026666177494917,19.45050048828125,0.000005495849109138362],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[0.14937159419059753,-0.14748123288154602,-0.01920015551149845,0.9775319695472717,0.14937163889408112,-0.14748121798038483,-0.019200153648853302,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999998211860657,0.9999999403953552,0.9999997615814209,0.9999998211860657,0.9999999403953552,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,4.566666603088379],"values":[0.1468990445137024,6.409502983093262,0.2580995261669159,0.1468961536884308,6.409496307373047,0.2581040859222412],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[0.020259680226445198,0.012515293434262276,-0.011196218430995941,0.999653697013855,0.020259665325284004,0.012515261769294739,-0.011196215637028217,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999999403953552,1.000000238418579,1,0.9999999403953552,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,4.566666603088379],"values":[9.582731763657648e-7,6.815904140472412,0.000003613511807998293,-0.000004036352947878186,6.8159027099609375,0.0000029911766432633158],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284,3.3333332538604736,3.366666555404663,3.4000000953674316,3.433333396911621,3.4666666984558105,3.5,3.5333333015441895,3.566666603088379,3.5999999046325684,3.633333444595337,3.6666667461395264,3.700000047683716,3.7333333492279053,3.7666666507720947,3.799999952316284,3.8333332538604736,3.866666555404663,3.9000000953674316,3.933333396911621,3.9666666984558105,4,4.0333333015441895,4.066666603088379,4.099999904632568,4.133333206176758,4.166666507720947,4.199999809265137,4.233333110809326,4.266666889190674,4.300000190734863,4.333333492279053,4.366666793823242,4.400000095367432,4.433333396911621,4.4666666984558105,4.5,4.5333333015441895,4.566666603088379],"values":[0.06514779478311539,0.30207815766334534,-0.05072128772735596,0.9497009515762329,0.06968709826469421,0.3111051917076111,-0.052301548421382904,0.9463729858398438,0.07335133105516434,0.32341042160987854,-0.053619761019945145,0.9418865442276001,0.07736214250326157,0.33827242255210876,-0.05400500074028969,0.9363067746162415,0.0821552500128746,0.3528110980987549,-0.05339831858873367,0.9305500984191895,0.08791550248861313,0.3656057119369507,-0.05176929011940956,0.9251612424850464,0.09438657760620117,0.37858837842941284,-0.04996204376220703,0.9193834066390991,0.10158911347389221,0.39095577597618103,-0.04823332652449608,0.9135134220123291,0.10939840972423553,0.4023357629776001,-0.04684436693787575,0.9077243804931641,0.11781349033117294,0.41229578852653503,-0.04617412015795708,0.9022195339202881,0.12768065929412842,0.41647300124168396,-0.04624025896191597,0.8989492654800415,0.1377459466457367,0.41198012232780457,-0.045852180570364,0.8995532393455505,0.14823560416698456,0.40386494994163513,-0.04982322081923485,0.9013528823852539,0.15925589203834534,0.3987182676792145,-0.053174253553152084,0.9015730023384094,0.17017622292041779,0.3964550495147705,-0.056987058371305466,0.9003421664237976,0.1786089688539505,0.395590603351593,-0.05921159312129021,0.8989443182945251,0.1861237734556198,0.39501598477363586,-0.060396697372198105,0.8975926637649536,0.19153666496276855,0.39345404505729675,-0.06025008484721184,0.8971496820449829,0.19578592479228973,0.3911489248275757,-0.05937029421329498,0.89729905128479,0.19685745239257812,0.3874831199645996,-0.057232070714235306,0.8987928032875061,0.19736842811107635,0.38105323910713196,-0.053090762346982956,0.901679277420044,0.19487851858139038,0.3731386959552765,-0.048559028655290604,0.9057769775390625,0.1895962357521057,0.3601717948913574,-0.04116852954030037,0.9124881625175476,0.1835840493440628,0.33859947323799133,-0.03002643771469593,0.9223588109016418,0.17475458979606628,0.30987805128097534,-0.01637839339673519,0.934434711933136,0.15404172241687775,0.2654617130756378,0.0038393917493522167,0.9517281651496887,0.13753926753997803,0.20209082961082458,0.0258526261895895,0.9693162441253662,0.12293863296508789,0.1430118829011917,0.04719112068414688,0.9809213876724243,0.11743371188640594,0.08824261277914047,0.055664174258708954,0.9875850081443787,0.11486156284809113,0.03657633811235428,0.05931684002280235,0.9909341335296631,0.11029673367738724,-0.012911731377243996,0.05624552071094513,0.9922219514846802,0.10229704529047012,-0.05850865691900253,0.05224397033452988,0.9916565418243408,0.08178719878196716,-0.10192076861858368,0.04483044892549515,0.9904106855392456,0.05710447207093239,-0.1454647034406662,0.03140408545732498,0.9872147440910339,0.03290386125445366,-0.19231615960597992,0.01590506173670292,0.9806522727012634,0.03407324478030205,-0.23772349953651428,0.0034108704421669245,0.9707291126251221,0.047289662063121796,-0.2795568108558655,-0.0075111486949026585,0.958934485912323,0.06406793743371964,-0.3268866240978241,-0.018821019679307938,0.9427015781402588,0.07470066845417023,-0.3616929054260254,-0.03057803213596344,0.9287965893745422,0.07524912804365158,-0.38198694586753845,-0.038374509662389755,0.9202994108200073,0.07113710790872574,-0.3925275206565857,-0.0448312871158123,0.9158885478973389,0.060674071311950684,-0.4004283547401428,-0.047361548990011215,0.9130896329879761,0.04910677671432495,-0.408805251121521,-0.044804371893405914,0.9101974368095398,0.05052894353866577,-0.41717061400413513,-0.03243514895439148,0.9068425893783569,0.06319376081228256,-0.4187202751636505,-0.01422420609742403,0.9058021306991577,0.07681261748075485,-0.4142216742038727,0.006736618932336569,0.9069040417671204,0.09534371644258499,-0.40897008776664734,0.027456706389784813,0.907137930393219,0.11164695024490356,-0.4014396667480469,0.047914884984493256,0.9077914953231812,0.12198422849178314,-0.3953767418861389,0.06687209755182266,0.9079235792160034,0.12750279903411865,-0.3922310471534729,0.08415237814188004,0.9070922136306763,0.12520046532154083,-0.39033955335617065,0.09999193251132965,0.9066209197044373,0.12249166518449783,-0.38792264461517334,0.115139901638031,0.9062309861183167,0.11511731892824173,-0.385696679353714,0.1275470107793808,0.9064865708351135,0.1039002314209938,-0.3769626319408417,0.13554370403289795,0.9103471040725708,0.09807024896144867,-0.3642277121543884,0.1408601850271225,0.915357232093811,0.09463491290807724,-0.3488461375236511,0.14250247180461884,0.9214357137680054,0.09597423672676086,-0.32901856303215027,0.13506683707237244,0.929673433303833,0.11782588064670563,-0.2978346049785614,0.11424844712018967,0.9404035806655884,0.15790332853794098,-0.24573174118995667,0.08709190785884857,0.9524166584014893,0.21391969919204712,-0.1801658272743225,0.05818836763501167,0.958328127861023,0.2575702965259552,-0.1170835867524147,0.028711875900626183,0.9587098360061646,0.2706384062767029,-0.0595942884683609,-0.0011404462857171893,0.9608340859413147,0.2603309154510498,0.002002301160246134,-0.027371270582079887,0.9651293158531189,0.23655164241790771,0.06933807581663132,-0.04981817677617073,0.9678603410720825,0.2048925906419754,0.12203410267829895,-0.06576155871152878,0.9689180254936218,0.1740683615207672,0.15311673283576965,-0.06989219784736633,0.9702425599098206,0.15791897475719452,0.19219042360782623,-0.07380815595388412,0.9657519459724426,0.16473601758480072,0.22778475284576416,-0.07880758494138718,0.9564337730407715,0.16250750422477722,0.24804463982582092,-0.07783631980419159,0.9518438577651978,0.14866690337657928,0.26751071214675903,-0.06989377737045288,0.949447751045227,0.12844780087471008,0.2940511703491211,-0.05531821399927139,0.945502519607544,0.10488706082105637,0.3176157772541046,-0.03756044805049896,0.9416518211364746,0.08849593997001648,0.33552825450897217,-0.02236686646938324,0.9375974535942078,0.08263219892978668,0.350387841463089,-0.012779668904840946,0.9328649640083313,0.08495704084634781,0.36050301790237427,-0.0068198200315237045,0.9288559556007385,0.0914333313703537,0.3676801323890686,-0.006731367204338312,0.9254220724105835,0.10538274794816971,0.3744703531265259,-0.011202322319149971,0.9211628437042236,0.12216955423355103,0.37523049116134644,-0.014292825944721699,0.9187341332435608,0.1448337882757187,0.3708950877189636,-0.016618359833955765,0.9171607494354248,0.1653580665588379,0.3644934594631195,-0.01978451758623123,0.9161931276321411,0.1814967840909958,0.3587237000465393,-0.021752038970589638,0.915370523929596,0.19440072774887085,0.34906989336013794,-0.024238279089331627,0.9163902997970581,0.20128118991851807,0.326858252286911,-0.021653590723872185,0.9231363534927368,0.20670866966247559,0.2909497916698456,-0.015610853210091591,0.9340107440948486,0.20220863819122314,0.2459939867258072,-0.0049074310809373856,0.9479317665100098,0.19277842342853546,0.19487859308719635,0.008540615439414978,0.9616578817367554,0.19001367688179016,0.157842218875885,0.015137042850255966,0.9688919186592102,0.18156862258911133,0.1221618801355362,0.010875643230974674,0.975700318813324,0.17071250081062317,0.08594851940870285,0.004791052546352148,0.9815534949302673,0.15163567662239075,0.046742334961891174,-0.003902262542396784,0.9873229265213013,0.13473957777023315,-0.0020624001044780016,-0.008344796486198902,0.9908437728881836,0.12393227219581604,-0.051071785390377045,-0.010252664797008038,0.9909225106239319,0.11579429358243942,-0.0940844863653183,-0.013384814374148846,0.9887166619300842,0.11779821664094925,-0.13421128690242767,-0.014790776185691357,0.9838151335716248,0.12347623705863953,-0.17079712450504303,-0.00881241075694561,0.9774990081787109,0.13224871456623077,-0.2058200091123581,-0.0038522579707205296,0.9696048498153687,0.13141703605651855,-0.23604798316955566,-0.0027247231919318438,0.962810218334198,0.13018271327018738,-0.256271630525589,-0.002011143835261464,0.957796037197113,0.12743300199508667,-0.26169976592063904,-0.0024781343527138233,0.9566963911056519,0.12463895976543427,-0.2542972266674042,-0.004478096030652523,0.9590505957603455,0.12358112633228302,-0.23422731459140778,-0.007297883741557598,0.9642676115036011,0.12489014863967896,-0.20429874956607819,-0.011225518770515919,0.9708442091941833,0.1261853724718094,-0.16601420938968658,-0.015571119263768196,0.9778926968574524,0.12823215126991272,-0.11906438320875168,-0.02172386460006237,0.9843314290046692,0.12839387357234955,-0.04231734201312065,-0.03230183199048042,0.9902933239936829,0.12910296022891998,0.043740011751651764,-0.04369893670082092,0.9897018074989319,0.13188478350639343,0.1183779314160347,-0.05331640690565109,0.9827260375022888,0.13534694910049438,0.1647193282842636,-0.05766962096095085,0.9753066301345825,0.13636524975299835,0.19875454902648926,-0.05946097522974014,0.9686927199363708,0.13692812621593475,0.2264447659254074,-0.060748390853405,0.9624360203742981,0.1370408535003662,0.24935965240001678,-0.06181945279240608,0.9566702246665955,0.13667964935302734,0.266231894493103,-0.06270407140254974,0.9521068930625916,0.13561582565307617,0.2774667739868164,-0.06300197541713715,0.9490264654159546,0.13412944972515106,0.2852359414100647,-0.06324191391468048,0.9469161629676819,0.13294440507888794,0.292428195476532,-0.06313929706811905,0.9448941946029663,0.132033109664917,0.30008795857429504,-0.06257512420415878,0.9426552057266235,0.1313748061656952,0.3062928318977356,-0.062205974012613297,0.9407740235328674,0.13039764761924744,0.3113256096839905,-0.062109604477882385,0.9392631649971008,0.12811395525932312,0.3150181174278259,-0.06173566356301308,0.9383704662322998,0.1252845972776413,0.31715384125709534,-0.06126847490668297,0.9380636215209961,0.12186379730701447,0.31761911511421204,-0.06132727116346359,0.9383529424667358,0.1188468486070633,0.3164311945438385,-0.06172327324748039,0.9391149878501892,0.1154809519648552,0.3146807849407196,-0.062143828719854355,0.9400948286056519,0.1113353967666626,0.31358322501182556,-0.0606185682117939,0.9410607814788818,0.10683222860097885,0.31303471326828003,-0.0596255324780941,0.9418285489082336,0.10207483172416687,0.31263571977615356,-0.05865276977419853,0.942549467086792,0.09717263281345367,0.3119608759880066,-0.05718515068292618,0.9433810710906982,0.09225735813379288,0.31105977296829224,-0.05549939349293709,0.9442723393440247,0.0874912366271019,0.309946209192276,-0.05413742735981941,0.9451707601547241,0.08297418802976608,0.30868902802467346,-0.05310104787349701,0.9460479021072388,0.07877769321203232,0.30736666917800903,-0.052309270948171616,0.9468809366226196,0.07497553527355194,0.3060545325279236,-0.051687806844711304,0.9476485252380371,0.07171361148357391,0.3048206865787506,-0.05120016261935234,0.9483248591423035,0.06900389492511749,0.3037479817867279,-0.050825443118810654,0.9488900899887085,0.06694668531417847,0.3029029965400696,-0.050571128726005554,0.9493210911750793,0.06564662605524063,0.30235159397125244,-0.050445880740880966,0.949594259262085,0.06521730870008469,0.3022273778915405,-0.050466589629650116,0.9496622681617737],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,4.566666603088379],"values":[1,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,4.566666603088379],"values":[0.574600338935852,51.8169059753418,-20.522708892822266,0.5745993852615356,51.81687545776367,-20.52268409729004],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[-0.18743082880973816,9.309538739898926e-8,-0.005247782915830612,0.9822638034820557,-0.18743079900741577,1.0565823060915136e-7,-0.005247793160378933,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,4.566666603088379],"values":[1.0000001192092896,1.000000238418579,1,1.0000001192092896,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,4.566666603088379],"values":[-0.574600875377655,4.330305576324463,20.522705078125,-0.5746017098426819,4.3302764892578125,20.5227108001709],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[0.6296830773353577,-8.034307086290937e-8,0.017631016671657562,0.7766520380973816,0.6296830773353577,-7.794476886147095e-8,0.017631007358431816,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.9999999403953552,1.0000001192092896,1.000000238418579,0.9999999403953552,1.000000238418579,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,4.566666603088379],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,4.566666603088379],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,4.566666603088379],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"3e1439ec-5d89-40b3-8f14-9058ca909d8e","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/spear1.json b/public/anims/baked/sword_shield/spear1.json new file mode 100644 index 0000000..555ccd2 --- /dev/null +++ b/public/anims/baked/sword_shield/spear1.json @@ -0,0 +1 @@ +{"name":"sword_shield/spear1","duration":3.299999952316284,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.040549635887145996,56.49176025390625,0.727227509021759,0.011357009410858154,56.51219940185547,0.6860297918319702,0.001749277114868164,56.54761505126953,0.6443542838096619,0.01426011323928833,56.602630615234375,0.5977652072906494,0.031006038188934326,56.6756591796875,0.5349235534667969,0.021699845790863037,56.75470733642578,0.43788179755210876,-0.041183412075042725,56.82727813720703,0.2961636483669281,-0.17168092727661133,56.903526306152344,0.1333615481853485,-0.36539191007614136,57.0277214050293,0.015678465366363525,-0.6009453535079956,57.22987747192383,0.014510810375213623,-0.8560851216316223,57.48884582519531,0.14694857597351074,-1.1308505535125732,57.72561264038086,0.3563534617424011,-1.4401652812957764,57.86332702636719,0.5467568635940552,-1.8140931129455566,57.85260009765625,0.6344282627105713,-2.254669189453125,57.715579986572266,0.6196637749671936,-2.749760866165161,57.48522186279297,0.6155586242675781,-3.2793047428131104,57.203155517578125,0.7807296514511108,-3.7837283611297607,56.93638610839844,1.2193979024887085,-4.1569013595581055,56.740882873535156,1.9560885429382324,-4.296825885772705,56.55288314819336,2.981459856033325,-4.307775020599365,56.54364013671875,4.189612865447998,-4.122259140014648,56.619232177734375,5.579718112945557,-3.6763241291046143,56.6612663269043,7.16165018081665,-2.9381542205810547,56.547943115234375,8.965068817138672,-1.9917850494384766,56.18488311767578,10.96594524383545,-0.9435938596725464,55.511192321777344,13.105703353881836,0.18205158412456512,54.445430755615234,15.389090538024902,1.3329018354415894,52.99299621582031,17.721601486206055,2.4114551544189453,51.48050308227539,19.811439514160156,3.3698890209198,50.16883850097656,21.508939743041992,4.181649684906006,49.09092712402344,23.09535789489746,4.81049919128418,48.39202880859375,24.630619049072266,5.269073963165283,48.070899963378906,25.936786651611328,5.582822322845459,47.951637268066406,26.89752197265625,5.783109188079834,47.80974197387695,27.60472869873047,5.861030101776123,47.60355758666992,28.126251220703125,5.829342842102051,47.46092987060547,28.43995475769043,5.740592956542969,47.390411376953125,28.59687614440918,5.61266565322876,47.432159423828125,28.582157135009766,5.425947189331055,47.613624572753906,28.41946029663086,5.138298988342285,47.92042922973633,28.19501304626465,4.710483551025391,48.30422592163086,27.969636917114258,4.132552146911621,48.710350036621094,27.69797134399414,3.4314897060394287,49.08275604248047,27.26292610168457,2.655613899230957,49.38135528564453,26.56923484802246,1.8767234086990356,49.61587905883789,25.654247283935547,1.0445330142974854,49.96889114379883,24.413925170898438,0.07573653757572174,50.643985748291016,22.803668975830078,-0.9909941554069519,51.502681732177734,21.049901962280273,-1.9324898719787598,52.10293197631836,19.473180770874023,-2.8139960765838623,52.733863830566406,17.577449798583984,-3.8385229110717773,53.80576705932617,14.94336223602295,-4.788026809692383,54.85456085205078,12.307389259338379,-5.269086837768555,55.45197677612305,10.340156555175781,-5.547850608825684,56.00830078125,8.624833106994629,-5.6697163581848145,56.45848083496094,7.106977462768555,-5.61221981048584,56.80815124511719,5.7939372062683105,-5.3684163093566895,57.06879425048828,4.675581455230713,-4.9541778564453125,57.23579788208008,3.736985445022583,-4.392577648162842,57.28426742553711,2.9615159034729004,-3.7200427055358887,57.188690185546875,2.3253631591796875,-2.990748405456543,56.96024703979492,1.8142966032028198,-2.2635304927825928,56.681026458740234,1.4149775505065918,-1.5339457988739014,56.39591979980469,1.1309583187103271,-0.8148634433746338,56.1595458984375,0.9392592906951904,-0.13518834114074707,55.993526458740234,0.8180969953536987,0.46635937690734863,55.88974380493164,0.7507287859916687,0.9786227941513062,55.84228515625,0.7217694520950317,1.4095485210418701,55.81675338745117,0.7245665192604065,1.7454848289489746,55.820377349853516,0.7395901083946228,1.9934372901916504,55.84272384643555,0.756069004535675,2.169158458709717,55.881065368652344,0.7609549760818481,2.2855803966522217,55.92789840698242,0.7466442584991455,2.3453152179718018,55.969276428222656,0.7122252583503723,2.3472671508789062,55.996646881103516,0.6586189270019531,2.2923784255981445,56.01350021362305,0.5860067009925842,2.1829802989959717,56.02821731567383,0.4959627091884613,2.0237672328948975,56.04511642456055,0.39300093054771423,1.8280658721923828,56.06541442871094,0.2841808497905731,1.6097910404205322,56.08269500732422,0.1764582246541977,1.3855609893798828,56.098976135253906,0.08127012848854065,1.1674050092697144,56.12133026123047,0.008587703108787537,0.967322587966919,56.15656280517578,-0.03774687647819519,0.7978470325469971,56.20089340209961,-0.057382360100746155,0.6577085256576538,56.2554931640625,-0.05743876099586487,0.5462650060653687,56.30915832519531,-0.04050266742706299,0.4589879512786865,56.35475158691406,-0.00924578309059143,0.3899155855178833,56.39095687866211,0.03326144814491272,0.33490681648254395,56.41716003417969,0.08386150002479553,0.2900245189666748,56.43268585205078,0.13987389206886292,0.2504847049713135,56.43894577026367,0.19897052645683289,0.2132347822189331,56.439701080322266,0.25841161608695984,0.17952477931976318,56.440185546875,0.3172376751899719,0.15126144886016846,56.44379425048828,0.37701067328453064,0.126606285572052,56.450782775878906,0.4393899440765381,0.10215592384338379,56.46128845214844,0.505204439163208,0.07736384868621826,56.47441482543945,0.5748456716537476,0.05560302734375,56.48575973510742,0.6488777995109558,0.04054462909698486,56.49176025390625,0.7272301316261292],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.36085090041160583,-0.022732077166438103,0.19298070669174194,0.9121558666229248,0.3608500361442566,-0.022919628769159317,0.19293247163295746,0.9121617674827576,0.3604510426521301,-0.023802340030670166,0.19353294372558594,0.9121696949005127,0.35856127738952637,-0.026672298088669777,0.19524483382701874,0.9124702215194702,0.35450512170791626,-0.0326765812933445,0.19812384247779846,0.9132389426231384,0.34891167283058167,-0.041587188839912415,0.20186322927474976,0.9142113327980042,0.3434096574783325,-0.05184081941843033,0.20610778033733368,0.9148234724998474,0.33927375078201294,-0.062097519636154175,0.21075399219989777,0.9146693348884583,0.33667248487472534,-0.07213608920574188,0.2158769965171814,0.9136986136436462,0.3355742394924164,-0.0820157378911972,0.22139877080917358,0.9119462370872498,0.3368598520755768,-0.09149817377328873,0.22755642235279083,0.9090498685836792,0.3419381380081177,-0.10049868375062943,0.23529435694217682,0.9042205810546875,0.35114699602127075,-0.10998412221670151,0.2447042614221573,0.8970613479614258,0.36278775334358215,-0.12198902666568756,0.25421667098999023,0.888187825679779,0.3751699924468994,-0.13749361038208008,0.2624996304512024,0.8783148527145386,0.38796988129615784,-0.15425428748130798,0.2708423137664795,0.8673692941665649,0.3993680477142334,-0.16845299303531647,0.281599223613739,0.8560552597045898,0.4047950506210327,-0.17812754213809967,0.29468393325805664,0.8470967411994934,0.4017207622528076,-0.18296539783477783,0.3079181909561157,0.8428110480308533,0.39338192343711853,-0.1824733465909958,0.3193787634372711,0.842586100101471,0.38403576612472534,-0.17634885013103485,0.3282918334007263,0.8447734117507935,0.3743703365325928,-0.1674426943063736,0.3329166769981384,0.8491032719612122,0.36275893449783325,-0.16109612584114075,0.33226385712623596,0.855601966381073,0.3498222231864929,-0.16147835552692413,0.32797548174858093,0.8625434637069702,0.33906200528144836,-0.1680293083190918,0.323070764541626,0.867426335811615,0.33355340361595154,-0.17873942852020264,0.31756913661956787,0.8694505095481873,0.33272475004196167,-0.19475118815898895,0.3097625970840454,0.8691452145576477,0.3330659568309784,-0.21762041747570038,0.3012440502643585,0.8665797710418701,0.3330722749233246,-0.24307464063167572,0.29551997780799866,0.8617688417434692,0.33577221632003784,-0.26234644651412964,0.293099969625473,0.8558760285377502,0.3450414836406708,-0.2674011290073395,0.29211992025375366,0.8509459495544434,0.35938236117362976,-0.2574733793735504,0.29056817293167114,0.8486000299453735,0.371942937374115,-0.2423776090145111,0.2868537902832031,0.8488972187042236,0.3792632520198822,-0.23394757509231567,0.28095313906669617,0.849996030330658,0.38370683789253235,-0.23415543138980865,0.27446091175079346,0.8500655889511108,0.3879047930240631,-0.23708906769752502,0.269558846950531,0.8489150404930115,0.39192038774490356,-0.23853610455989838,0.2669866681098938,0.8474768996238708,0.3943469822406769,-0.23980793356895447,0.2653282880783081,0.8465125560760498,0.394487202167511,-0.24289311468601227,0.2633676826953888,0.8461798429489136,0.39332300424575806,-0.24693390727043152,0.2614387571811676,0.8461503982543945,0.3916285037994385,-0.2499241679906845,0.2604980766773224,0.8463485240936279,0.38840028643608093,-0.2508314549922943,0.2605780065059662,0.8475422859191895,0.38192102313041687,-0.25005850195884705,0.260346382856369,0.850780189037323,0.3711094856262207,-0.24998053908348083,0.2586548328399658,0.8560871481895447,0.35611996054649353,-0.25361168384552,0.2556970417499542,0.8622521162033081,0.3389105796813965,-0.2601742446422577,0.25321200489997864,0.8679473996162415,0.3233411908149719,-0.26224613189697266,0.25245389342308044,0.873466968536377,0.31367599964141846,-0.25118663907051086,0.25263065099716187,0.8801650404930115,0.31140318512916565,-0.22505292296409607,0.2504102885723114,0.8886359930038452,0.31331485509872437,-0.1894827038049698,0.24292141199111938,0.8982868790626526,0.3160612881183624,-0.15030674636363983,0.2337358146905899,0.9071277379989624,0.3191533088684082,-0.11220542341470718,0.23070953786373138,0.9123180508613586,0.32165610790252686,-0.0846104547381401,0.2323460876941681,0.9139987826347351,0.32465019822120667,-0.06923545151948929,0.23349258303642273,0.9139419794082642,0.3286396265029907,-0.059854503720998764,0.23371852934360504,0.9131205081939697,0.3331029713153839,-0.052142802625894547,0.23264311254024506,0.9122503995895386,0.337512731552124,-0.044429268687963486,0.23115767538547516,0.9114150404930115,0.34184935688972473,-0.03639497235417366,0.229827418923378,0.9104909896850586,0.3459325134754181,-0.027971530333161354,0.22817592322826385,0.9096614718437195,0.3494207561016083,-0.019666826352477074,0.22502733767032623,0.9093300104141235,0.35243967175483704,-0.01237376220524311,0.22051574289798737,0.9093986749649048,0.3557564318180084,-0.006444781087338924,0.21612589061260223,0.9092224836349487,0.3593222498893738,-0.0017328639514744282,0.21286682784557343,0.9086100459098816,0.3618670105934143,0.0012180054327473044,0.21000750362873077,0.9082663059234619,0.36233407258987427,0.0013234942452982068,0.20643454790115356,0.9088988304138184,0.36086127161979675,-0.001560425153002143,0.20201769471168518,0.910475492477417,0.358094722032547,-0.005759899038821459,0.19773441553115845,0.9124889969825745,0.354761004447937,-0.009133211337029934,0.19417771697044373,0.9145251512527466,0.35153648257255554,-0.010662482120096684,0.1916324645280838,0.9162889122962952,0.3489176034927368,-0.011096129193902016,0.19017145037651062,0.9175882935523987,0.34692248702049255,-0.011510469019412994,0.18960830569267273,0.9184557795524597,0.3452797830104828,-0.012267121113836765,0.18945622444152832,0.9190961718559265,0.34382370114326477,-0.013445715419948101,0.18936556577682495,0.9196441173553467,0.3425714075565338,-0.015321007929742336,0.1893480271100998,0.9200855493545532,0.3416809141635895,-0.01797150820493698,0.18954099714756012,0.9203289747238159,0.3414810299873352,-0.021057000383734703,0.18988455832004547,0.9202669262886047,0.3422674238681793,-0.02418704144656658,0.19009408354759216,0.919854462146759,0.3439813256263733,-0.027133788913488388,0.1898413449525833,0.9191849231719971,0.34614720940589905,-0.02963656187057495,0.1889704167842865,0.9184737205505371,0.3481777012348175,-0.03129275515675545,0.18769028782844543,0.9179136157035828,0.34984689950942993,-0.03195054084062576,0.18635843694210052,0.9175275564193726,0.3512641489505768,-0.03187061473727226,0.18522916734218597,0.917217493057251,0.3525231182575226,-0.03146534413099289,0.18442820012569427,0.9169098138809204,0.3536297380924225,-0.031051309779286385,0.18405070900917053,0.9165735840797424,0.3545973002910614,-0.03068440780043602,0.18412184715270996,0.9161977767944336,0.3554912507534027,-0.030149638652801514,0.184608593583107,0.9157710671424866,0.3564363420009613,-0.029400000348687172,0.18542784452438354,0.9152624607086182,0.3574819266796112,-0.02866123616695404,0.18641628324985504,0.914677083492279,0.35848087072372437,-0.028054332360625267,0.18734753131866455,0.9141145348548889,0.3592071831226349,-0.027488678693771362,0.18808570504188538,0.9136949181556702,0.3595592975616455,-0.02694401517510414,0.18870705366134644,0.913444459438324,0.35969725251197815,-0.026505757123231888,0.189381405711174,0.9132633805274963,0.35991325974464417,-0.026089660823345184,0.19013844430446625,0.9130329489707947,0.3602507710456848,-0.02546587958931923,0.19084888696670532,0.9127691984176636,0.36049506068229675,-0.024651089683175087,0.19141696393489838,0.9125761389732361,0.360568642616272,-0.023921141400933266,0.1918824017047882,0.9124687314033508,0.3606226146221161,-0.023408247157931328,0.19230055809020996,0.9123726487159729,0.36074116826057434,-0.02302705869078636,0.19267217814922333,0.9122570753097534,0.3608510196208954,-0.02273206226527691,0.19298072159290314,0.91215580701828],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,0.9999997615814209,0.9999997615814209,0.9999998807907104,0.9999998211860657,0.9999997615814209],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[8.305598258972168,-6.711595058441162,5.719298839569092,8.305602073669434,-6.711596488952637,5.719298362731934],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.7301109433174133,-0.069363072514534,-0.18277397751808167,0.6547675132751465,0.7303155064582825,-0.06947797536849976,-0.1835167407989502,0.6543192863464355,0.7309786081314087,-0.07014285773038864,-0.1852131336927414,0.653028666973114,0.7331252098083496,-0.07079587876796722,-0.18835188448429108,0.6496452689170837,0.7373349666595459,-0.07077046483755112,-0.1930210292339325,0.6434839963912964,0.7430030107498169,-0.06996564567089081,-0.19849924743175507,0.6353341341018677,0.7488440275192261,-0.06851039826869965,-0.2036636918783188,0.6269450187683105,0.7541892528533936,-0.06665495038032532,-0.2081621140241623,0.6192126274108887,0.7595499157905579,-0.06517898291349411,-0.21286050975322723,0.611167848110199,0.7653103470802307,-0.06465864926576614,-0.2184544950723648,0.6019942164421082,0.7708520293235779,-0.06479940563440323,-0.2238820344209671,0.5928449034690857,0.7746553421020508,-0.06500815600156784,-0.22720298171043396,0.5865678787231445,0.7750826478004456,-0.06513174623250961,-0.2287561148405075,0.5853849053382874,0.7710846662521362,-0.0650724470615387,-0.23254305124282837,0.5891669988632202,0.762945294380188,-0.06508569419384003,-0.2418881058692932,0.5959601402282715,0.7529825568199158,-0.06433051079511642,-0.25294986367225647,0.6040656566619873,0.7474136352539062,-0.059810150414705276,-0.25571197271347046,0.6102516055107117,0.7514284253120422,-0.052054859697818756,-0.24433742463588715,0.6106921434402466,0.7504719495773315,-0.0527857281267643,-0.22214674949645996,0.6202067136764526,0.7425743937492371,-0.054266344755887985,-0.20577126741409302,0.6350564360618591,0.7307798266410828,-0.05676848068833351,-0.2018248289823532,0.6496191620826721,0.7183393239974976,-0.06608683615922928,-0.2056979537010193,0.6612938642501831,0.7106127738952637,-0.07632660120725632,-0.21125489473342896,0.6667647361755371,0.7082856297492981,-0.07980265468358994,-0.216655895113945,0.6671006679534912,0.7088881731033325,-0.07468098402023315,-0.22226811945438385,0.6652047038078308,0.7128716707229614,-0.06351078301668167,-0.22796985507011414,0.660159170627594,0.7213985919952393,-0.05085047706961632,-0.23377324640750885,0.6498834490776062,0.7114927172660828,-0.034005552530288696,-0.24163933098316193,0.6589630246162415,0.6755619645118713,-0.019024614244699478,-0.2493055760860443,0.6936143636703491,0.6327697038650513,-0.005525622051209211,-0.26089853048324585,0.729043185710907,0.5904850959777832,0.0035147008020430803,-0.2691420018672943,0.7608400583267212,0.5567141175270081,0.004393918439745903,-0.2626185119152069,0.7880873084068298,0.5348841547966003,0.0001710927754174918,-0.24771475791931152,0.8077971935272217,0.5212127566337585,-0.004421456251293421,-0.2388233095407486,0.8193175196647644,0.5098333358764648,-0.004801868926733732,-0.23766613006591797,0.8267779350280762,0.4928479492664337,0.0008145179017446935,-0.23493154346942902,0.8377991318702698,0.4793921113014221,0.0047567118890583515,-0.22569496929645538,0.8480697870254517,0.47203388810157776,0.008439026772975922,-0.2146519273519516,0.8550072312355042,0.4713534414768219,0.012999912723898888,-0.20584434270858765,0.857487678527832,0.47664692997932434,0.018397439271211624,-0.19991399347782135,0.8558642864227295,0.48653775453567505,0.02304050885140896,-0.19693909585475922,0.8508614301681519,0.5006412267684937,0.025145916268229485,-0.1977200210094452,0.8423970937728882,0.5194326043128967,0.02468232996761799,-0.20123633742332458,0.8301111459732056,0.5436156988143921,0.025440193712711334,-0.20511797070503235,0.8134872317314148,0.5739787817001343,0.034424249082803726,-0.20945857465267181,0.7908795475959778,0.6079811453819275,0.046838272362947464,-0.2126486748456955,0.7635087966918945,0.6426860094070435,0.055037252604961395,-0.21635349094867706,0.732882559299469,0.6762847900390625,0.051761794835329056,-0.2240370362997055,0.6998336315155029,0.7071458101272583,0.033332470804452896,-0.23351594805717468,0.6665613651275635,0.7293065786361694,0.01074033509939909,-0.23565325140953064,0.6422337293624878,0.7390972375869751,-0.004032834433019161,-0.22334833443164825,0.6354798078536987,0.7550511956214905,-0.014177203178405762,-0.19812919199466705,0.624853253364563,0.7679494619369507,-0.014880554750561714,-0.17709387838840485,0.6153616309165955,0.7585297226905823,-0.014120158739387989,-0.17275607585906982,0.6281629204750061,0.7617001533508301,-0.022246140986680984,-0.18075166642665863,0.6218093633651733,0.7709826231002808,-0.02790030837059021,-0.1851896196603775,0.6086971163749695,0.7827982902526855,-0.024082988500595093,-0.17762432992458344,0.595899760723114,0.791465163230896,-0.01932748220860958,-0.1673620641231537,0.5875366926193237,0.794152021408081,-0.02707667648792267,-0.16867992281913757,0.5832122564315796,0.7918683290481567,-0.04314398020505905,-0.18334168195724487,0.5809208154678345,0.780025839805603,-0.060259860008955,-0.19777676463127136,0.5906037092208862,0.7661428451538086,-0.0697464793920517,-0.20376214385032654,0.605509340763092,0.7530503869056702,-0.07471967488527298,-0.20251329243183136,0.6215468645095825,0.74104243516922,-0.08056417107582092,-0.19948124885559082,0.636060357093811,0.7311973571777344,-0.08633556962013245,-0.19706566631793976,0.6473498940467834,0.7243192791938782,-0.08866886794567108,-0.19498313963413239,0.6553481221199036,0.7202692031860352,-0.08853833377361298,-0.19352643191814423,0.660243034362793,0.7183990478515625,-0.08866719156503677,-0.1928117573261261,0.6624686121940613,0.7177707552909851,-0.08999274671077728,-0.19198623299598694,0.6632102727890015,0.7181618213653564,-0.09159623831510544,-0.19073234498500824,0.6629290580749512,0.7191889882087708,-0.09271034598350525,-0.18919125199317932,0.6621017456054688,0.7206101417541504,-0.09338092058897018,-0.18751104176044464,0.6609393358230591,0.7221457958221436,-0.09370813518762589,-0.18599246442317963,0.6596447229385376,0.7235918045043945,-0.09312447160482407,-0.18495438992977142,0.6584334969520569,0.7247989773750305,-0.09127882868051529,-0.18448449671268463,0.6574954390525818,0.7254953980445862,-0.08860557526350021,-0.1845809668302536,0.657065749168396,0.725429356098175,-0.08550459891557693,-0.18520790338516235,0.6573731303215027,0.7246291041374207,-0.08187759667634964,-0.1862175315618515,0.658431351184845,0.723456621170044,-0.07773797959089279,-0.18736854195594788,0.6598942875862122,0.7223420739173889,-0.0735701248049736,-0.1883654147386551,0.6613077521324158,0.721577525138855,-0.06995595991611481,-0.18900594115257263,0.6623510122299194,0.7212387323379517,-0.06711936742067337,-0.18922679126262665,0.6629502177238464,0.7213476300239563,-0.06507468223571777,-0.189096137881279,0.6630728840827942,0.7217976450920105,-0.06375077366828918,-0.18883219361305237,0.6627869009971619,0.7225837707519531,-0.06292404234409332,-0.18860623240470886,0.6620733141899109,0.7234562039375305,-0.06268861889839172,-0.18841741979122162,0.6611960530281067,0.7241365909576416,-0.06324800848960876,-0.18845348060131073,0.6603872179985046,0.7245200276374817,-0.06426256895065308,-0.18883225321769714,0.6597602367401123,0.7247620820999146,-0.06502310931682587,-0.1892174780368805,0.6593092679977417,0.7250509858131409,-0.06538932770490646,-0.18924570083618164,0.658947229385376,0.7254958152770996,-0.06577802449464798,-0.1888534277677536,0.6585313677787781,0.7260800004005432,-0.06648527830839157,-0.18797507882118225,0.6580675840377808,0.7267081141471863,-0.06744910031557083,-0.1863621324300766,0.6577348709106445,0.7273841500282288,-0.0683043822646141,-0.18395206332206726,0.6575777530670166,0.7281723022460938,-0.06869440525770187,-0.181466743350029,0.6573553681373596,0.7289535403251648,-0.06876085698604584,-0.1801920086145401,0.6568329334259033,0.729505181312561,-0.06891286373138428,-0.18067219853401184,0.6560722589492798,0.7298216223716736,-0.06917893886566162,-0.18199414014816284,0.6553265452384949,0.7301105856895447,-0.06936299800872803,-0.1827739179134369,0.6547679901123047],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1,0.9999992251396179,0.9999998807907104,1.0000001192092896,0.9999990463256836],"interpolation":2300,"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.000003437824489083141,25.812711715698242,-0.0000039608453334949445,-0.000002000968834181549,25.812707901000977,3.7117303008926683e-7],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.4491277039051056,0.06208262965083122,-0.0323302298784256,0.8907215595245361,0.4478130042552948,0.06133296713232994,-0.0331554114818573,0.8914048075675964,0.44640517234802246,0.06028146669268608,-0.03362226113677025,0.8921648859977722,0.44511958956718445,0.05824101343750954,-0.03375288099050522,0.8929374814033508,0.4440245032310486,0.0548088476061821,-0.034462910145521164,0.893672525882721,0.44270411133766174,0.05007682740688324,-0.03675331920385361,0.8945136070251465,0.4404507279396057,0.04426462575793266,-0.04008039832115173,0.8957886695861816,0.4366312325000763,0.03946062922477722,-0.04210919514298439,0.8977877497673035,0.430209755897522,0.035553861409425735,-0.04274702072143555,0.9010151028633118,0.42073729634284973,0.03458815813064575,-0.04140933230519295,0.9055766463279724,0.4090507924556732,0.03427370265126228,-0.03802947700023651,0.9110743999481201,0.3971216380596161,0.03326164558529854,-0.03175675496459007,0.9166131019592285,0.3872668147087097,0.03230362758040428,-0.02469419315457344,0.9210706353187561,0.38211363554000854,0.03321903198957443,-0.02246725745499134,0.9232447743415833,0.3819214105606079,0.0366029292345047,-0.028019443154335022,0.9230445623397827,0.3852420151233673,0.03999718278646469,-0.03535618260502815,0.9212701916694641,0.39051106572151184,0.03901504725217819,-0.03331605717539787,0.9191675782203674,0.39846906065940857,0.031266141682863235,-0.020672103390097618,0.9164155721664429,0.4382539391517639,0.01726360432803631,-0.01020606979727745,0.8986274600028992,0.4879852831363678,-0.0033860995899885893,-0.0035666441544890404,0.872838020324707,0.52053302526474,-0.011527448892593384,0.003458433784544468,0.8537567257881165,0.5299220681190491,-0.008760153315961361,0.00029719778103753924,0.8480010628700256,0.517093300819397,0.001033272361382842,-0.012011121027171612,0.8558441400527954,0.48832806944847107,0.00869706179946661,-0.02321162447333336,0.8723080158233643,0.44223353266716003,0.011409706436097622,-0.021404866129159927,0.8965718746185303,0.36822155117988586,0.014148563146591187,-0.008022328838706017,0.9295958280563354,0.26602625846862793,0.018388571217656136,-0.0037641944363713264,0.9637830257415771,0.21507391333580017,0.0276123508810997,-0.021320121362805367,0.9759745001792908,0.26649364829063416,0.0455278605222702,-0.047089990228414536,0.9616084694862366,0.3544539511203766,0.05026588216423988,-0.06308354437351227,0.9315879940986633,0.43541285395622253,0.04732360690832138,-0.0604051873087883,0.8969544768333435,0.49052003026008606,0.04261050745844841,-0.04999305307865143,0.868950605392456,0.5243473649024963,0.036853693425655365,-0.04246703162789345,0.8496459126472473,0.5492082834243774,0.03501266986131668,-0.04279104247689247,0.8338544964790344,0.5736553072929382,0.03510890156030655,-0.04846636578440666,0.8169075846672058,0.598004937171936,0.0356459878385067,-0.054006725549697876,0.7988759279251099,0.6156325936317444,0.03470961004495621,-0.0583234541118145,0.7851051688194275,0.6258606314659119,0.03467601165175438,-0.061233505606651306,0.776753842830658,0.6297523379325867,0.03486120328307152,-0.0642971321940422,0.7733450531959534,0.6274685859680176,0.03457283601164818,-0.06692647188901901,0.7749895453453064,0.6198163628578186,0.03470804914832115,-0.0674523189663887,0.7810718417167664,0.6082061529159546,0.03509167209267616,-0.06678170710802078,0.7901861071586609,0.5932970643043518,0.03619975224137306,-0.06708645075559616,0.8013660907745361,0.573930025100708,0.040420129895210266,-0.06966976076364517,0.8149335384368896,0.5458651185035706,0.0503484308719635,-0.07186656445264816,0.8332656025886536,0.5113511681556702,0.0561060830950737,-0.0764489695429802,0.8541239500045776,0.4619213938713074,0.05539386719465256,-0.07839567959308624,0.8817110061645508,0.3825284540653229,0.04692089185118675,-0.07018963992595673,0.9200782179832458,0.2727124094963074,0.033653393387794495,-0.05454249680042267,0.9599586129188538,0.18363866209983826,0.025703519582748413,-0.03539235144853592,0.9820201396942139,0.1657085120677948,0.022904766723513603,-0.016970675438642502,0.9857627153396606,0.1698765903711319,0.019733641296625137,-0.004517798312008381,0.9852573871612549,0.20847836136817932,0.01996968872845173,0.005947363097220659,0.9778050184249878,0.29378366470336914,0.055674780160188675,0.015580369159579277,0.954122006893158,0.31320786476135254,0.08347423374652863,0.011690854094922543,0.9459366798400879,0.30761197209358215,0.09127186983823776,0.00845193862915039,0.94708651304245,0.3037356734275818,0.07509544491767883,0.014848623424768448,0.9496762156486511,0.31235814094543457,0.05322429537773132,0.025415882468223572,0.9481316208839417,0.3270249664783478,0.04861151799559593,0.0268317349255085,0.9433830976486206,0.33469662070274353,0.06420872360467911,0.018458444625139236,0.9399546384811401,0.35690757632255554,0.08175092935562134,0.009053551591932774,0.9305115938186646,0.3793201446533203,0.09006883949041367,0.007066664285957813,0.9208441376686096,0.39912477135658264,0.09065203368663788,0.00865897536277771,0.9123632311820984,0.4185691773891449,0.09059594571590424,0.005804792046546936,0.9036363363265991,0.4370932877063751,0.0909409150481224,-0.0019831156823784113,0.8948045969009399,0.4535684585571289,0.08945168554782867,-0.009966040961444378,0.8866649866104126,0.4670133590698242,0.08683545142412186,-0.01586293615400791,0.8798332214355469,0.47692635655403137,0.08495563268661499,-0.019949767738580704,0.8746004104614258,0.4843940734863281,0.08386179059743881,-0.02263147570192814,0.8705270886421204,0.4891126751899719,0.08250505477190018,-0.023992804810404778,0.8679781556129456,0.491822212934494,0.08033747225999832,-0.02473476156592369,0.8666285276412964,0.4929736852645874,0.07774018496274948,-0.02591804973781109,0.8661764860153198,0.4930832087993622,0.07533612847328186,-0.02787361480295658,0.8662658333778381,0.4927770495414734,0.07303887605667114,-0.0298173651099205,0.8665720224380493,0.49232685565948486,0.07049199193716049,-0.031118255108594894,0.8669929504394531,0.49150270223617554,0.06790241599082947,-0.03202057629823685,0.8676341772079468,0.490019828081131,0.06574492901563644,-0.03288261964917183,0.8686062693595886,0.4878912568092346,0.06428217887878418,-0.033651065081357956,0.86988365650177,0.4852541983127594,0.06364744901657104,-0.034165702760219574,0.8713839650154114,0.48229649662971497,0.06385714560747147,-0.03440576791763306,0.8729997873306274,0.4790342152118683,0.06460706144571304,-0.03446707874536514,0.8747366666793823,0.4754540026187897,0.0654081478714943,-0.03455538675189018,0.8766248822212219,0.4715844988822937,0.06595316529273987,-0.034833427518606186,0.8786608576774597,0.4677894711494446,0.06623184680938721,-0.035142913460731506,0.8806539177894592,0.463968425989151,0.06625862419605255,-0.03499525040388107,0.8826768398284912,0.46041804552078247,0.06617559492588043,-0.03437485545873642,0.8845645785331726,0.45729225873947144,0.06630996614694595,-0.0337064154446125,0.8862001299858093,0.45474541187286377,0.06662099063396454,-0.03302399069070816,0.8875120878219604,0.4529205560684204,0.06671424955129623,-0.032107703387737274,0.8884713053703308,0.45183491706848145,0.06647886335849762,-0.03120163269340992,0.8890738487243652,0.45127883553504944,0.06611818075180054,-0.031004847958683968,0.8893899321556091,0.4509403705596924,0.06559433788061142,-0.03195104002952576,0.889566957950592,0.45052850246429443,0.06463523209095001,-0.03385369852185249,0.8897753953933716,0.4499754309654236,0.06312345713376999,-0.035875264555215836,0.8900845646858215,0.4494374096393585,0.061455126851797104,-0.036957282572984695,0.8904288411140442,0.44903185963630676,0.06051069498062134,-0.036621883511543274,0.8907119035720825,0.4487662613391876,0.06079413741827011,-0.0352209098637104,0.8908829689025879,0.4487534463405609,0.06167712062597275,-0.03349580615758896,0.8908952474594116,0.4491284191608429,0.062082689255476,-0.03233043849468231,0.8907211422920227],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1.000000238418579,1,1,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000014016276281836326,22.742708206176758,-2.4630890038679354e-7,0.0000012094089925085427,22.742706298828125,-0.0000028597780783456983],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.5238346457481384,-0.08466523885726929,0.12221039086580276,0.8387452960014343,-0.5226155519485474,-0.08495199680328369,0.12251409143209457,0.8394322395324707,-0.5216612815856934,-0.0849640816450119,0.12227590382099152,0.8400591015815735,-0.5214133858680725,-0.08484933525323868,0.12177887558937073,0.8402966856956482,-0.521998405456543,-0.0851106196641922,0.12199101597070694,0.8398762345314026,-0.5227836966514587,-0.0862324982881546,0.12381868809461594,0.8390054702758789,-0.522770881652832,-0.08827745914459229,0.12704473733901978,0.8383181691169739,-0.5217345952987671,-0.09265584498643875,0.129599928855896,0.8381001353263855,-0.5197641849517822,-0.09822282940149307,0.13130609691143036,0.838424801826477,-0.5175095200538635,-0.10573197901248932,0.13125687837600708,0.8389137387275696,-0.5154153108596802,-0.11357555538415909,0.13005521893501282,0.8393648266792297,-0.5137946605682373,-0.12299581617116928,0.1273297816514969,0.8394487500190735,-0.5126740336418152,-0.13607680797576904,0.12387404590845108,0.8386319875717163,-0.512069582939148,-0.15232537686824799,0.1232825368642807,0.8362913131713867,-0.5117911696434021,-0.16882802546024323,0.12799863517284393,0.8325762748718262,-0.512881338596344,-0.18429753184318542,0.13521380722522736,0.8274686932563782,-0.5179660320281982,-0.20144371688365936,0.13878165185451508,0.8196775913238525,-0.5291391611099243,-0.22078868746757507,0.13393335044384003,0.8082858324050903,-0.5549181699752808,-0.22222831845283508,0.08353334665298462,0.7973095774650574,-0.5535776615142822,-0.2064211517572403,0.04821820184588432,0.805367648601532,-0.5202516317367554,-0.18196897208690643,0.038641564548015594,0.8335061073303223,-0.4766556918621063,-0.15181216597557068,0.041926100850105286,0.8648668527603149,-0.4384182393550873,-0.12759332358837128,0.054031383246183395,0.8880259394645691,-0.41438421607017517,-0.10838104039430618,0.0662083551287651,0.9011968374252319,-0.40083035826683044,-0.0918753519654274,0.07074274122714996,0.9087846279144287,-0.38063958287239075,-0.0866846963763237,0.0746455118060112,0.9176204800605774,-0.3346259295940399,-0.10687872767448425,0.09714656323194504,0.9312169551849365,-0.2786272168159485,-0.14196330308914185,0.13364434242248535,0.9404001235961914,-0.2774292826652527,-0.15576182305812836,0.14788852632045746,0.936429500579834,-0.3152068555355072,-0.1478288769721985,0.1616075038909912,0.9234036803245544,-0.35852354764938354,-0.12872081995010376,0.16230598092079163,0.9102464318275452,-0.39174699783325195,-0.1158822774887085,0.1526327133178711,0.8998938202857971,-0.4143926203250885,-0.10969788581132889,0.14098186790943146,0.8923952579498291,-0.4326086938381195,-0.10708453506231308,0.132889062166214,0.8852813839912415,-0.45075950026512146,-0.10565038025379181,0.1298118233680725,0.8768140077590942,-0.44918254017829895,-0.1047677993774414,0.12430491298437119,0.8785254955291748,-0.4504856467247009,-0.1044308990240097,0.10773590952157974,0.8800851106643677,-0.4521093964576721,-0.10801607370376587,0.09167978167533875,0.8806386590003967,-0.45426246523857117,-0.11320321261882782,0.07960774749517441,0.8800530433654785,-0.4558883011341095,-0.11934700608253479,0.07303845882415771,0.8789696097373962,-0.4566299617290497,-0.12581965327262878,0.07130994647741318,0.8778231143951416,-0.45767295360565186,-0.12913884222507477,0.07369324564933777,0.8766002058982849,-0.4595991373062134,-0.1289772242307663,0.078789122402668,0.8751718401908875,-0.45840510725975037,-0.1325952410697937,0.09018350392580032,0.8741568922996521,-0.4433729648590088,-0.15074776113033295,0.119880311191082,0.8753994703292847,-0.423378050327301,-0.17119188606739044,0.1534433662891388,0.8762987852096558,-0.39438727498054504,-0.1819361448287964,0.1803649514913559,0.8825114369392395,-0.34804272651672363,-0.17296022176742554,0.19093464314937592,0.9013850688934326,-0.2824779748916626,-0.14684288203716278,0.17838050425052643,0.931033730506897,-0.22892725467681885,-0.12859466671943665,0.15351766347885132,0.9526216983795166,-0.224049374461174,-0.12085802853107452,0.1290600448846817,0.9584042429924011,-0.24996760487556458,-0.1144518032670021,0.12514686584472656,0.9532865881919861,-0.3074013292789459,-0.10197902470827103,0.13825294375419617,0.9359437823295593,-0.3634622097015381,-0.10124627500772476,0.1354459673166275,0.9161325693130493,-0.37968727946281433,-0.1046648845076561,0.1446324735879898,0.9077247977256775,-0.3966236114501953,-0.10526779294013977,0.14712439477443695,0.8999793529510498,-0.4220750331878662,-0.10179668664932251,0.14157508313655853,0.8896328210830688,-0.45115914940834045,-0.09614414721727371,0.13273566961288452,0.8772644996643066,-0.47744324803352356,-0.09152870625257492,0.12703748047351837,0.8645992875099182,-0.4857284724712372,-0.09512511640787125,0.1381288766860962,0.8578691482543945,-0.4962875247001648,-0.09157738834619522,0.13363762199878693,0.852908730506897,-0.5033740401268005,-0.08697964996099472,0.12531216442584991,0.8504974842071533,-0.5082426071166992,-0.08220583945512772,0.11697269976139069,0.8492638468742371,-0.5139297842979431,-0.07887528091669083,0.11126784980297089,0.8469205498695374,-0.5204187035560608,-0.07706128805875778,0.10831550508737564,0.8435008525848389,-0.5268874168395996,-0.0758848786354065,0.10648811608552933,0.8398163318634033,-0.5326810479164124,-0.07471301406621933,0.10467430204153061,0.8364880084991455,-0.5374475717544556,-0.07329676300287247,0.10242097824811935,0.8338391184806824,-0.541560173034668,-0.07172220945358276,0.09978418797254562,0.8316318988800049,-0.5446014404296875,-0.07025616616010666,0.09730330854654312,0.83006352186203,-0.5467279553413391,-0.0692174956202507,0.0955558568239212,0.8289551138877869,-0.5479670166969299,-0.06879576295614243,0.09488078951835632,0.8282493352890015,-0.5484870076179504,-0.06894110143184662,0.09522221982479095,0.8278537392616272,-0.54860520362854,-0.0694919303059578,0.09625830501317978,0.8276095390319824,-0.5484297871589661,-0.0703079029917717,0.09776739776134491,0.8274800181388855,-0.5477392077445984,-0.07138440012931824,0.09969233721494675,0.8276155591011047,-0.5463092923164368,-0.07271558046340942,0.10199037194252014,0.8281645774841309,-0.5441663265228271,-0.07421755790710449,0.10455452650785446,0.8291219472885132,-0.5414980053901672,-0.07577158510684967,0.1071862131357193,0.8303913474082947,-0.5385335087776184,-0.07727212458848953,0.10971187055110931,0.8318497538566589,-0.5354601740837097,-0.07863841950893402,0.11199067533016205,0.8334006071090698,-0.5324179530143738,-0.07988856732845306,0.11405174434185028,0.834949791431427,-0.5295361876487732,-0.08104807138442993,0.1159428283572197,0.8364089727401733,-0.527081310749054,-0.08205408602952957,0.11754585802555084,0.8376367688179016,-0.5249695777893066,-0.08277515321969986,0.11865779757499695,0.83873450756073,-0.5232463479042053,-0.08318068087100983,0.11922772228717804,0.8396898508071899,-0.521887481212616,-0.08337316662073135,0.11944044381380081,0.8404858112335205,-0.5209235548973083,-0.08340680599212646,0.11939648538827896,0.8410864472389221,-0.5203909873962402,-0.08332843333482742,0.11920040845870972,0.8414516448974609,-0.520263671875,-0.0832512229681015,0.11904840916395187,0.8415595293045044,-0.520439863204956,-0.08335366100072861,0.1192915067076683,0.841405987739563,-0.5207571387290955,-0.08379919081926346,0.12016799300909042,0.8410406708717346,-0.5210222601890564,-0.08454511314630508,0.12161212414503098,0.8405941128730774,-0.5211679935455322,-0.08535117655992508,0.12317826598882675,0.8401941657066345,-0.5213314294815063,-0.08589277416467667,0.12425606697797775,0.8398788571357727,-0.5216709971427917,-0.08595879375934601,0.124446339905262,0.839633047580719,-0.522205114364624,-0.08558432757854462,0.12381376326084137,0.8394327759742737,-0.5229235887527466,-0.08503216505050659,0.1228458359837532,0.8391837477684021,-0.5238351225852966,-0.08466535061597824,0.12221061438322067,0.8387449383735657],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998211860657,0.9999999403953552,0.9999998807907104,0.9999999403953552,0.9999998807907104,0.9999998807907104],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,3.299999952316284],"values":[-5.7776631479100615e-8,13.415114402770996,-0.000004460066065803403,-2.2029749402463494e-7,13.415112495422363,-0.000001364529680358828],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.30698150396347046,-0.005662639159709215,-0.0018292088061571121,0.9516968727111816,-0.30698150396347046,-0.0056626442819833755,-0.001829210901632905,0.9516968727111816],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1.0000001192092896,1.0000001192092896,0.9999999403953552,0.9999999403953552,1],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[-11.139497756958008,-4.020097255706787,0.420299768447876,-11.139495849609375,-4.020099639892578,0.4202965497970581],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.6909880042076111,-0.3413213789463043,0.2998862564563751,0.5622308850288391,0.6916221380233765,-0.34167036414146423,0.29826831817626953,0.562099814414978,0.6920906901359558,-0.3428780734539032,0.2962988615036011,0.5618292689323425,0.6925786137580872,-0.344806969165802,0.29326286911964417,0.5616404414176941,0.6936606764793396,-0.3471684753894806,0.2876352071762085,0.5617605447769165,0.695008397102356,-0.3492090106010437,0.27943840622901917,0.562965989112854,0.6953324675559998,-0.34998077154159546,0.27076879143714905,0.5663130879402161,0.6939190626144409,-0.34951886534690857,0.2634630799293518,0.5717518329620361,0.6916484832763672,-0.348789244890213,0.2577589750289917,0.5775195956230164,0.6899592280387878,-0.34855949878692627,0.25308099389076233,0.5817323923110962,0.6886026859283447,-0.34890830516815186,0.2487465888261795,0.5849910974502563,0.6849830746650696,-0.3492387533187866,0.24396173655986786,0.5910273194313049,0.6772083640098572,-0.3474058508872986,0.23774327337741852,0.6034701466560364,0.6660479307174683,-0.3407655954360962,0.2288544923067093,0.6228038668632507,0.6535795331001282,-0.32894960045814514,0.2171720415353775,0.6461132168769836,0.6396028399467468,-0.3171248435974121,0.20404298603534698,0.6698556542396545,0.6240151524543762,-0.3131106495857239,0.19115880131721497,0.6899458765983582,0.6107867956161499,-0.319352924823761,0.1798870712518692,0.701850414276123,0.6046114563941956,-0.33314579725265503,0.1714051365852356,0.7029076814651489,0.6045697331428528,-0.35264852643013,0.16740413010120392,0.694341778755188,0.6103600859642029,-0.37389928102493286,0.17150931060314178,0.6769375801086426,0.6216240525245667,-0.39360499382019043,0.18316276371479034,0.6520046591758728,0.6394752264022827,-0.4115358293056488,0.1972217708826065,0.6187190413475037,0.6608600616455078,-0.43032535910606384,0.21125179529190063,0.577457070350647,0.6773599982261658,-0.44659358263015747,0.22490528225898743,0.5395879745483398,0.6860449314117432,-0.45638683438301086,0.23941640555858612,0.5135496258735657,0.6904815435409546,-0.4609791040420532,0.25076186656951904,0.4978473484516144,0.6922646760940552,-0.46641287207603455,0.2564505338668823,0.4873005449771881,0.6904409527778625,-0.4710434079170227,0.260547935962677,0.4832434356212616,0.6830933690071106,-0.47305071353912354,0.2695498764514923,0.4867743253707886,0.6741453409194946,-0.47398310899734497,0.2824600636959076,0.49100345373153687,0.6717450022697449,-0.47626468539237976,0.29293352365493774,0.48592230677604675,0.6798292398452759,-0.47843125462532043,0.29737624526023865,0.4695776104927063,0.6899083256721497,-0.47675034403800964,0.29392072558403015,0.4586351215839386,0.6980398297309875,-0.4708775281906128,0.2947227358818054,0.45183324813842773,0.7026766538619995,-0.4645492136478424,0.2998010218143463,0.44783806800842285,0.7052867412567139,-0.4597402513027191,0.3054147958755493,0.4448946714401245,0.7069728374481201,-0.4557037055492401,0.31053072214126587,0.4428252875804901,0.7081468105316162,-0.4504701793193817,0.3150843679904938,0.44308751821517944,0.7091362476348877,-0.444129079580307,0.3181222677230835,0.4457279145717621,0.7098335027694702,-0.4383253753185272,0.3188636302947998,0.44981470704078674,0.7088242769241333,-0.4335091710090637,0.3198019564151764,0.4553731381893158,0.7058653235435486,-0.4295881986618042,0.32165977358818054,0.4623236060142517,0.7040265798568726,-0.42809849977493286,0.3183366060256958,0.4687645137310028,0.7057973742485046,-0.4289957284927368,0.3049826920032501,0.4741290211677551,0.7086246013641357,-0.43078893423080444,0.28854453563690186,0.4785541892051697,0.7102265357971191,-0.43162867426872253,0.2777283489704132,0.4818111062049866,0.7149002552032471,-0.42934998869895935,0.27359288930892944,0.47929447889328003,0.7259649634361267,-0.42216724157333374,0.2714652121067047,0.47016629576683044,0.7410063147544861,-0.41167816519737244,0.27149373292922974,0.45576515793800354,0.7523636817932129,-0.4033454358577728,0.2731289863586426,0.44346582889556885,0.7570346593856812,-0.3927469849586487,0.27383309602737427,0.44459396600723267,0.7529582381248474,-0.38217607140541077,0.27835705876350403,0.4577255845069885,0.7427944540977478,-0.3704774081707001,0.28662699460983276,0.4783805310726166,0.7334851026535034,-0.35618653893470764,0.29180654883384705,0.4999796748161316,0.729046642780304,-0.34585079550743103,0.28729110956192017,0.5160834193229675,0.7282921671867371,-0.34135234355926514,0.27529993653297424,0.5265730023384094,0.7274585962295532,-0.33966273069381714,0.2648317515850067,0.5341323614120483,0.7235975861549377,-0.3400229811668396,0.2621667981147766,0.5404253602027893,0.7172693014144897,-0.34082767367362976,0.26761001348495483,0.545661211013794,0.7099553942680359,-0.34096309542655945,0.27635788917541504,0.5507575869560242,0.7026075720787048,-0.3424483835697174,0.2825707793235779,0.5560805201530457,0.6957558989524841,-0.34716176986694336,0.285158634185791,0.5604346990585327,0.6897895336151123,-0.35323163866996765,0.2872711420059204,0.5629326105117798,0.6859895586967468,-0.35714855790138245,0.2908617854118347,0.5632607936859131,0.6852301955223083,-0.35816073417663574,0.2941220700740814,0.5618475675582886,0.6867060661315918,-0.35850632190704346,0.29601621627807617,0.5588224530220032,0.6892509460449219,-0.3588615953922272,0.2980615198612213,0.5543562173843384,0.6917152404785156,-0.35973361134529114,0.3006178140640259,0.5493183732032776,0.6941624879837036,-0.36151230335235596,0.302120566368103,0.5442155599594116,0.6965579986572266,-0.3639214336872101,0.3018137812614441,0.5397005677223206,0.6985306143760681,-0.36554476618766785,0.301251620054245,0.5363576412200928,0.6996263861656189,-0.36557430028915405,0.3018108904361725,0.5345919728279114,0.6996606588363647,-0.36459243297576904,0.3029659390449524,0.5345643162727356,0.6986519694328308,-0.36334991455078125,0.30366840958595276,0.5363280773162842,0.6967618465423584,-0.3615091145038605,0.30388087034225464,0.5398986339569092,0.6944845914840698,-0.3584239184856415,0.3038271367549896,0.5448967814445496,0.6925084590911865,-0.3543475568294525,0.3032247722148895,0.5503858923912048,0.691221296787262,-0.35009443759918213,0.3023107945919037,0.555207371711731,0.6902757883071899,-0.34594592452049255,0.30169394612312317,0.5593045949935913,0.6895919442176819,-0.34228354692459106,0.30185019969940186,0.5623089671134949,0.6892443895339966,-0.3392828404903412,0.30216866731643677,0.5643787980079651,0.6893231272697449,-0.33700066804885864,0.30220359563827515,0.5656299591064453,0.6896295547485352,-0.3354424834251404,0.3019389510154724,0.566323459148407,0.689913809299469,-0.33450520038604736,0.30169838666915894,0.5666598677635193,0.689850926399231,-0.3342147767543793,0.30186405777931213,0.5668194890022278,0.6893890500068665,-0.3343867063522339,0.30237331986427307,0.5670086145401001,0.6887734532356262,-0.3347768187522888,0.3027985095977783,0.5672994256019592,0.6882477402687073,-0.33526894450187683,0.3030063211917877,0.567535936832428,0.6879156827926636,-0.33579570055007935,0.3031716048717499,0.5675389170646667,0.6878277659416199,-0.3362272083759308,0.30320796370506287,0.5673704147338867,0.6880516409873962,-0.3364970088005066,0.3026233911514282,0.5672511458396912,0.6886491775512695,-0.33674129843711853,0.3011414110660553,0.5671697854995728,0.689556360244751,-0.33723029494285583,0.2992374897003174,0.5667845606803894,0.6904728412628174,-0.3381209969520569,0.2977961301803589,0.5658965706825256,0.6909807324409485,-0.33920228481292725,0.2975096106529236,0.5647791624069214,0.6909642219543457,-0.34009605646133423,0.29834941029548645,0.563818097114563,0.6908127665519714,-0.3407421410083771,0.29943495988845825,0.5630375146865845,0.6909875869750977,-0.3413213789463043,0.29988613724708557,0.5622314810752869],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999998807907104,1.0000001192092896,1,1.0000001192092896,1.0000003576278687],"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000013492330026565469,25.527099609375,0.0000014513161659124307,-0.0000018184631471740431,25.527103424072266,-7.663825272175018e-7],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.4410494565963745,-0.030267326161265373,0.0256109070032835,0.8966065645217896,0.4410233795642853,-0.03011947311460972,0.02438487857580185,0.8966585397720337,0.4401451647281647,-0.029803061857819557,0.02257603593170643,0.8971478939056396,0.43832823634147644,-0.029624130576848984,0.021114489063620567,0.8980785012245178,0.4359441101551056,-0.029702238738536835,0.020408224314451218,0.8992519378662109,0.43387243151664734,-0.030065828934311867,0.020659437403082848,0.9002355337142944,0.4329337775707245,-0.030698487535119057,0.022013669833540916,0.9006338715553284,0.432822585105896,-0.03187338635325432,0.023111337795853615,0.9006190299987793,0.4312136173248291,-0.03904322534799576,0.01939479634165764,0.9011960029602051,0.4263111352920532,-0.0452510267496109,0.013089661486446857,0.9033492207527161,0.41819584369659424,-0.04945753514766693,0.005812964867800474,0.9069908857345581,0.40999162197113037,-0.052548110485076904,0.000005800218787044287,0.9105743169784546,0.40494510531425476,-0.05604737624526024,-0.001702621579170227,0.9126200079917908,0.40507128834724426,-0.06168590486049652,0.0011185862822458148,0.9122011065483093,0.40860018134117126,-0.06874453276395798,0.006571456324309111,0.9100971817970276,0.4142597019672394,-0.07540170848369598,0.010341010056436062,0.9069710969924927,0.42249661684036255,-0.08038340508937836,0.00939260609447956,0.9027441143989563,0.43260252475738525,-0.08495201915502548,0.007287358399480581,0.8975439667701721,0.44174596667289734,-0.0903911292552948,0.009894730523228645,0.8925200700759888,0.44870904088020325,-0.09587875008583069,0.0162451583892107,0.8883712887763977,0.447131872177124,-0.09965430200099945,0.022104021161794662,0.8886245489120483,0.4362657070159912,-0.10286478698253632,0.027241704985499382,0.8935037851333618,0.4141990840435028,-0.10542670637369156,0.03095361404120922,0.9035298824310303,0.376434862613678,-0.10877946764230728,0.028629979118704796,0.9195891618728638,0.334188312292099,-0.11427531391382217,0.02348666451871395,0.935258150100708,0.2977602481842041,-0.12506969273090363,0.019886765629053116,0.9462034702301025,0.2690787613391876,-0.1375257819890976,0.016866907477378845,0.9530996084213257,0.24145278334617615,-0.1519959419965744,0.011842085048556328,0.9583619236946106,0.21216224133968353,-0.1692688763141632,0.009067563340067863,0.9624204039573669,0.188406839966774,-0.18153806030750275,0.006772137712687254,0.9651429653167725,0.18021607398986816,-0.17191319167613983,0.008356201462447643,0.9684514403343201,0.184324249625206,-0.16217800974845886,0.010641898959875107,0.9693346619606018,0.1858852505683899,-0.13689757883548737,0.016431158408522606,0.9728493094444275,0.19348303973674774,-0.11406292766332626,0.023479297757148743,0.9741677045822144,0.18672853708267212,-0.10417294502258301,0.02565106935799122,0.976535975933075,0.1731100231409073,-0.10198193043470383,0.02480929344892502,0.9792942404747009,0.16052331030368805,-0.10037919878959656,0.023787792772054672,0.9816264510154724,0.1503250002861023,-0.09994696080684662,0.022732364013791084,0.9833088517189026,0.14206954836845398,-0.1035921722650528,0.021104607731103897,0.9841949343681335,0.13711680471897125,-0.10990828275680542,0.019397471100091934,0.9842473864555359,0.13902495801448822,-0.11609896272420883,0.01842421293258667,0.9832871556282043,0.1513984352350235,-0.12022360414266586,0.01862267777323723,0.9809576869010925,0.17688912153244019,-0.12571169435977936,0.019380182027816772,0.9759770631790161,0.21366490423679352,-0.13281500339508057,0.0202325489372015,0.9676250219345093,0.25526124238967896,-0.14147748053073883,0.021409770473837852,0.9562256932258606,0.29136815667152405,-0.15072591602802277,0.02159309946000576,0.9444151520729065,0.3202390670776367,-0.15926659107208252,0.019721345975995064,0.9336445331573486,0.3425728976726532,-0.165329247713089,0.01766408421099186,0.9246610403060913,0.36613234877586365,-0.14890991151332855,0.026321815326809883,0.9181939959526062,0.3920865058898926,-0.1238831952214241,0.03979863226413727,0.9106795191764832,0.41956645250320435,-0.0906040370464325,0.04077865555882454,0.902270495891571,0.4431549906730652,-0.05849161371588707,0.04163429141044617,0.89356529712677,0.45854029059410095,-0.04571428522467613,0.04051251709461212,0.8865719437599182,0.4716438055038452,-0.04619421809911728,0.04610201716423035,0.8793707489967346,0.47877803444862366,-0.04916314035654068,0.054564207792282104,0.8748584985733032,0.4823477864265442,-0.05052734166383743,0.05821998044848442,0.8725812435150146,0.48332124948501587,-0.05080533027648926,0.05734036862850189,0.8720846176147461,0.4825687110424042,-0.05147368833422661,0.05583944171667099,0.8725594282150269,0.48009026050567627,-0.05215988680720329,0.05366959795355797,0.8740207552909851,0.47680580615997314,-0.05255282670259476,0.051172953099012375,0.8759427666664124,0.4743747413158417,-0.053198136389255524,0.05042561516165733,0.8772661089897156,0.47413015365600586,-0.05383691191673279,0.05002471059560776,0.8773822784423828,0.47501105070114136,-0.053378261625766754,0.046853698790073395,0.8771088719367981,0.47494176030158997,-0.052142489701509476,0.042030218988657,0.8774650692939758,0.47124844789505005,-0.05115681514143944,0.03833624720573425,0.8796807527542114,0.4649030566215515,-0.049745190888643265,0.03585805743932724,0.8832354545593262,0.4570375084877014,-0.0479830764234066,0.03345213085412979,0.8875219821929932,0.4481526017189026,-0.046970516443252563,0.031707245856523514,0.8921590447425842,0.4398201107978821,-0.04675977677106857,0.031049758195877075,0.8963301777839661,0.43234819173812866,-0.04641703888773918,0.030172428116202354,0.9000056385993958,0.42600318789482117,-0.04562155529856682,0.02878979966044426,0.9031119346618652,0.42055946588516235,-0.044759832322597504,0.028048504143953323,0.9057260155677795,0.4161205291748047,-0.044102612882852554,0.028454724699258804,0.9077935218811035,0.4134162962436676,-0.04335196688771248,0.028992919251322746,0.9090473055839539,0.41311368346214294,-0.042183104902505875,0.028746170923113823,0.9092476963996887,0.41488757729530334,-0.040803808718919754,0.028332646936178207,0.9085156321525574,0.41774412989616394,-0.03955530747771263,0.02859356813132763,0.9072527885437012,0.42096295952796936,-0.03834402188658714,0.028988098725676537,0.9058033227920532,0.42393958568573,-0.03801276534795761,0.02808002196252346,0.904456615447998,0.427426278591156,-0.038194529712200165,0.026548877358436584,0.9028527736663818,0.4308922588825226,-0.0381692536175251,0.025304600596427917,0.9012406468391418,0.43412357568740845,-0.03738931939005852,0.024550100788474083,0.8997421860694885,0.43659132719039917,-0.03593304008245468,0.024071769788861275,0.8986197113990784,0.4381425082683563,-0.03438853845000267,0.02367277629673481,0.8979355692863464,0.4388229250907898,-0.03313576057553291,0.02310173213481903,0.8976652026176453,0.4390972852706909,-0.03233333304524422,0.022357074543833733,0.8975791335105896,0.4392603039741516,-0.031829703599214554,0.021792558953166008,0.897531270980835,0.4393479526042938,-0.03132227808237076,0.021586887538433075,0.8975111842155457,0.43943414092063904,-0.030691325664520264,0.02154875360429287,0.8974916934967041,0.4396992325782776,-0.03002777509391308,0.02163393422961235,0.8973822593688965,0.44021549820899963,-0.029467791318893433,0.022313684225082397,0.8971310257911682,0.4408244490623474,-0.02957346849143505,0.023758554831147194,0.8967913389205933,0.44126009941101074,-0.03027740679681301,0.02565697394311428,0.8965013027191162,0.44140931963920593,-0.03101574257016182,0.027326270937919617,0.8963532447814941,0.4413975477218628,-0.031431932002305984,0.02802344225347042,0.896323025226593,0.44133391976356506,-0.031385958194732666,0.027630895376205444,0.896368145942688,0.4412246644496918,-0.031027302145957947,0.026725003495812416,0.8964618444442749,0.44112199544906616,-0.030606022104620934,0.02596571110188961,0.8965492248535156,0.44105032086372375,-0.030267290771007538,0.025610988959670067,0.8966061472892761],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999998211860657,0.9999998807907104,1,0.9999999403953552,1],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.000001122145931731211,23.109195709228516,0.0000022853425889479695,0.0000017281651025768952,23.10919952392578,6.494125273093232e-7],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.556774377822876,0.15937629342079163,-0.19432352483272552,0.7917321920394897,-0.5570329427719116,0.16025707125663757,-0.19308236241340637,0.7916762232780457,-0.5566033124923706,0.16055312752723694,-0.1917683184146881,0.7922375798225403,-0.5550733208656311,0.15962348878383636,-0.19077050685882568,0.7937383651733398,-0.5525063276290894,0.15779900550842285,-0.18991447985172272,0.7960959672927856,-0.5494616031646729,0.15506064891815186,-0.1890505999326706,0.7989418506622314,-0.5466364622116089,0.15048764646053314,-0.1883579045534134,0.8019123673439026,-0.5444930791854858,0.14384283125400543,-0.1872316598892212,0.8048483729362488,-0.5434403419494629,0.14098581671714783,-0.1815279871225357,0.8073680996894836,-0.5419376492500305,0.1376715749502182,-0.17491386830806732,0.8104044198989868,-0.53963702917099,0.13462983071804047,-0.168599933385849,0.81378173828125,-0.537211537361145,0.13234283030033112,-0.1635579615831375,0.8167851567268372,-0.5352208018302917,0.13050265610218048,-0.1603553742170334,0.8190200924873352,-0.534309983253479,0.12917323410511017,-0.1580173522233963,0.8202790021896362,-0.5338888168334961,0.1279347538948059,-0.15553268790245056,0.8212217092514038,-0.5345005989074707,0.12661243975162506,-0.1524575799703598,0.8216052055358887,-0.5378937721252441,0.12557826936244965,-0.1496839076280594,0.8200579881668091,-0.5446496605873108,0.1261550337076187,-0.15015973150730133,0.8154101371765137,-0.5533093810081482,0.12964865565299988,-0.1568979024887085,0.8077270984649658,-0.5627435445785522,0.1363546997308731,-0.16955292224884033,0.7974828481674194,-0.5701714158058167,0.14378242194652557,-0.18345074355602264,0.7877671718597412,-0.5744085907936096,0.15182402729988098,-0.1982801854610443,0.7795442938804626,-0.5735185742378235,0.16154712438583374,-0.21454493701457977,0.7739182710647583,-0.5648654103279114,0.17412400245666504,-0.23082609474658966,0.7728694677352905,-0.5538212060928345,0.18357783555984497,-0.24120324850082397,0.7755013108253479,-0.5456640720367432,0.18826253712177277,-0.24407914280891418,0.7792518138885498,-0.5399183034896851,0.19110499322414398,-0.24731731414794922,0.7815377116203308,-0.5338498950004578,0.1948615163564682,-0.25058355927467346,0.783735454082489,-0.5249841213226318,0.19868551194667816,-0.2532964050769806,0.7878811955451965,-0.5145495533943176,0.19683311879634857,-0.25637638568878174,0.7942081689834595,-0.508349597454071,0.18363691866397858,-0.2631226181983948,0.7991400361061096,-0.5153436064720154,0.1852138340473175,-0.2563581168651581,0.7964906692504883,-0.5206711292266846,0.18690450489521027,-0.2514796257019043,0.7941827774047852,-0.5281186699867249,0.18135088682174683,-0.2384713888168335,0.7945652604103088,-0.526932418346405,0.17786718904972076,-0.2302045077085495,0.7985683679580688,-0.5216602683067322,0.1767086386680603,-0.22707583010196686,0.8031694889068604,-0.5161308646202087,0.1767110824584961,-0.22657327353954315,0.806874692440033,-0.5109409689903259,0.17710471153259277,-0.22746644914150238,0.8098347187042236,-0.5059195160865784,0.17730876803398132,-0.22855116426944733,0.8126323819160461,-0.502315104007721,0.1773277372121811,-0.22902800142765045,0.8147273659706116,-0.5024780035018921,0.1773933470249176,-0.22862063348293304,0.8147270679473877,-0.5073497891426086,0.17405551671981812,-0.22973430156707764,0.8121102452278137,-0.5183334350585938,0.16795764863491058,-0.23173964023590088,0.8058644533157349,-0.5352014303207397,0.16312870383262634,-0.23276861011981964,0.7954667210578918,-0.5546178221702576,0.16357791423797607,-0.23291558027267456,0.7819153666496277,-0.5699726343154907,0.16668091714382172,-0.23629392683506012,0.7690994143486023,-0.5806859135627747,0.17078499495983124,-0.2432377189397812,0.7579392194747925,-0.5911039710044861,0.18193671107292175,-0.2477608621120453,0.7457275986671448,-0.6042124629020691,0.18540798127651215,-0.25124725699424744,0.7330935001373291,-0.620521068572998,0.18907801806926727,-0.2537212371826172,0.7175156474113464,-0.6344515085220337,0.18581794202327728,-0.25377562642097473,0.7060743570327759,-0.6415858864784241,0.18144677579402924,-0.2520115375518799,0.7013806700706482,-0.6436454057693481,0.17963463068008423,-0.2462722659111023,0.7019985318183899,-0.6444754004478455,0.17337031662464142,-0.23839253187179565,0.7055233716964722,-0.642387330532074,0.16824986040592194,-0.22769062221050262,0.7121710181236267,-0.6388871669769287,0.17253704369068146,-0.2153758555650711,0.7180998921394348,-0.6339747905731201,0.1858176738023758,-0.20246367156505585,0.7228805422782898,-0.6283432841300964,0.1997380256652832,-0.19204039871692657,0.72691810131073,-0.6214120984077454,0.20707498490810394,-0.1857246309518814,0.7324434518814087,-0.6137736439704895,0.20692192018032074,-0.18355122208595276,0.7394418120384216,-0.6059978604316711,0.203782856464386,-0.1849599927663803,0.746343731880188,-0.5990011692047119,0.20252732932567596,-0.1878517121076584,0.7515930533409119,-0.5930067300796509,0.20325979590415955,-0.19011850655078888,0.7555683255195618,-0.5869991779327393,0.20303358137607574,-0.19246821105480194,0.7597140073776245,-0.5794868469238281,0.20043009519577026,-0.19559644162654877,0.7653527855873108,-0.5714053511619568,0.1959143728017807,-0.1986808180809021,0.7717768549919128,-0.5634454488754272,0.1911916732788086,-0.20121951401233673,0.7781296372413635,-0.5560899376869202,0.18726220726966858,-0.2036636918783188,0.7837206125259399,-0.5499110221862793,0.18458697199821472,-0.20644725859165192,0.7879753112792969,-0.5449479222297668,0.1836068034172058,-0.20854073762893677,0.7910948395729065,-0.5410062074661255,0.1839398890733719,-0.20968571305274963,0.7934168577194214,-0.5375878810882568,0.18401561677455902,-0.21047767996788025,0.7955103516578674,-0.5345054268836975,0.18250726163387299,-0.21131810545921326,0.7977089881896973,-0.5321152210235596,0.17921662330627441,-0.21181398630142212,0.799918532371521,-0.5308511853218079,0.17460377514362335,-0.21149025857448578,0.8018618226051331,-0.5306268334388733,0.1693028211593628,-0.2105368822813034,0.8033965229988098,-0.5310989618301392,0.16415084898471832,-0.2092854082584381,0.8044800162315369,-0.5322017073631287,0.15974198281764984,-0.20759733021259308,0.8050758838653564,-0.533990740776062,0.1568765938282013,-0.20481468737125397,0.8051674365997314,-0.536561131477356,0.15479259192943573,-0.20150746405124664,0.8046963810920715,-0.5392763614654541,0.15312282741069794,-0.19864162802696228,0.803912878036499,-0.5418869256973267,0.15185141563415527,-0.19637152552604675,0.8029557466506958,-0.5441406965255737,0.150883287191391,-0.194633349776268,0.8020368218421936,-0.5459394454956055,0.15016043186187744,-0.19332939386367798,0.8012651205062866,-0.5473009943962097,0.1495402306318283,-0.19219227135181427,0.8007256388664246,-0.548395037651062,0.14902852475643158,-0.19123414158821106,0.8003017902374268,-0.5492864847183228,0.14871332049369812,-0.19062694907188416,0.7998937964439392,-0.5499705076217651,0.14861652255058289,-0.19042295217514038,0.7994903326034546,-0.5505335330963135,0.14865578711032867,-0.19047735631465912,0.7990824580192566,-0.5511186718940735,0.14878593385219574,-0.19071029126644135,0.798599123954773,-0.551762580871582,0.14910273253917694,-0.19131144881248474,0.7979514002799988,-0.5524643659591675,0.15040631592273712,-0.19210797548294067,0.7970292568206787,-0.5531567335128784,0.15305998921394348,-0.19288446009159088,0.7958554625511169,-0.5538147687911987,0.15643839538097382,-0.19351154565811157,0.7945876717567444,-0.5544977784156799,0.15931260585784912,-0.19383709132671356,0.7934601306915283,-0.5551717281341553,0.1605171412229538,-0.1939157098531723,0.7927265763282776,-0.55576092004776,0.1600886583328247,-0.19395118951797485,0.7923915386199951,-0.5562841892242432,0.15931864082813263,-0.19408755004405975,0.7921462059020996,-0.5567750334739685,0.1593758761882782,-0.19432324171066284,0.7917319536209106],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,0.9999999403953552,0.9999998807907104,0.9999998807907104,0.9999999403953552,1.000000238418579],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000011380543583072722,13.547399520874023,5.306914019342912e-9,-0.0000019939436697313795,13.54739761352539,0.0000018021441974269692],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.30788561701774597,0.01450190506875515,0.0046918014995753765,0.9513012766838074,-0.30788561701774597,0.014501920901238918,0.0046918271109461784,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1,0.9999998807907104,1,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,3.299999952316284],"values":[2.833993911743164,7.754294395446777,-6.139593601226807,2.834002733230591,7.754303932189941,-6.1396050453186035],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.3320007026195526,-0.09571804106235504,-0.11373622715473175,0.9314922094345093,-0.3320006728172302,-0.09571805596351624,-0.11373621225357056,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,0.9999999403953552,0.9999998211860657,1,1,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,3.299999952316284],"values":[4.6845553924868e-7,10.288599967956543,-9.241045972885331e-7,-6.798652663064786e-8,10.288609504699707,2.95694988494688e-8],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-1.5148543752729893e-8,-5.151376214485026e-9,1.3140379451215267e-8,1,-1.5148543752729893e-8,-9.691574653913904e-9,1.2674718163907528e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999999403953552,0.9999999403953552,1,1,1],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,3.299999952316284],"values":[-1.1743264138885934e-7,10.28860092163086,8.392657377953583e-7,-7.128357992769452e-7,10.288601875305176,-8.463734957331326e-7],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.03407181799411774,0.019583264365792274,-0.0144273079931736,0.9991233944892883,0.03441993519663811,0.020268401131033897,-0.013625775463879108,0.9991090297698975,0.0344400554895401,0.021795639768242836,-0.013124470598995686,0.9990829229354858,0.03435347601771355,0.024042265489697456,-0.013005661778151989,0.9990358352661133,0.03457178920507431,0.025955697521567345,-0.013051670044660568,0.9989798665046692,0.03470584750175476,0.02582593820989132,-0.013395051471889019,0.9989740252494812,0.03353509679436684,0.022387463599443436,-0.014389772899448872,0.9990831613540649,0.030255116522312164,0.015593172051012516,-0.016178961843252182,0.9992896318435669,0.02568952552974224,0.0065736896358430386,-0.018461061641573906,0.9994779229164124,0.021733492612838745,-0.003143732901662588,-0.020618047565221786,0.999546229839325,0.01973477005958557,-0.013022897765040398,-0.022548465058207512,0.9994661211967468,0.02002754807472229,-0.02396272122859955,-0.024951720610260963,0.9992007613182068,0.02345339208841324,-0.037401724606752396,-0.02800838090479374,0.9986323714256287,0.031382787972688675,-0.05392257869243622,-0.031162414699792862,0.9975652098655701,0.04214753583073616,-0.0715259462594986,-0.03407863900065422,0.9959650039672852,0.05032768473029137,-0.08649981021881104,-0.0369664803147316,0.9942929148674011,0.05238225311040878,-0.09675277769565582,-0.04009323939681053,0.9931200742721558,0.050127580761909485,-0.1022101491689682,-0.043264031410217285,0.9925565719604492,0.046861566603183746,-0.10310964286327362,-0.046120647341012955,0.9924944639205933,0.043238647282123566,-0.10016871243715286,-0.048339102417230606,0.9928544759750366,0.03843938559293747,-0.09567505121231079,-0.05022941902279854,0.9934011101722717,0.03373633697628975,-0.0913313701748848,-0.05177466198801994,0.9939013123512268,0.031099732965230942,-0.08651530742645264,-0.05305124446749687,0.994350790977478,0.030093219131231308,-0.07890529185533524,-0.0535445436835289,0.9949880838394165,0.027986083179712296,-0.06688868254423141,-0.052416183054447174,0.9959895610809326,0.023368077352643013,-0.05116952210664749,-0.0482858307659626,0.9972482323646545,0.017728745937347412,-0.035026177763938904,-0.04128825664520264,0.9983757138252258,0.01366960909217596,-0.02287852205336094,-0.037084370851516724,0.9989566802978516,0.012094372883439064,-0.017678245902061462,-0.04336807131767273,0.9988295435905457,0.010495386086404324,-0.018752723932266235,-0.05899479240179062,0.998026967048645,0.008059524931013584,-0.02402791939675808,-0.07503753900527954,0.9968585968017578,0.008217411115765572,-0.03132236376404762,-0.08464890718460083,0.9958845376968384,0.012132905423641205,-0.03693832829594612,-0.08941301703453064,0.9952355027198792,0.016935838386416435,-0.03845488280057907,-0.09293209761381149,0.9947854280471802,0.020140526816248894,-0.03723517060279846,-0.09703192859888077,0.9943805932998657,0.021251311525702477,-0.035600461065769196,-0.10153889656066895,0.9939672350883484,0.021183589473366737,-0.03382830694317818,-0.10512088239192963,0.9936581254005432,0.021199775859713554,-0.031022757291793823,-0.10732847452163696,0.9935134053230286,0.02141244150698185,-0.027921579778194427,-0.10858964920043945,0.9934637546539307,0.02093188464641571,-0.025942495092749596,-0.10883203148841858,0.9935011267662048,0.019205665215849876,-0.024105917662382126,-0.1072046235203743,0.9937591552734375,0.01686711050570011,-0.019835935905575752,-0.10371436923742294,0.9942662715911865,0.014931525103747845,-0.013462389819324017,-0.09958090633153915,0.9948263168334961,0.01358154695481062,-0.009097028523683548,-0.09529215097427368,0.9953151345252991,0.012700855731964111,-0.008980945684015751,-0.089573934674263,0.9958587288856506,0.013242780230939388,-0.009901219978928566,-0.0804808959364891,0.9966190457344055,0.015982182696461678,-0.008128948509693146,-0.06873517483472824,0.9974737763404846,0.019427470862865448,-0.0028092369902879,-0.05650798976421356,0.9982091784477234,0.020097604021430016,0.005591649562120438,-0.04275597631931305,0.9988677501678467,0.017010603100061417,0.01429428905248642,-0.024661835283041,0.9994488954544067,0.015106002800166607,0.01792760379612446,-0.01081213727593422,0.999666690826416,0.019601233303546906,0.0147702656686306,-0.013254689984023571,0.9996109008789062,0.027206024155020714,0.010619848035275936,-0.022447265684604645,0.9993213415145874,0.03250333294272423,0.008900998160243034,-0.027578312903642654,0.9990514516830444,0.03383979946374893,0.008953937329351902,-0.028897657990455627,0.9989692568778992,0.032814059406518936,0.009930265136063099,-0.027165208011865616,0.9990428686141968,0.03148801997303963,0.011469571851193905,-0.024884652346372604,0.999128520488739,0.03079487755894661,0.013077415525913239,-0.023307282477617264,0.9991683959960938,0.030561642721295357,0.014243760146200657,-0.022062448784708977,0.9991878271102905,0.030528707429766655,0.01492375135421753,-0.02000501938164234,0.9992222785949707,0.030550919473171234,0.015723545104265213,-0.017726659774780273,0.9992522597312927,0.030563078820705414,0.017373641952872276,-0.01684851571917534,0.9992398023605347,0.03072238527238369,0.02029469422996044,-0.017891395837068558,0.9991617202758789,0.03129829838871956,0.02427837811410427,-0.01970868743956089,0.9990208148956299,0.032262273132801056,0.028322214260697365,-0.02087818831205368,0.9988598823547363,0.0334409736096859,0.03132455796003342,-0.02079756371676922,0.9987332224845886,0.034673113375902176,0.032783281058073044,-0.020326465368270874,0.9986540675163269,0.03579094260931015,0.03275781124830246,-0.019862717017531395,0.9986248016357422,0.03645363822579384,0.03177424520254135,-0.01949533261358738,0.9986397624015808,0.03641463816165924,0.030719108879566193,-0.01926153525710106,0.9986787438392639,0.035924024879932404,0.02996784821152687,-0.019201179966330528,0.9987205862998962,0.03545272350311279,0.029317734763026237,-0.01906612701714039,0.9987592697143555,0.03523656353354454,0.02867721952497959,-0.018658755347132683,0.9987931847572327,0.03528835251927376,0.028210081160068512,-0.01815015822649002,0.998814046382904,0.03553604707121849,0.027948280796408653,-0.01786481775343418,0.9988177418708801,0.03577343747019768,0.02767079696059227,-0.017897628247737885,0.9988164305686951,0.03575180098414421,0.027047116309404373,-0.018104147166013718,0.9988305568695068,0.035474300384521484,0.02578040026128292,-0.018313100561499596,0.9988701343536377,0.03523315116763115,0.023826103657484055,-0.018446488305926323,0.9989247918128967,0.035285331308841705,0.021610457450151443,-0.018543412908911705,0.9989715218544006,0.035547029227018356,0.019838379696011543,-0.01853996329009533,0.9989990592002869,0.03573404625058174,0.018810831010341644,-0.018304916098713875,0.9990165829658508,0.03567555174231529,0.018272127956151962,-0.01781425252556801,0.9990375638008118,0.03539830818772316,0.017995743080973625,-0.017214346677064896,0.9990629553794861,0.03500577434897423,0.01797972433269024,-0.016619838774204254,0.9990871548652649,0.03455497696995735,0.01813068799674511,-0.01604074239730835,0.9991095662117004,0.034054700285196304,0.01830913871526718,-0.015520859509706497,0.9991317391395569,0.03357046842575073,0.01845370978116989,-0.015214149840176105,0.9991501569747925,0.03322760388255119,0.018550651147961617,-0.015149184502661228,0.9991608262062073,0.03305375576019287,0.018682371824979782,-0.015055500902235508,0.9991655349731445,0.03297317773103714,0.018982766196131706,-0.014652134850621223,0.9991685748100281,0.032950494438409805,0.019401298835873604,-0.014057101681828499,0.9991698265075684,0.03303894028067589,0.01968752034008503,-0.013661031611263752,0.9991667866706848,0.03331732749938965,0.019672436639666557,-0.013650620356202126,0.9991579651832581,0.03375616669654846,0.019467856734991074,-0.013876073062419891,0.9991441369056702,0.03415663167834282,0.019316144287586212,-0.014136286452412605,0.9991298317909241,0.034310441464185715,0.019318709149956703,-0.014336531981825829,0.9991216659545898,0.034225549548864365,0.019431577995419502,-0.014438924379646778,0.9991208910942078,0.034071825444698334,0.019583236426115036,-0.014427412301301956,0.9991233944892883],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999997019767761,0.9999997019767761,0.9999999403953552,0.9999998211860657,0.9999997019767761],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,3.299999952316284],"values":[4.366398334503174,7.562901973724365,-0.17680102586746216,4.366398811340332,7.5629072189331055,-0.17679741978645325],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.6548703908920288,0.4794819951057434,-0.4197899401187897,0.4062243402004242,0.6549434065818787,0.47921356558799744,-0.41970571875572205,0.4065103232860565,0.6549406051635742,0.47894129157066345,-0.41947126388549805,0.4070774018764496,0.6550625562667847,0.47866201400756836,-0.4188309609889984,0.40786802768707275,0.6561979651451111,0.47752368450164795,-0.4173213541507721,0.40892326831817627,0.6598643660545349,0.47362589836120605,-0.4140014946460724,0.4109261929988861,0.6668979525566101,0.46530696749687195,-0.4083836078643799,0.4146799147129059,0.6757423281669617,0.45351219177246094,-0.40180182456970215,0.41982653737068176,0.6833372116088867,0.4418574571609497,-0.3965986371040344,0.4248785972595215,0.6868376135826111,0.4357917606830597,-0.39086344838142395,0.4307731091976166,0.6881534457206726,0.4339682161808014,-0.38617202639579773,0.4347270429134369,0.6922524571418762,0.4288688600063324,-0.3890817165374756,0.43066638708114624,0.6941127777099609,0.42803752422332764,-0.3867983818054199,0.4305558502674103,0.6976290941238403,0.42387905716896057,-0.3838253617286682,0.43164610862731934,0.7022038102149963,0.41777485609054565,-0.3807202875614166,0.43292737007141113,0.706636369228363,0.41240042448043823,-0.3782537877559662,0.4330300986766815,0.7099283337593079,0.4089660048484802,-0.3767109215259552,0.43224695324897766,0.7107978463172913,0.40884479880332947,-0.3769128918647766,0.43075403571128845,0.7092558741569519,0.41213759779930115,-0.3792664110660553,0.42808377742767334,0.7069761157035828,0.41581663489341736,-0.3824947476387024,0.42541640996932983,0.7054256796836853,0.41762349009513855,-0.384989470243454,0.42396748065948486,0.7050420641899109,0.4184131324291229,-0.38600608706474304,0.4229012727737427,0.7060871720314026,0.41913706064224243,-0.38504117727279663,0.4213174283504486,0.708337128162384,0.4188609719276428,-0.38237491250038147,0.4202421307563782,0.7115445733070374,0.4152745306491852,-0.3789767622947693,0.42145946621894836,0.7166975140571594,0.4053467810153961,-0.3742218315601349,0.4266108274459839,0.7208767533302307,0.39029088616371155,-0.36353418231010437,0.4425524175167084,0.7071569561958313,0.3963579535484314,-0.32012709975242615,0.4902530312538147,0.6874700784683228,0.40438923239707947,-0.2676645517349243,0.5405645370483398,0.6691710948944092,0.40545475482940674,-0.21741394698619843,0.5835646390914917,0.6663587093353271,0.3804025948047638,-0.20467400550842285,0.6077569723129272,0.6643257737159729,0.35900384187698364,-0.20008787512779236,0.624301552772522,0.6635910272598267,0.3489689826965332,-0.19946430623531342,0.6309371590614319,0.6637816429138184,0.34825676679611206,-0.20122939348220825,0.630569577217102,0.6627300977706909,0.3509165942668915,-0.20446716248989105,0.6291579008102417,0.6605749726295471,0.35406816005706787,-0.20885401964187622,0.6282168626785278,0.6589531302452087,0.3578749895095825,-0.2132592350244522,0.6262801289558411,0.6584216952323914,0.36247214674949646,-0.21686671674251556,0.6229475736618042,0.6585164070129395,0.36908572912216187,-0.2206074595451355,0.6176279187202454,0.6585226655006409,0.38147905468940735,-0.22614888846874237,0.608011782169342,0.6578844785690308,0.40120676159858704,-0.23462262749671936,0.5925988554954529,0.6573268175125122,0.4250037968158722,-0.2438233643770218,0.5725762248039246,0.6593230962753296,0.43885910511016846,-0.2598036527633667,0.5524471402168274,0.6711670756340027,0.4356619715690613,-0.2889004349708557,0.5256139039993286,0.6812601089477539,0.4296676516532898,-0.3198443651199341,0.4989688992500305,0.6888381838798523,0.4247867465019226,-0.3583342432975769,0.4654619097709656,0.6933408379554749,0.42235493659973145,-0.40058431029319763,0.42476704716682434,0.6826976537704468,0.4405340850353241,-0.4173930585384369,0.40698477625846863,0.6662397980690002,0.4614945948123932,-0.42086032032966614,0.40746018290519714,0.6505499482154846,0.476222962141037,-0.4193069636821747,0.4173465669155121,0.6408765316009521,0.48346036672592163,-0.4163135588169098,0.4268799424171448,0.645035982131958,0.4825778305530548,-0.41516536474227905,0.42271146178245544,0.6508240103721619,0.479625940322876,-0.4162355363368988,0.41609498858451843,0.6508313417434692,0.4796046316623688,-0.4184384346008301,0.41389280557632446,0.649161696434021,0.4816139340400696,-0.4204551577568054,0.4121342599391937,0.6483039259910583,0.48340415954589844,-0.42118996381759644,0.41063541173934937,0.6486477851867676,0.48378458619117737,-0.42069482803344727,0.41015174984931946,0.6493777632713318,0.4834071397781372,-0.419422447681427,0.410744309425354,0.6499535441398621,0.48313090205192566,-0.41776034235954285,0.4118509888648987,0.6503611207008362,0.4834675192832947,-0.4157664179801941,0.41282913088798523,0.6508274078369141,0.48414215445518494,-0.41392016410827637,0.4131588339805603,0.6515820622444153,0.4842509925365448,-0.41277119517326355,0.4129912555217743,0.65282142162323,0.48313137888908386,-0.4121478199958801,0.41296783089637756,0.6544171571731567,0.4812273383140564,-0.411555677652359,0.41325587034225464,0.6558767557144165,0.4796098470687866,-0.41099345684051514,0.41338181495666504,0.6568284630775452,0.47875526547431946,-0.41074031591415405,0.4131127893924713,0.657468855381012,0.47816339135169983,-0.410942018032074,0.4125787615776062,0.657936692237854,0.4775799810886383,-0.41141635179519653,0.41203558444976807,0.6581063866615295,0.4771381914615631,-0.41211849451065063,0.4115743041038513,0.6578136682510376,0.47689369320869446,-0.41315704584121704,0.4112844467163086,0.6571965217590332,0.47677212953567505,-0.41441184282302856,0.4111495316028595,0.6566141843795776,0.4766995310783386,-0.41547366976737976,0.41109251976013184,0.6563020944595337,0.47661876678466797,-0.416099488735199,0.41105154156684875,0.6562049388885498,0.4765036702156067,-0.41648051142692566,0.41095414757728577,0.6561243534088135,0.4764114022254944,-0.4169574975967407,0.41070616245269775,0.655930757522583,0.4764358103275299,-0.4176810085773468,0.410251647233963,0.6557006239891052,0.4765394330024719,-0.41851624846458435,0.40964752435684204,0.6555736064910889,0.4766552150249481,-0.41921865940093994,0.4089973270893097,0.655500054359436,0.4768315553665161,-0.4197465479373932,0.40836766362190247,0.6554140448570251,0.4770934283733368,-0.4202031195163727,0.4077299237251282,0.6553985476493835,0.47731831669807434,-0.42058148980140686,0.40710097551345825,0.6555215120315552,0.47738268971443176,-0.4207988977432251,0.4066025912761688,0.6556174159049988,0.4774591028690338,-0.42088982462882996,0.40626388788223267,0.6554582715034485,0.4778216481208801,-0.4210522770881653,0.4059259593486786,0.6550073027610779,0.4784219264984131,-0.421316921710968,0.40567222237586975,0.6544824242591858,0.4790347218513489,-0.42157483100891113,0.4055282771587372,0.6540648937225342,0.4795190691947937,-0.42171815037727356,0.40548041462898254,0.6537521481513977,0.4798808693885803,-0.4217248857021332,0.4055498242378235,0.6534737348556519,0.4801531136035919,-0.42163023352622986,0.4057745337486267,0.6533357501029968,0.480293869972229,-0.4214283227920532,0.40603992342948914,0.653515100479126,0.48026689887046814,-0.4211326241493225,0.4060899615287781,0.6538773775100708,0.4801193177700043,-0.4209359288215637,0.4058852195739746,0.654215931892395,0.4799624979496002,-0.4208458662033081,0.4056183993816376,0.654515266418457,0.4797849655151367,-0.4206351041793823,0.4055641293525696,0.6548143029212952,0.4795553684234619,-0.42029526829719543,0.4057053029537201,0.6550548076629639,0.47935786843299866,-0.4199890196323395,0.40586763620376587,0.6551434993743896,0.4792914390563965,-0.41981402039527893,0.40598398447036743,0.6550619602203369,0.4793626070022583,-0.41978007555007935,0.40606650710105896,0.6548704504966736,0.47948190569877625,-0.4197900891304016,0.4062243103981018],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,0.9999997615814209,0.9999998807907104,0.9999998807907104,0.9999997615814209,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000037596346373902634,17.630796432495117,0.00001018491093418561,-0.0000018217176602774998,17.63079833984375,0.000010165447747567669],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.3181892931461334,-0.0039811888709664345,0.23236121237277985,0.9191017746925354,0.31899935007095337,-0.005300112068653107,0.2326832264661789,0.918732762336731,0.3196083903312683,-0.007881339639425278,0.2336355447769165,0.9182608127593994,0.31826409697532654,-0.013454142957925797,0.23474569618701935,0.9183797836303711,0.31192082166671753,-0.025563614442944527,0.23504596948623657,0.9202202558517456,0.2973317801952362,-0.04673650860786438,0.23294711112976074,0.9247406125068665,0.27204975485801697,-0.07228367775678635,0.22765959799289703,0.9321668744087219,0.23743493854999542,-0.08715716749429703,0.22113099694252014,0.9418754577636719,0.20264729857444763,-0.07388181984424591,0.2173587530851364,0.9519615173339844,0.18706101179122925,-0.025314927101135254,0.22039009630680084,0.956972062587738,0.19369766116142273,0.05363308638334274,0.23519541323184967,0.9509405493736267,0.2027921825647354,0.1470423936843872,0.2589341104030609,0.9328488111495972,0.22421297430992126,0.22977526485919952,0.281637966632843,0.9042189717292786,0.24228473007678986,0.2867628037929535,0.2917134761810303,0.8797547817230225,0.256944864988327,0.3190963864326477,0.29008549451828003,0.8648741245269775,0.2685731053352356,0.33482691645622253,0.2841111123561859,0.857344925403595,0.28005504608154297,0.33994412422180176,0.2784143090248108,0.8535178303718567,0.2933329939842224,0.33635273575782776,0.2744646370410919,0.8517580628395081,0.3052043318748474,0.324104368686676,0.2709380090236664,0.8534631133079529,0.3102351129055023,0.3030253052711487,0.263414204120636,0.8617092370986938,0.3047686219215393,0.268787145614624,0.24963046610355377,0.8789506554603577,0.2891515791416168,0.2126675397157669,0.23129133880138397,0.9042501449584961,0.2666969895362854,0.13003727793693542,0.21270127594470978,0.9309786558151245,0.23724506795406342,0.029030773788690567,0.20059850811958313,0.9500696063041687,0.19299429655075073,-0.07223436236381531,0.2018888294696808,0.9574843645095825,0.12342498451471329,-0.15347324311733246,0.21430703997612,0.9567051529884338,0.03260769322514534,-0.19408245384693146,0.2227841168642044,0.954796314239502,-0.02104044146835804,-0.20704063773155212,0.19924306869506836,0.9575979113578796,-0.060894936323165894,-0.2093760073184967,0.1567704677581787,0.9632634520530701,-0.08503453433513641,-0.2020777314901352,0.08056838810443878,0.9723386764526367,-0.13319344818592072,-0.20112112164497375,0.05343930423259735,0.9689964652061462,-0.16352476179599762,-0.2188906967639923,0.05957941338419914,0.9601024985313416,-0.17877653241157532,-0.2293972671031952,0.07054850459098816,0.9541691541671753,-0.1893821656703949,-0.23428593575954437,0.08358144760131836,0.9498729705810547,-0.19738027453422546,-0.23617355525493622,0.09394921362400055,0.9468033909797668,-0.20352183282375336,-0.2363666146993637,0.1015682965517044,0.9446658492088318,-0.20831112563610077,-0.23477648198604584,0.10776469111442566,0.9433310031890869,-0.2095438838005066,-0.23716595768928528,0.11524516344070435,0.9415743350982666,-0.20605380833148956,-0.2454988956451416,0.12661588191986084,0.9387441277503967,-0.20057301223278046,-0.2495478391647339,0.14362351596355438,0.9364126324653625,-0.19058288633823395,-0.24500948190689087,0.16738364100456238,0.935751736164093,-0.1674845665693283,-0.2299405038356781,0.181988924741745,0.9412525296211243,-0.1577320098876953,-0.16592390835285187,0.16805815696716309,0.9588254690170288,-0.12134312838315964,-0.15171268582344055,0.16579358279705048,0.9668359160423279,-0.090402752161026,-0.11122395098209381,0.14882561564445496,0.9784209728240967,-0.06177624687552452,-0.0684184655547142,0.1334504336118698,0.9867591261863708,-0.027868086472153664,-0.042279720306396484,0.1353844851255417,0.9894983172416687,0.06749817728996277,-0.033806923776865005,0.15434886515140533,0.9851281642913818,0.18310604989528656,-0.022519072517752647,0.18154588341712952,0.9659224152565002,0.2836952209472656,-0.005873921327292919,0.20702660083770752,0.9362812638282776,0.34533756971359253,0.010734939947724342,0.22457024455070496,0.911150336265564,0.3517877757549286,0.01660292223095894,0.23435527086257935,0.9061166048049927,0.3405355215072632,0.01579657755792141,0.2370540201663971,0.9097205400466919,0.3400617837905884,0.014566238969564438,0.23715591430664062,0.9098917245864868,0.34559208154678345,0.013821626082062721,0.23816551268100739,0.9075528979301453,0.34979090094566345,0.014681164175271988,0.2402641773223877,0.9053750038146973,0.3483956456184387,0.017681848257780075,0.2432626485824585,0.9050586223602295,0.34417837858200073,0.022061677649617195,0.24741296470165253,0.9054509401321411,0.34023141860961914,0.025893669575452805,0.252909779548645,0.9053224325180054,0.3375008702278137,0.027943534776568413,0.25984033942222595,0.9043204188346863,0.3339071273803711,0.027686627581715584,0.2678855359554291,0.9033143520355225,0.3280106484889984,0.025273295119404793,0.27579358220100403,0.9031656980514526,0.3212469220161438,0.021596265956759453,0.2822405993938446,0.9037003517150879,0.3160417377948761,0.018055422231554985,0.2865698039531708,0.9042507410049438,0.3133786916732788,0.01525962445884943,0.2884601056575775,0.9046279788017273,0.3126155138015747,0.013017134740948677,0.28768226504325867,0.9051746129989624,0.3114038407802582,0.010974512435495853,0.28466346859931946,0.9065726399421692,0.3088846206665039,0.009042905643582344,0.28091099858283997,0.908623993396759,0.3057365417480469,0.00725467037409544,0.27785736322402954,0.9106414914131165,0.30265185236930847,0.005967041943222284,0.27566778659820557,0.9123450517654419,0.2995446026325226,0.005542199593037367,0.2737661898136139,0.9139444231987,0.2964613437652588,0.00583308981731534,0.27185314893722534,0.9155176281929016,0.2939678728580475,0.006249037571251392,0.26988354325294495,0.916900634765625,0.292492538690567,0.0063283201307058334,0.26762863993644714,0.918032169342041,0.29188787937164307,0.006133703980594873,0.2649174928665161,0.9190117120742798,0.291771799325943,0.005953959655016661,0.261850506067276,0.9199283123016357,0.2921546399593353,0.005791253410279751,0.25821855664253235,0.9208340048789978,0.2934071719646454,0.005432548932731152,0.25360870361328125,0.9217187166213989,0.2957472503185272,0.004813936073333025,0.24846963584423065,0.9223737120628357,0.29865244030952454,0.0040125297382473946,0.2442195564508438,0.9225765466690063,0.3013451397418976,0.0032165050506591797,0.2418251633644104,0.9223347306251526,0.3035460412502289,0.0029300381429493427,0.24074816703796387,0.9218956232070923,0.30529704689979553,0.0035658807028084993,0.2398703545331955,0.9215439558029175,0.30666813254356384,0.0049127377569675446,0.23875559866428375,0.9213719964027405,0.3078986704349518,0.006289173848927021,0.23756065964698792,0.9212620854377747,0.3093698024749756,0.007133824750781059,0.23650895059108734,0.9210336208343506,0.31121158599853516,0.007263699546456337,0.23560288548469543,0.9206442832946777,0.3131173551082611,0.006748849526047707,0.2348065972328186,0.920205295085907,0.31456202268600464,0.005789173301309347,0.23428475856781006,0.9198521375656128,0.31535056233406067,0.0045605795457959175,0.23416414856910706,0.9196196794509888,0.3155718147754669,0.0031852335669100285,0.234371155500412,0.9194968342781067,0.3153167963027954,0.0017329431138932705,0.23471805453300476,0.9194997549057007,0.31483137607574463,0.00014358478074427694,0.23491913080215454,0.9196164011955261,0.314483642578125,-0.0016148369759321213,0.23470932245254517,0.9197874665260315,0.3144666254520416,-0.003280725097283721,0.2341119945049286,0.9199410676956177,0.31484290957450867,-0.004445259924978018,0.23334145545959473,0.9200032353401184,0.31565022468566895,-0.004876451566815376,0.2326832413673401,0.9198911190032959,0.3168094754219055,-0.004622187465429306,0.23240356147289276,0.9195645451545715,0.31818944215774536,-0.003981189336627722,0.23236125707626343,0.9191016554832458],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[1.0000001192092896,1.0000001192092896,0.9999998807907104,1.0000001192092896,1.0000001192092896,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,3.299999952316284],"values":[0.000001140690528700361,17.827802658081055,8.94418178631895e-7,-0.0000011700087725330377,17.82780647277832,3.0704222808708437e-7],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.15487217903137207,-0.06201845407485962,0.8523130416870117,0.49571242928504944,-0.15486390888690948,-0.06202854588627815,0.8522670865058899,0.4957927465438843,-0.15475845336914062,-0.06215532124042511,0.8516867160797119,0.4968060851097107,-0.15450717508792877,-0.06245600804686546,0.8503044843673706,0.4992084801197052,-0.15406452119350433,-0.06298114359378815,0.8478676080703735,0.5034064054489136,-0.15335357189178467,-0.06381198763847351,0.8439558148384094,0.5100483298301697,-0.15237115323543549,-0.06493702530860901,0.8385487198829651,0.5190397500991821,-0.15147560834884644,-0.06593947112560272,0.8336203694343567,0.5270524621009827,-0.15148384869098663,-0.06593015789985657,0.8336663246154785,0.5269784927368164,-0.15301184356212616,-0.06420647352933884,0.8420747518539429,0.5132008194923401,-0.15536363422870636,-0.061422958970069885,0.8550177216529846,0.4909522235393524,-0.15727394819259644,-0.05903230607509613,0.8655308485031128,0.47184377908706665,-0.15802979469299316,-0.05805058777332306,0.8696909546852112,0.4639981687068939,-0.15801221132278442,-0.058073703199625015,0.8695943355560303,0.4641823470592499,-0.15793344378471375,-0.05817696824669838,0.8691608309745789,0.4650074243545532,-0.15815649926662445,-0.05788412317633629,0.8703879714012146,0.46266695857048035,-0.1586347371339798,-0.057249538600444794,0.8730200529098511,0.4575952887535095,-0.15906186401844025,-0.05667537450790405,0.8753703832626343,0.45300543308258057,-0.15918727219104767,-0.05650543048977852,0.876060426235199,0.4516466557979584,-0.15901418030261993,-0.056739889085292816,0.8751078844070435,0.4535210132598877,-0.158585324883461,-0.057315658777952194,0.8727481961250305,0.45812246203422546,-0.1574382483959198,-0.058820728212594986,0.8664345741271973,0.47015368938446045,-0.1545991748571396,-0.06234604865312576,0.8508105278015137,0.4983307123184204,-0.14906318485736847,-0.06853888928890228,0.8203444480895996,0.5478298664093018,-0.14000993967056274,-0.07719751447439194,0.7705204486846924,0.6170381903648376,-0.12662476301193237,-0.08763713389635086,0.6968580484390259,0.7004818916320801,-0.10837139189243317,-0.09873460978269577,0.5964038968086243,0.7891828417778015,-0.08736733347177505,-0.10830900818109512,0.4808107018470764,0.8657119870185852,-0.06323928385972977,-0.11611618101596832,0.3480265140533447,0.9281138777732849,-0.05475594848394394,-0.1181761622428894,0.3013405501842499,0.9445793032646179,-0.045214977115392685,-0.12010619789361954,0.24883367121219635,0.9600061774253845,-0.03917425870895386,-0.12112514674663544,0.21558861434459686,0.9681506156921387,-0.03959556668996811,-0.12105904519557953,0.21790781617164612,0.9676223993301392,-0.04091378673911095,-0.12084750086069107,0.2251625806093216,0.9659315943717957,-0.04174764081835747,-0.12070988863706589,0.2297520637512207,0.9648317098617554,-0.04234994947910309,-0.12060868740081787,0.2330653965473175,0.9640230536460876,-0.042837124317884445,-0.12052571773529053,0.2357473373413086,0.9633595943450928,-0.043025702238082886,-0.12049326300621033,0.23678529262542725,0.9631006717681885,-0.0428701713681221,-0.12052002549171448,0.2359289675951004,0.963314414024353,-0.0425984188914299,-0.12056652456521988,0.2344335913658142,0.9636856317520142,-0.044293567538261414,-0.120271697640419,0.24376210570335388,0.9613287448883057,-0.05463377758860588,-0.11820347607135773,0.3006685674190521,0.9447970390319824,-0.07568278908729553,-0.11246975511312485,0.4165076017379761,0.8989683389663696,-0.09999140352010727,-0.10290968418121338,0.5502858757972717,0.8225550651550293,-0.11819688230752945,-0.09314034134149551,0.65047687292099,0.7444691061973572,-0.13115964829921722,-0.08435991406440735,0.7218155860900879,0.6742869019508362,-0.13915935158729553,-0.07793546468019485,0.7658402919769287,0.6229360699653625,-0.14366000890731812,-0.07389312237501144,0.7906091213226318,0.5906258225440979,-0.14523953199386597,-0.07238893210887909,0.7993017435073853,0.5786035060882568,-0.1456073373556137,-0.07203197479248047,0.8013254404067993,0.5757495164871216,-0.14586269855499268,-0.07178257405757904,0.8027308583259583,0.5737547278404236,-0.14659172296524048,-0.0710628405213356,0.8067423105239868,0.5680033564567566,-0.14814504981040955,-0.06949199736118317,0.8152917623519897,0.5554487109184265,-0.15007087588310242,-0.06747037172317505,0.8258899450302124,0.5392887592315674,-0.15162409842014313,-0.06577462702989578,0.8344378471374512,0.5257349610328674,-0.1527276188135147,-0.06453194469213486,0.8405106067657471,0.5158021450042725,-0.1535670906305313,-0.06356389075517654,0.8451309204101562,0.5080655217170715,-0.15425294637680054,-0.06275815516710281,0.8489050269126892,0.5016250610351562,-0.15477865934371948,-0.06213105469942093,0.8517981171607971,0.4966118037700653,-0.15503747761249542,-0.061818912625312805,0.8532232046127319,0.4941173791885376,-0.15500956773757935,-0.06185292452573776,0.853069007396698,0.49438807368278503,-0.15483823418617249,-0.06205928698182106,0.85212641954422,0.49603864550590515,-0.15466514229774475,-0.06226709857583046,0.8511735200881958,0.4976998269557953,-0.1544610857963562,-0.06251078844070435,0.8500503897666931,0.49964842200279236,-0.15414223074913025,-0.06288927793502808,0.8482958078384399,0.5026721954345703,-0.1537240892648697,-0.06338083744049072,0.845994770526886,0.5066011548042297,-0.1533130705356598,-0.06385903805494308,0.8437322974205017,0.5104243159294128,-0.15298210084438324,-0.06424059718847275,0.8419109582901001,0.5134740471839905,-0.15274658799171448,-0.0645102858543396,0.8406147956848145,0.5156294107437134,-0.15261507034301758,-0.06466027349233627,0.8398904204368591,0.5168285965919495,-0.1525866687297821,-0.06469244509935379,0.8397346138954163,0.5170859694480896,-0.15261197090148926,-0.06466366350650787,0.8398739695549011,0.5168558359146118,-0.1526351124048233,-0.06463729590177536,0.8400019407272339,0.516644299030304,-0.15267808735370636,-0.06458866596221924,0.8402377367019653,0.5162540674209595,-0.1528218388557434,-0.064424067735672,0.8410296440124512,0.5149409770965576,-0.15311044454574585,-0.06409291923046112,0.8426178097724915,0.5122934579849243,-0.1535273641347885,-0.06361029297113419,0.8449119329452515,0.5084357857704163,-0.15403366088867188,-0.06301721930503845,0.8476991057395935,0.503695011138916,-0.154549241065979,-0.062405750155448914,0.8505350351333618,0.49880871176719666,-0.1549462378025055,-0.061929263174533844,0.8527200818061829,0.49499988555908203,-0.15515081584453583,-0.061681851744651794,0.8538469076156616,0.49302032589912415,-0.15520109236240387,-0.06162068992853165,0.8541229367256165,0.492533802986145,-0.15517908334732056,-0.06164742261171341,0.8540016412734985,0.4927477240562439,-0.15513795614242554,-0.06169722601771355,0.8537759184837341,0.4931453764438629,-0.15509594976902008,-0.06174829229712486,0.8535441756248474,0.49355316162109375,-0.1550581157207489,-0.06179437413811684,0.8533357381820679,0.4939195513725281,-0.15504637360572815,-0.06180821359157562,0.8532720804214478,0.4940316081047058,-0.15508265793323517,-0.06176410987973213,0.8534722924232483,0.4936797022819519,-0.15514378249645233,-0.06169033423066139,0.8538075685501099,0.49308961629867554,-0.15516728162765503,-0.061661817133426666,0.8539367318153381,0.4928620159626007,-0.1551092118024826,-0.06173225864768028,0.8536168336868286,0.49342530965805054,-0.15499523282051086,-0.0618700310587883,0.8529903888702393,0.4945260286331177,-0.1548980325460434,-0.06198716163635254,0.8524563312530518,0.49546176195144653,-0.15485212206840515,-0.06204265356063843,0.8522028923034668,0.49590498208999634,-0.1548335999250412,-0.06206493452191353,0.8521002531051636,0.49608442187309265,-0.15482276678085327,-0.06207815185189247,0.8520406484603882,0.4961884617805481,-0.15482455492019653,-0.062075793743133545,0.8520509004592896,0.4961705803871155,-0.15484169125556946,-0.062055282294750214,0.8521445393562317,0.49600696563720703,-0.15487229824066162,-0.062018364667892456,0.8523131012916565,0.4957122802734375],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1.000000238418579,0.9999996423721313,0.9999999403953552,1,0.9999998211860657],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000021575310711341444,18.75110626220703,0.0000028630443011934403,7.960702532727737e-7,18.751102447509766,0.0000021240032310743118],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.055037736892700195,0.05196232721209526,-0.01394519954919815,0.9970337748527527,0.055037692189216614,0.05196231231093407,-0.013945172540843487,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,1.000000238418579,0.9999999403953552,0.9999999403953552,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,3.299999952316284],"values":[-4.513199329376221,8.246394157409668,-0.08130410313606262,-4.513199329376221,8.246405601501465,-0.08130117505788803],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.6687788963317871,-0.4600946009159088,0.41974231600761414,0.4060347080230713,0.6688723564147949,-0.4596114158630371,0.419747918844223,0.406421959400177,0.6692569255828857,-0.45880523324012756,0.41971951723098755,0.40672898292541504,0.6700936555862427,-0.4577363431453705,0.4195316433906555,0.40674957633018494,0.6711391806602478,-0.4565814733505249,0.41908469796180725,0.4067845046520233,0.6722335815429688,-0.4553098678588867,0.4181712567806244,0.40734222531318665,0.6733548045158386,-0.4539017975330353,0.4165627658367157,0.40870770812034607,0.674216091632843,-0.4530910551548004,0.41444554924964905,0.41033658385276794,0.6735641360282898,-0.45465758442878723,0.4133087694644928,0.41082078218460083,0.6690760254859924,-0.46077725291252136,0.4160866141319275,0.40852615237236023,0.6591508388519287,-0.47213274240493774,0.42476874589920044,0.40271878242492676,0.6449201107025146,-0.486461877822876,0.437742680311203,0.394733190536499,0.6300269365310669,-0.49971866607666016,0.4507814049720764,0.3874836564064026,0.6183486580848694,-0.5093096494674683,0.4599529504776001,0.383003830909729,0.6110838651657104,-0.5158938765525818,0.4642513692378998,0.38065820932388306,0.6062034964561462,-0.5215610265731812,0.4658220410346985,0.3788157105445862,0.6015655994415283,-0.5267821550369263,0.4674062132835388,0.3770289123058319,0.597604513168335,-0.530266284942627,0.46936681866645813,0.3760070502758026,0.596285879611969,-0.5308855175971985,0.46973681449890137,0.37676405906677246,0.598240852355957,-0.5283675789833069,0.4673561453819275,0.3801497519016266,0.5990191698074341,-0.5282506346702576,0.4579288363456726,0.3904210329055786,0.6012579202651978,-0.527132511138916,0.44741690158843994,0.40054768323898315,0.6071326732635498,-0.5235754251480103,0.43649882078170776,0.408322811126709,0.6176139712333679,-0.5160561800003052,0.42486223578453064,0.41440439224243164,0.6287673711776733,-0.506999671459198,0.4125082194805145,0.4212362766265869,0.6357665061950684,-0.5007048845291138,0.39924079179763794,0.4309319257736206,0.6388340592384338,-0.49675890803337097,0.3818874955177307,0.4464119076728821,0.6422474980354309,-0.4907241761684418,0.3536868095397949,0.4707585275173187,0.6450363993644714,-0.4843079149723053,0.31024274230003357,0.5031137466430664,0.6377913951873779,-0.4873647391796112,0.2600194215774536,0.5367380976676941,0.6208184957504272,-0.500947892665863,0.22787046432495117,0.5583105683326721,0.5985063910484314,-0.5211007595062256,0.2142786979675293,0.5694987177848816,0.5836180448532104,-0.5374855399131775,0.2121787965297699,0.5705081224441528,0.5823431015014648,-0.5442289113998413,0.21473294496536255,0.5644300580024719,0.5914523601531982,-0.5408227443695068,0.21661432087421417,0.5574703216552734,0.6037399768829346,-0.531469464302063,0.2150285840034485,0.5538961887359619,0.6138087511062622,-0.5220844745635986,0.21233318746089935,0.5527941584587097,0.6207748651504517,-0.5159125328063965,0.2146100252866745,0.5499231219291687,0.6271154284477234,-0.5119659900665283,0.22502462565898895,0.5422002077102661,0.6367020010948181,-0.5061898827552795,0.24368782341480255,0.5282032489776611,0.6513077616691589,-0.49606627225875854,0.2715446650981903,0.5059447288513184,0.6674674153327942,-0.48374098539352417,0.30937519669532776,0.4740980565547943,0.6762369871139526,-0.4761349558830261,0.34971392154693604,0.4401128590106964,0.6672443747520447,-0.48697322607040405,0.3708482086658478,0.4243980944156647,0.655781626701355,-0.49642616510391235,0.3860962688922882,0.4176616668701172,0.6500868201255798,-0.49672630429267883,0.3970686197280884,0.4159165918827057,0.6508137583732605,-0.48952925205230713,0.4039086699485779,0.41672587394714355,0.6545228958129883,-0.4795193076133728,0.4102049469947815,0.4164048433303833,0.6567224264144897,-0.47085779905319214,0.4205399751663208,0.41249823570251465,0.6558886766433716,-0.46235448122024536,0.43817776441574097,0.4050169885158539,0.6526721715927124,-0.45851168036460876,0.4534069895744324,0.397754043340683,0.6496773362159729,-0.4663374125957489,0.45271438360214233,0.3943328857421875,0.6486854553222656,-0.47291287779808044,0.4465315043926239,0.39518386125564575,0.6492284536361694,-0.4733624756336212,0.44376689195632935,0.3968644440174103,0.6503041982650757,-0.47310614585876465,0.44211673736572266,0.39725038409233093,0.65180903673172,-0.47263333201408386,0.4405105412006378,0.39713120460510254,0.6538578271865845,-0.4709763526916504,0.4387061297893524,0.3977288007736206,0.6567694544792175,-0.4676806926727295,0.43670693039894104,0.3990185260772705,0.6603963971138,-0.46340182423591614,0.43458616733551025,0.4003376364707947,0.6641166806221008,-0.45945319533348083,0.4327004849910736,0.4007769227027893,0.6672852039337158,-0.45673951506614685,0.4309757351875305,0.4004739820957184,0.6701626777648926,-0.45448991656303406,0.428598016500473,0.4007800817489624,0.6731691360473633,-0.45177653431892395,0.4252888858318329,0.4023316204547882,0.6758258938789368,-0.4492979049682617,0.4220126271247864,0.4040991961956024,0.6776734590530396,-0.44788414239883423,0.41952770948410034,0.40516048669815063,0.6789254546165466,-0.4471515119075775,0.41769689321517944,0.4057648181915283,0.6795514822006226,-0.4469330608844757,0.41629308462142944,0.40639957785606384,0.6793874502182007,-0.4474111795425415,0.4155462682247162,0.4069118797779083,0.6784992218017578,-0.44853290915489197,0.4155483543872833,0.4071568250656128,0.6770809292793274,-0.4499402940273285,0.41617992520332336,0.40731993317604065,0.6755028367042542,-0.451272189617157,0.4170791208744049,0.4075467884540558,0.6743036508560181,-0.4521998465061188,0.4177708625793457,0.40779581665992737,0.6737292408943176,-0.45262593030929565,0.4180212914943695,0.4080158472061157,0.6734940409660339,-0.4528035819530487,0.41797497868537903,0.40825435519218445,0.673183262348175,-0.4531005918979645,0.4178831875324249,0.4085312783718109,0.6726001501083374,-0.45374858379364014,0.41789624094963074,0.40875908732414246,0.6716962456703186,-0.45484355092048645,0.41811323165893555,0.4088067412376404,0.6704890727996826,-0.45639681816101074,0.4186208248138428,0.40853768587112427,0.6690836548805237,-0.45825430750846863,0.4194074273109436,0.4079553186893463,0.6677160263061523,-0.4601162374019623,0.42021793127059937,0.40726563334465027,0.6666274070739746,-0.46167662739753723,0.42077645659446716,0.4067060351371765,0.6658613681793213,-0.4627965986728668,0.42108869552612305,0.4063647389411926,0.6653494238853455,-0.4635090231895447,0.4213365316390991,0.4061342477798462,0.6650567650794983,-0.4639242887496948,0.42154228687286377,0.4059258997440338,0.6649338603019714,-0.4640409052371979,0.4217710793018341,0.4057563245296478,0.6649360060691833,-0.46389856934547424,0.42198067903518677,0.40569761395454407,0.6650449633598328,-0.4636167287826538,0.4221179783344269,0.40569838881492615,0.6653121113777161,-0.46329930424690247,0.42213961482048035,0.405600368976593,0.6657533049583435,-0.46293380856513977,0.4220627248287201,0.40537381172180176,0.6663037538528442,-0.4624682366847992,0.4218660891056061,0.4052053689956665,0.6668598055839539,-0.46197155117988586,0.42146116495132446,0.40527868270874023,0.6673014760017395,-0.4615316092967987,0.4209001958370209,0.4056358337402344,0.6676661372184753,-0.4612031877040863,0.42032337188720703,0.40600723028182983,0.6680507063865662,-0.46091389656066895,0.419884592294693,0.40615710616111755,0.6684122085571289,-0.4606512188911438,0.41960418224334717,0.40615013241767883,0.6686349511146545,-0.4604937732219696,0.4194786846637726,0.4060916602611542,0.668718695640564,-0.4604182541370392,0.41949382424354553,0.40602385997772217,0.6687422394752502,-0.46032363176345825,0.4195866286754608,0.40599632263183594,0.6687788963317871,-0.4600946605205536,0.41974231600761414,0.4060346484184265],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999999403953552,1,0.9999998807907104,0.9999998211860657,1],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000047950529733498115,17.9213924407959,-0.000013593928997579496,-0.0000022337203517963644,17.9213924407959,0.0000070342862272809725],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.12597204744815826,-0.35897591710090637,-0.22951777279376984,0.8958733081817627,0.12585395574569702,-0.35774046182632446,-0.2288246899843216,0.8965610861778259,0.12438954412937164,-0.3566005527973175,-0.22863702476024628,0.8972671627998352,0.11910151690244675,-0.35555243492126465,-0.23092184960842133,0.8978153467178345,0.10914371907711029,-0.35426121950149536,-0.23682747781276703,0.8980531096458435,0.09578218311071396,-0.35360315442085266,-0.24343764781951904,0.898069441318512,0.08240842074155807,-0.35597461462020874,-0.24356205761432648,0.8984255790710449,0.07452162355184555,-0.3634050488471985,-0.23070694506168365,0.899531900882721,0.07745108753442764,-0.3776474893093109,-0.20353619754314423,0.8999759554862976,0.09077593684196472,-0.4011895954608917,-0.1662076860666275,0.8962040543556213,0.1071372926235199,-0.43404898047447205,-0.12593957781791687,0.8855859041213989,0.11856845766305923,-0.4718157649040222,-0.08777774125337601,0.8692677617073059,0.12239988893270493,-0.5082471966743469,-0.05270567908883095,0.8508379459381104,0.12134908884763718,-0.5376231074333191,-0.023202048614621162,0.8340848088264465,0.1204829216003418,-0.5568995475769043,-0.004972115159034729,0.8217798471450806,0.12444595247507095,-0.5666053295135498,-0.0005730325938202441,0.8145374655723572,0.13351872563362122,-0.5693855285644531,-0.00591275142505765,0.8111336827278137,0.14241500198841095,-0.5682722926139832,-0.014580827206373215,0.8102912902832031,0.14679387211799622,-0.5642760396003723,-0.022882407531142235,0.8121086955070496,0.1472000926733017,-0.5570481419563293,-0.03063255175948143,0.8167564868927002,0.1516047865152359,-0.548189640045166,-0.04927365109324455,0.8210214376449585,0.1426452249288559,-0.5417289137840271,-0.07513343542814255,0.8249467611312866,0.11502978205680847,-0.5330801010131836,-0.10492106527090073,0.8316161036491394,0.07471250742673874,-0.511223316192627,-0.12950803339481354,0.8463429808616638,0.034793123602867126,-0.4690730571746826,-0.14500315487384796,0.8704792261123657,0.0031827075872570276,-0.40399953722953796,-0.15625590085983276,0.9013092517852783,-0.01988077163696289,-0.3115718960762024,-0.17203707993030548,0.9343077540397644,-0.03988557308912277,-0.19472262263298035,-0.19702988862991333,0.9600372314453125,-0.06093772128224373,-0.0739164724946022,-0.2221667617559433,0.9702911376953125,-0.0794864296913147,0.029743803665041924,-0.23130466043949127,0.9691725373268127,-0.09391888231039047,0.10566533356904984,-0.22656850516796112,0.9636808633804321,-0.08769099414348602,0.15132927894592285,-0.2143229991197586,0.960976243019104,-0.0726487785577774,0.17088036239147186,-0.20301537215709686,0.9614087343215942,-0.06651584804058075,0.17481009662151337,-0.19734998047351837,0.9623253345489502,-0.07107505947351456,0.1771836280822754,-0.19064652919769287,0.9629165530204773,-0.07711729407310486,0.18539859354496002,-0.1769677698612213,0.9635158181190491,-0.07871466875076294,0.19680249691009521,-0.16166532039642334,0.963813841342926,-0.07565655559301376,0.20433621108531952,-0.15757785737514496,0.9631677269935608,-0.06819359958171844,0.202464297413826,-0.1703581064939499,0.9619438648223877,-0.05602900683879852,0.18721885979175568,-0.19220784306526184,0.9616995453834534,-0.039964526891708374,0.15413671731948853,-0.2108786702156067,0.964455783367157,-0.021149443462491035,0.09898331016302109,-0.21829204261302948,0.9706202149391174,0.005534202326089144,0.021769771352410316,-0.21347808837890625,0.9766896367073059,0.060705725103616714,-0.06571676582098007,-0.21086634695529938,0.9734123349189758,0.09073115885257721,-0.15226328372955322,-0.2183619886636734,0.9596363306045532,0.08191709965467453,-0.2299962043762207,-0.22251705825328827,0.9438631534576416,0.050799883902072906,-0.2967360019683838,-0.2171926349401474,0.928544282913208,0.024044223129749298,-0.3497273623943329,-0.20633991062641144,0.9135296940803528,0.01250140368938446,-0.39117202162742615,-0.19059111177921295,0.9002795219421387,0.0020690136589109898,-0.4278687536716461,-0.16056805849075317,0.8894616365432739,-0.0017339263577014208,-0.45199674367904663,-0.13685984909534454,0.8814563751220703,0.021041477099061012,-0.45003464818000793,-0.1491163969039917,0.8802217841148376,0.0470443069934845,-0.4389972388744354,-0.17131608724594116,0.8807491660118103,0.060620471835136414,-0.4322855770587921,-0.18512287735939026,0.8804453015327454,0.06699772924184799,-0.42733365297317505,-0.1960519403219223,0.8800346255302429,0.06868400424718857,-0.42365846037864685,-0.20156937837600708,0.8804349899291992,0.0680890679359436,-0.42076003551483154,-0.2006543129682541,0.8820786476135254,0.06647738069295883,-0.41784659028053284,-0.194785937666893,0.8848974108695984,0.06377287954092026,-0.4148494601249695,-0.1862894743680954,0.8883294463157654,0.05945885181427002,-0.41181403398513794,-0.17841432988643646,0.8916513919830322,0.05551217868924141,-0.40803030133247375,-0.1736362874507904,0.8945837616920471,0.05569743365049362,-0.4029998481273651,-0.17008645832538605,0.897529661655426,0.06080712005496025,-0.39738550782203674,-0.16515575349330902,0.9006169438362122,0.06732305139303207,-0.39245936274528503,-0.1619919389486313,0.9028852581977844,0.07137399911880493,-0.38920828700065613,-0.1652105152606964,0.9033980965614319,0.07287812978029251,-0.38742443919181824,-0.17427697777748108,0.9023406505584717,0.07494814693927765,-0.3855728209018707,-0.18545769155025482,0.9007340669631958,0.07958878576755524,-0.3826969265937805,-0.195132777094841,0.8995175957679749,0.0865786150097847,-0.37967032194137573,-0.2014971673488617,0.8987510204315186,0.09431377798318863,-0.37805676460266113,-0.20514695346355438,0.8978266716003418,0.1010078564286232,-0.3783300817012787,-0.20783622562885284,0.8963636755943298,0.10552238672971725,-0.3798370063304901,-0.21035858988761902,0.8946161866188049,0.10781999677419662,-0.3817557692527771,-0.2125975340604782,0.89299476146698,0.10883781313896179,-0.3834368586540222,-0.2146461457014084,0.8916599750518799,0.10991182178258896,-0.38424479961395264,-0.21669535338878632,0.8906842470169067,0.11207298189401627,-0.383745014667511,-0.21873338520526886,0.8901320695877075,0.11556348949670792,-0.3819809854030609,-0.22107252478599548,0.8898665904998779,0.11994906514883041,-0.3792632520198822,-0.22443239390850067,0.8896076083183289,0.12462238222360611,-0.37597984075546265,-0.22896161675453186,0.8892046809196472,0.1292904168367386,-0.3726435601711273,-0.2333834320306778,0.8887929916381836,0.13390952348709106,-0.3698001205921173,-0.23594020307064056,0.8886216282844543,0.1379680186510086,-0.3679107129573822,-0.23634307086467743,0.8886779546737671,0.14039017260074615,-0.3672216832637787,-0.2358185350894928,0.8887229561805725,0.14053310453891754,-0.3676382303237915,-0.23537904024124146,0.8886446356773376,0.1387522667646408,-0.3688521087169647,-0.23492829501628876,0.8885407447814941,0.1359100341796875,-0.3704751431941986,-0.23400399088859558,0.8885486721992493,0.1327911764383316,-0.37192198634147644,-0.2327224612236023,0.8887524008750916,0.1298896074295044,-0.37249547243118286,-0.23169654607772827,0.889208972454071,0.12748214602470398,-0.371883749961853,-0.2313271313905716,0.8899092674255371,0.12585042417049408,-0.3703671097755432,-0.23137255012989044,0.8907618522644043,0.12516586482524872,-0.3684271574020386,-0.23139500617980957,0.8916565179824829,0.12521667778491974,-0.3663158714771271,-0.23140168190002441,0.89251708984375,0.1256648451089859,-0.36403170228004456,-0.2314748764038086,0.8933692574501038,0.12634243071079254,-0.3616575300693512,-0.23133036494255066,0.8942749500274658,0.1269877701997757,-0.3596261143684387,-0.23093676567077637,0.8951041102409363,0.12721453607082367,-0.3584640920162201,-0.23071953654289246,0.8955939412117004,0.12694619596004486,-0.3582596182823181,-0.23074765503406525,0.8957065939903259,0.1264696717262268,-0.35858044028282166,-0.23046255111694336,0.8957189917564392,0.12597209215164185,-0.35897600650787354,-0.22951805591583252,0.8958731889724731],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999998211860657,0.9999997615814209,0.9999997615814209,0.9999997019767761,0.9999996423721313],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,3.299999952316284],"values":[-2.6725072643785097e-7,17.203693389892578,0.0000030974013043305604,-0.0000032314139843947487,17.20370101928711,0.0000020397135358507512],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.11323847621679306,0.12101450562477112,-0.6050754189491272,0.7787274122238159,-0.1132562980055809,0.12100301682949066,-0.605169951915741,0.778653085231781,-0.11323077976703644,0.12101951241493225,-0.6050344705581665,0.7787594795227051,-0.11305119842290878,0.12113657593727112,-0.6040740609169006,0.7795126438140869,-0.11294735968112946,0.12120403349399567,-0.6035192608833313,0.7799468040466309,-0.11376681923866272,0.12066880613565445,-0.6078979969024658,0.7765025496482849,-0.11644073575735092,0.11887814104557037,-0.6221857070922852,0.7649800777435303,-0.12072572112083435,0.11586146056652069,-0.645081639289856,0.7455676794052124,-0.12505070865154266,0.11261969059705734,-0.6681914925575256,0.7247063517570496,-0.1277204006910324,0.11051225662231445,-0.6824572086334229,0.7111446857452393,-0.12783561646938324,0.11041932553052902,-0.6830729842185974,0.7105469703674316,-0.12585660815238953,0.11199232190847397,-0.6724982261657715,0.7206692099571228,-0.12334418296813965,0.11392322182655334,-0.659073531627655,0.7330960035324097,-0.12159572541713715,0.11522572487592697,-0.6497308015823364,0.7414764761924744,-0.12077431380748749,0.11582625657320023,-0.6453408598899841,0.745340883731842,-0.12032406777143478,0.11615213751792908,-0.6429364085197449,0.7474380135536194,-0.1198590025305748,0.11648688465356827,-0.640450656414032,0.7495916485786438,-0.11956998705863953,0.11669381707906723,-0.6389060616493225,0.7509225010871887,-0.11982540041208267,0.11651090532541275,-0.6402707099914551,0.7497469782829285,-0.12072772532701492,0.11586001515388489,-0.6450924277305603,0.7455582022666931,-0.12171104550361633,0.11514082551002502,-0.6503468155860901,0.7409304976463318,-0.12404395639896393,0.11339263617992401,-0.6628125905990601,0.7296811938285828,-0.12837481498718262,0.10998252779245377,-0.685953676700592,0.7077367901802063,-0.13464567065238953,0.10462720692157745,-0.7194609045982361,0.673275351524353,-0.14177747070789337,0.09784237295389175,-0.7575692534446716,0.6296148896217346,-0.1484491378068924,0.09068551659584045,-0.7932180762290955,0.5835615396499634,-0.15316559374332428,0.08504510670900345,-0.8184192776679993,0.5472636818885803,-0.1544799953699112,0.08337176591157913,-0.8254436254501343,0.5364959239959717,-0.15228186547756195,0.08614368736743927,-0.8136977553367615,0.5543333888053894,-0.14875538647174835,0.09033536165952682,-0.7948547005653381,0.5813066363334656,-0.14637896418571472,0.0930011197924614,-0.7821567058563232,0.5984604358673096,-0.14669443666934967,0.09265400469303131,-0.7838428020477295,0.5962268114089966,-0.14808687567710876,0.09109741449356079,-0.7912829518318176,0.5862105488777161,-0.1485937237739563,0.09052054584026337,-0.793990969657898,0.5824983716011047,-0.14846201241016388,0.09067093580961227,-0.7932876944541931,0.5834659337997437,-0.14865393936634064,0.0904514491558075,-0.7943132519721985,0.5820541381835938,-0.14917141199111938,0.08985597640275955,-0.7970775365829468,0.5782225131988525,-0.1492779552936554,0.0897325873374939,-0.7976468205451965,0.5774285793304443,-0.1486784815788269,0.09042343497276306,-0.7944440245628357,0.5818737149238586,-0.14788517355918884,0.09132544696331024,-0.7902052402496338,0.5876778960227966,-0.14744795858860016,0.09181663393974304,-0.7878690958023071,0.5908393263816833,-0.1471841186285019,0.09211111068725586,-0.78645920753479,0.5927346348762512,-0.14615273475646973,0.09324856847524643,-0.780947744846344,0.6000539660453796,-0.1430162489414215,0.09657793492078781,-0.7641890048980713,0.6214775443077087,-0.13767753541469574,0.10184064507484436,-0.7356621026992798,0.6553431749343872,-0.13347111642360687,0.10567072033882141,-0.7131850719451904,0.6799899339675903,-0.1317289024591446,0.10718312859535217,-0.7038761973381042,0.6897228360176086,-0.1308503895998001,0.10793071240186691,-0.6991815567016602,0.6945317387580872,-0.12996156513690948,0.10867633670568466,-0.694432258605957,0.6993305683135986,-0.13043266534805298,0.10828246176242828,-0.6969497203826904,0.6967950463294983,-0.13070444762706757,0.10805370658636093,-0.698401689529419,0.6953243017196655,-0.12788833677768707,0.11037660390138626,-0.683355450630188,0.7102724313735962,-0.124583899974823,0.11297955363988876,-0.6656976342201233,0.727022111415863,-0.12254779785871506,0.11452074348926544,-0.6548181176185608,0.7369397878646851,-0.12082864344120026,0.11578679829835892,-0.6456310153007507,0.7450869083404541,-0.11962927132844925,0.1166512668132782,-0.6392229199409485,0.7506499886512756,-0.1190364807844162,0.11707314103841782,-0.6360551714897156,0.7533645033836365,-0.11867155879735947,0.11733095347881317,-0.6341059803962708,0.7550232410430908,-0.11834218353033066,0.11756260693073273,-0.632345974445343,0.7565135359764099,-0.11791897565126419,0.11785846203565598,-0.6300844550132751,0.7584181427955627,-0.11734510958194733,0.11825694143772125,-0.6270183324813843,0.7609819173812866,-0.11686575412750244,0.11858715116977692,-0.6244567036628723,0.7631076574325562,-0.11669063568115234,0.11870741844177246,-0.6235216856002808,0.7638799548149109,-0.11632926762104034,0.11895430088043213,-0.6215896010398865,0.7654696106910706,-0.11520342528820038,0.11971524357795715,-0.6155746579170227,0.770366370677948,-0.11345117539167404,0.12087573111057281,-0.6062119007110596,0.7778335809707642,-0.11173463612794876,0.12198478728532791,-0.5970394015312195,0.784970760345459,-0.1105940043926239,0.12270697206258774,-0.5909447073936462,0.7896178364753723,-0.11020094901323318,0.12295320630073547,-0.5888437628746033,0.7912024855613708,-0.11038946360349655,0.12283521890640259,-0.589851438999176,0.7904435992240906,-0.11081866919994354,0.12256558239459991,-0.5921449065208435,0.7887086272239685,-0.11120294034481049,0.12232299149036407,-0.5941982865333557,0.7871463894844055,-0.11142636835575104,0.12218111008405685,-0.5953914523124695,0.7862347364425659,-0.11151649057865143,0.1221238523721695,-0.5958733558654785,0.7858656644821167,-0.11158439517021179,0.12208042293787003,-0.5962371826171875,0.7855867743492126,-0.11171997338533401,0.12199417501688004,-0.5969607830047607,0.7850311398506165,-0.11188604682683945,0.12188805639743805,-0.5978479385375977,0.7843485474586487,-0.11195690929889679,0.12184260785579681,-0.5982272028923035,0.7840563058853149,-0.1118757352232933,0.12189462035894394,-0.5977928042411804,0.7843910455703735,-0.1117585152387619,0.12196961790323257,-0.5971667170524597,0.784872829914093,-0.11177438497543335,0.12195935100317001,-0.5972512364387512,0.7848078608512878,-0.11192689090967178,0.12186194956302643,-0.5980662703514099,0.7841804027557373,-0.1120840534567833,0.121761254966259,-0.5989057421684265,0.7835325598716736,-0.11217280477285385,0.12170425057411194,-0.5993800163269043,0.7831659913063049,-0.11225500702857971,0.12165138870477676,-0.5998193025588989,0.7828260064125061,-0.11242836713790894,0.12153976410627365,-0.6007460951805115,0.7821074724197388,-0.11269516497850418,0.12136756628751755,-0.6021714210510254,0.780998945236206,-0.11294720321893692,0.1212041974067688,-0.6035182476043701,0.7799475789070129,-0.1130746603012085,0.12112122029066086,-0.6041997671127319,0.779414176940918,-0.113067626953125,0.12112587690353394,-0.6041617393493652,0.7794439196586609,-0.11299349367618561,0.12117403745651245,-0.6037659645080566,0.7797538042068481,-0.112916000187397,0.12122451514005661,-0.6033514142036438,0.7800779938697815,-0.11288215219974518,0.1212463229894638,-0.6031714081764221,0.7802187204360962,-0.11292858421802521,0.1212163046002388,-0.6034184694290161,0.7800256013870239,-0.11303078383207321,0.1211499571800232,-0.6039647459983826,0.7795981764793396,-0.11311227828264236,0.12109678983688354,-0.6044005751609802,0.7792568206787109,-0.11314038187265396,0.1210784912109375,-0.604550838470459,0.77913898229599,-0.11316513270139694,0.12106246501207352,-0.604682981967926,0.7790353298187256,-0.11323859542608261,0.12101462483406067,-0.6050752997398376,0.7787274122238159],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1.0000001192092896,0.9999999403953552,1.0000001192092896,1.0000001192092896,1],"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000021625419321935624,19.450496673583984,-7.211825163722096e-7,0.000007135808573366376,19.450496673583984,0.000004123735379835125],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.14937159419059753,-0.14748121798038483,-0.019200196489691734,0.9775319695472717,0.14937162399291992,-0.14748124778270721,-0.01920013502240181,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1,0.9999997019767761,0.9999998807907104,1,0.9999998807907104],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,3.299999952316284],"values":[0.1469002217054367,6.409506797790527,0.2581004798412323,0.146900475025177,6.409510135650635,0.25810056924819946],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.020259669050574303,0.01251528225839138,-0.01119622215628624,0.999653697013855,0.0202596727758646,0.012515280395746231,-0.01119622029364109,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,1.0000003576278687,1.000000238418579,0.9999999403953552,1.0000003576278687,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,3.299999952316284],"values":[-4.9581419858668596e-8,6.815907955169678,-4.053216571264784e-7,7.43493799859607e-8,6.815901756286621,-0.000004301386979932431],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.0387251153588295,0.03278214856982231,0.00079423125134781,0.9987117052078247,0.037096332758665085,0.031564533710479736,0.00021186948288232088,0.9988130927085876,0.03754359483718872,0.029236076399683952,0.00004190756953903474,0.9988672137260437,0.040882159024477005,0.028153810650110245,-0.0003663252864498645,0.9987671971321106,0.0463552251458168,0.03163394331932068,-0.0028917824383825064,0.9984198212623596,0.05382794141769409,0.04135819524526596,-0.008565648458898067,0.9976565837860107,0.0651397705078125,0.056883249431848526,-0.016806986182928085,0.9961117506027222,0.08150181919336319,0.07758564502000809,-0.025444649159908295,0.9933229684829712,0.09945472329854965,0.10275246202945709,-0.032565779983997345,0.9891866445541382,0.11153438687324524,0.1291055977344513,-0.038431331515312195,0.9845886826515198,0.11049912124872208,0.15231004357337952,-0.04442719370126724,0.9811308979988098,0.09243033081293106,0.17269691824913025,-0.05271928757429123,0.9792104363441467,0.056673020124435425,0.19675739109516144,-0.06669805198907852,0.976537823677063,0.006735184229910374,0.2305816411972046,-0.08691184967756271,0.9691404104232788,-0.04659052565693855,0.2720445692539215,-0.10713865607976913,0.9551661610603333,-0.08943963050842285,0.31133684515953064,-0.12114030122756958,0.9382936358451843,-0.11362060159444809,0.33971527218818665,-0.1299324929714203,0.924554705619812,-0.11727020144462585,0.3563973009586334,-0.13842560350894928,0.9165517091751099,-0.10305750370025635,0.3662152886390686,-0.14940190315246582,0.9126579761505127,-0.0793818011879921,0.37305018305778503,-0.1623116284608841,0.9100478291511536,-0.05485795810818672,0.3771365284919739,-0.17407481372356415,0.9079959392547607,-0.03106720559298992,0.37668606638908386,-0.18058928847312927,0.9080362915992737,-0.003769056173041463,0.3709946274757385,-0.17908278107643127,0.9111959934234619,0.02850496768951416,0.36207547783851624,-0.16933129727840424,0.916196346282959,0.06155936047434807,0.3508524000644684,-0.15347297489643097,0.9217153191566467,0.09079359471797943,0.3354211747646332,-0.1349966675043106,0.9279143810272217,0.1141095757484436,0.3183453381061554,-0.11818155646324158,0.9336318373680115,0.13034260272979736,0.30883243680000305,-0.10678960382938385,0.9360712170600891,0.14143119752407074,0.31145617365837097,-0.09986644238233566,0.934354841709137,0.15065819025039673,0.3182166814804077,-0.09235604107379913,0.9314025044441223,0.15439315140247345,0.31645694375038147,-0.08692284673452377,0.9319131970405579,0.14947520196437836,0.3035237491130829,-0.09004063904285431,0.9367087483406067,0.14352746307849884,0.28882136940956116,-0.09890420734882355,0.9413820505142212,0.14270427823066711,0.2814982235431671,-0.10485218465328217,0.9430801868438721,0.1428109109401703,0.28102564811706543,-0.10505466908216476,0.9431824684143066,0.13979777693748474,0.2818177342414856,-0.10391156375408173,0.9435241222381592,0.13496021926403046,0.28062865138053894,-0.10400787740945816,0.9445717334747314,0.13128188252449036,0.2791813015937805,-0.10269555449485779,0.9456619620323181,0.1303711086511612,0.2812933921813965,-0.0977749153971672,0.9456835985183716,0.1305173933506012,0.28841379284858704,-0.09038583934307098,0.9442526698112488,0.12884216010570526,0.2969900071620941,-0.08479539304971695,0.9423409700393677,0.12443698942661285,0.3013690114021301,-0.08448482304811478,0.9415702223777771,0.11696120351552963,0.30059507489204407,-0.08837957680225372,0.9424180388450623,0.1061779037117958,0.3002045750617981,-0.09192699193954468,0.9434791803359985,0.09525223076343536,0.30553117394447327,-0.08994506299495697,0.9431264996528625,0.0896715596318245,0.31425297260284424,-0.08268938958644867,0.9414704442024231,0.09153193980455399,0.3177448809146881,-0.0767972320318222,0.9406180381774902,0.09739073365926743,0.3073861002922058,-0.07208593934774399,0.9438392519950867,0.10280955582857132,0.2799600064754486,-0.06177403777837753,0.9524896740913391,0.1031160056591034,0.24028383195400238,-0.04115031287074089,0.964332640171051,0.10252690315246582,0.1987212747335434,-0.02311687543988228,0.9744042754173279,0.10768682509660721,0.1649472415447235,-0.02373400144279003,0.9801186919212341,0.10570964962244034,0.14305976033210754,-0.032595667988061905,0.9835125803947449,0.0963592678308487,0.1306033581495285,-0.0386471301317215,0.9859838485717773,0.0897604301571846,0.1224227175116539,-0.042079538106918335,0.9875146150588989,0.08678807318210602,0.11484408378601074,-0.04178772494196892,0.9887024164199829,0.08419825881719589,0.1058642715215683,-0.037708867341279984,0.9900916218757629,0.08003359287977219,0.09448499977588654,-0.0322081595659256,0.9917811751365662,0.07471166551113129,0.08109694719314575,-0.027622705325484276,0.9935182332992554,0.0700637623667717,0.06730291992425919,-0.02323334477841854,0.9949982762336731,0.06654047966003418,0.054421115666627884,-0.017226848751306534,0.9961495399475098,0.062002032995224,0.04215502366423607,-0.009482496418058872,0.9971402883529663,0.05495065078139305,0.029587525874376297,-0.001780342892743647,0.9980490803718567,0.04736293852329254,0.01783416047692299,0.004030432086437941,0.9987103939056396,0.04230112582445145,0.009725237265229225,0.007386696524918079,0.9990302920341492,0.04054547846317291,0.006266553420573473,0.008918634615838528,0.9991182088851929,0.04130823537707329,0.005773790180683136,0.009222335182130337,0.9990872144699097,0.04379570856690407,0.00627158535644412,0.00889038760215044,0.998981237411499,0.04696311801671982,0.00690047862008214,0.008152701891958714,0.9988394975662231,0.049344148486852646,0.007605543825775385,0.006955726072192192,0.9987286329269409,0.05024772882461548,0.008407355286180973,0.005510338582098484,0.9986862540245056,0.050267357379198074,0.009206500835716724,0.004237250424921513,0.9986844062805176,0.050208985805511475,0.010285879485309124,0.003225471591576934,0.9986805319786072,0.05015374347567558,0.012396365404129028,0.0024808573070913553,0.9986615180969238,0.0497475229203701,0.015879647806286812,0.002227788558229804,0.998633086681366,0.04877259209752083,0.02021435648202896,0.0022766017355024815,0.9986027479171753,0.047298964112997055,0.024735063314437866,0.001965816132724285,0.9985725283622742,0.04548364132642746,0.02921370603144169,0.0013374118134379387,0.9985369443893433,0.04362069442868233,0.03323199227452278,0.0012564104981720448,0.9984945058822632,0.0422452948987484,0.03579328954219818,0.0019602677784860134,0.9984639883041382,0.04158079996705055,0.03650327026844025,0.0027640180196613073,0.9984642863273621,0.041460298001766205,0.03645589202642441,0.002969768363982439,0.9984704256057739,0.04154684767127037,0.03693970665335655,0.0024097643326967955,0.9984505772590637,0.041547633707523346,0.03808540105819702,0.001493237097747624,0.9984092712402344,0.04142952337861061,0.03898784518241882,0.0008728539105504751,0.9983800649642944,0.04128193110227585,0.03910398110747337,0.0006139944307506084,0.9983818531036377,0.041144825518131256,0.03869827091693878,0.0006184113444760442,0.9984033107757568,0.04084736108779907,0.038162846118211746,0.0012202857760712504,0.9984355568885803,0.04048015549778938,0.03760340064764023,0.0026581280399113894,0.9984689354896545,0.0404677651822567,0.036771100014448166,0.004068492446094751,0.9984957575798035,0.04112996906042099,0.03551660478115082,0.004012292250990868,0.9985142946243286,0.04188235104084015,0.03421695530414581,0.0023428541608154774,0.9985337257385254,0.04175042361021042,0.033344998955726624,0.0005752395954914391,0.9985713362693787,0.040456149727106094,0.033056922256946564,0.000023589263946632855,0.9986343383789062,0.03884999454021454,0.033118706196546555,0.00037332435022108257,0.9986960291862488,0.03802463412284851,0.03322127088904381,0.0007193595520220697,0.9987241625785828,0.03824056312441826,0.03320186957716942,0.0007493001758120954,0.9987165331840515,0.03877770155668259,0.033036891371011734,0.0006813621148467064,0.9987013339996338,0.038725148886442184,0.032782141119241714,0.000794240680988878,0.9987117052078247],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999998211860657,1,1,1,1.0000001192092896],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,3.299999952316284],"values":[0.5746004581451416,51.816898345947266,-20.5226993560791,0.5746001601219177,51.81689453125,-20.5226993560791],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.18743081390857697,9.866570138683528e-8,-0.005247786175459623,0.9822638034820557,-0.18743081390857697,1.0423602248010866e-7,-0.005247785709798336,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,3.299999952316284],"values":[1.000000238418579,1.0000003576278687,1.000000238418579,1.000000238418579,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.5746001601219177,4.330291748046875,20.522708892822266,-0.5745998620986938,4.330296993255615,20.522708892822266],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.6296830773353577,-7.914392341490384e-8,0.017631012946367264,0.7766520380973816,0.6296831369400024,-7.434732651745435e-8,0.017631009221076965,0.7766519784927368],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,1.0000003576278687,1.000000238418579,0.9999999403953552,1.0000003576278687,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,3.299999952316284],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"1338c619-851e-4543-95ae-811015429759","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/st1able-sword-inward-slash.json b/public/anims/baked/sword_shield/st1able-sword-inward-slash.json new file mode 100644 index 0000000..a45d79c --- /dev/null +++ b/public/anims/baked/sword_shield/st1able-sword-inward-slash.json @@ -0,0 +1 @@ +{"name":"sword_shield/attack2-inward-slash","duration":2.2333333492279053,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[-0.5079180002212524,51.12571716308594,-0.2907598614692688,-0.5229778289794922,51.06105041503906,-0.3585361838340759,-0.584068775177002,51.01758575439453,-0.4123896360397339,-0.7182742357254028,51.07905578613281,-0.4693615436553955,-0.9413137435913086,51.28986358642578,-0.5418160557746887,-1.2631949186325073,51.64253616333008,-0.6307504773139954,-1.7079110145568848,52.107704162597656,-0.7249933481216431,-2.301510810852051,52.645973205566406,-0.7942993640899658,-3.042665481567383,53.19750213623047,-0.8128221035003662,-3.894700050354004,53.74564743041992,-0.7612035870552063,-4.847873210906982,54.266876220703125,-0.6535525321960449,-5.874859809875488,54.704837799072266,-0.49622857570648193,-6.930357456207275,55.07991027832031,-0.2684723138809204,-7.9572343826293945,55.374977111816406,0.022907018661499023,-8.907713890075684,55.42060852050781,0.35676026344299316,-9.782096862792969,55.267547607421875,0.7708151340484619,-10.570422172546387,54.97135543823242,1.2501760721206665,-11.231806755065918,54.50218200683594,1.7524858713150024,-11.788369178771973,53.904151916503906,2.317298173904419,-12.286933898925781,53.197792053222656,2.8558247089385986,-12.717623710632324,52.46310806274414,3.300978422164917,-12.885822296142578,52.13987731933594,3.49279522895813,-13.031959533691406,51.836639404296875,3.6863317489624023,-13.160112380981445,51.54629135131836,3.883962869644165,-13.273290634155273,51.268653869628906,4.077948093414307,-13.370118141174316,51.0080680847168,4.264316558837891,-13.443353652954102,50.76934051513672,4.446333885192871,-13.486588478088379,50.58113098144531,4.602141857147217,-13.505548477172852,50.227806091308594,4.9681477546691895,-13.4729642868042,50.049102783203125,5.355801582336426,-13.284398078918457,50.44222640991211,5.645642280578613,-12.922734260559082,51.649658203125,5.721325874328613,-12.343721389770508,53.79508590698242,5.5256028175354,-11.517045974731445,56.55458068847656,5.244359493255615,-10.424386024475098,59.16839599609375,5.031277656555176,-9.182827949523926,60.79032897949219,4.668849945068359,-7.834819793701172,61.449344635009766,3.9740090370178223,-6.525876045227051,61.759521484375,3.769320249557495,-5.3347086906433105,61.65555953979492,4.212440490722656,-4.578153610229492,61.01042938232422,5.268311023712158,-4.22838020324707,60.46778106689453,6.188136577606201,-4.037709712982178,60.05462646484375,6.823489665985107,-3.783000946044922,59.4825439453125,7.40582275390625,-3.6154634952545166,58.862205505371094,7.883398056030273,-3.5597214698791504,58.33682632446289,8.264135360717773,-3.509371280670166,57.989505767822266,8.448931694030762,-3.449718952178955,57.824764251708984,8.411269187927246,-3.3719642162323,57.6676139831543,8.209074020385742,-3.2779974937438965,57.37483215332031,7.883938312530518,-3.1976709365844727,56.901126861572266,7.446980953216553,-3.1092183589935303,56.2307014465332,6.914361476898193,-3.051588773727417,55.35566711425781,6.2949652671813965,-2.992673635482788,54.351104736328125,5.672277450561523,-2.888613700866699,53.233951568603516,5.052830696105957,-2.7479658126831055,52.014373779296875,4.396332740783691,-2.5433149337768555,50.900352478027344,3.7820255756378174,-2.2761783599853516,50.071990966796875,3.243281602859497,-1.9659621715545654,49.501407623291016,2.7829203605651855,-1.684400200843811,49.232688903808594,2.3180580139160156,-1.4436061382293701,49.29243469238281,1.852857232093811,-1.2322429418563843,49.53507995605469,1.425687313079834,-1.0557552576065063,49.9114875793457,1.0192281007766724,-0.8953961133956909,50.327545166015625,0.6285987496376038,-0.7352820634841919,50.697662353515625,0.2769346237182617,-0.6049215793609619,50.97390365600586,-0.0004749298095703125,-0.5310401916503906,51.116111755371094,-0.18400061130523682,-0.5079199075698853,51.1257209777832,-0.29075926542282104],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.27613547444343567,-0.38757723569869995,0.2166111171245575,0.8524158000946045,0.27985745668411255,-0.3796048164367676,0.21206693351268768,0.8559250235557556,0.28312644362449646,-0.3702976107597351,0.20497475564479828,0.8606419563293457,0.2852492332458496,-0.35844066739082336,0.1957133561372757,0.8670925498008728,0.28616636991500854,-0.34293878078460693,0.18591365218162537,0.8751788139343262,0.28648534417152405,-0.32312536239624023,0.17738470435142517,0.8843364119529724,0.2870737612247467,-0.2988300919532776,0.170575812458992,0.8939760327339172,0.28890836238861084,-0.27031514048576355,0.16453461349010468,0.9035430550575256,0.29245296120643616,-0.23801864683628082,0.15802402794361115,0.9126043915748596,0.2977733612060547,-0.20247508585453033,0.1495051383972168,0.9208599925041199,0.3039941191673279,-0.165130615234375,0.13616874814033508,0.9283197522163391,0.30624452233314514,-0.1271539032459259,0.12212913483381271,0.9354842305183411,0.307721346616745,-0.0867716372013092,0.11093378812074661,0.9409951567649841,0.308055579662323,-0.04594752937555313,0.1010708138346672,0.9448678493499756,0.3098241090774536,0.0008509453036822379,0.08858521282672882,0.9466577768325806,0.31130605936050415,0.052365679293870926,0.07683897763490677,0.9457495212554932,0.3086414635181427,0.1079009398818016,0.06510048359632492,0.942793607711792,0.30475905537605286,0.16721588373184204,0.051171187311410904,0.9362384080886841,0.3021138906478882,0.2236267626285553,0.03807388246059418,0.9258880615234375,0.2977144122123718,0.2720698416233063,0.0256960391998291,0.9147042632102966,0.2899596095085144,0.3074323534965515,0.01550073828548193,0.9061834812164307,0.28588420152664185,0.3198796808719635,0.01159887108951807,0.9032235145568848,0.28191375732421875,0.33024948835372925,0.007973137311637402,0.9007754325866699,0.27808263897895813,0.33883461356163025,0.0044488124549388885,0.8988000154495239,0.27422481775283813,0.3456403315067291,0.0009814531076699495,0.8974032402038574,0.27028071880340576,0.35071176290512085,-0.0024232796858996153,0.8966290950775146,0.2665300965309143,0.35420405864715576,-0.0055464101023972034,0.8963651657104492,0.26368406414985657,0.35631147027015686,-0.007832844741642475,0.8963545560836792,0.25779321789741516,0.3576197922229767,-0.010470466688275337,0.8975194096565247,0.24967682361602783,0.34870445728302,-0.012890170328319073,0.9032721519470215,0.24978426098823547,0.320933997631073,-0.00799103919416666,0.9135345220565796,0.25333866477012634,0.26082709431648254,0.009493641555309296,0.9315034747123718,0.2528271973133087,0.1534242331981659,0.04041394963860512,0.9544140696525574,0.2542855441570282,0.0029978896491229534,0.0913710817694664,0.9627986550331116,0.2572968006134033,-0.17384885251522064,0.15123821794986725,0.9384572505950928,0.2562500834465027,-0.32896801829338074,0.20086435973644257,0.886436402797699,0.25201326608657837,-0.42531803250312805,0.22809481620788574,0.8387888669967651,0.2582032382488251,-0.47185447812080383,0.22575072944164276,0.8122321963310242,0.28977152705192566,-0.46408823132514954,0.2119901478290558,0.809762179851532,0.3179311454296112,-0.43311572074890137,0.211273193359375,0.8165134787559509,0.32660818099975586,-0.4187275767326355,0.23252496123313904,0.8148168325424194,0.32392561435699463,-0.4244278371334076,0.24186548590660095,0.8102063536643982,0.3122710883617401,-0.44056448340415955,0.24346168339252472,0.8056774735450745,0.297788143157959,-0.45799019932746887,0.24758893251419067,0.8001668453216553,0.29203054308891296,-0.4682028591632843,0.25058385729789734,0.7954319715499878,0.2932009994983673,-0.47068139910697937,0.2516354024410248,0.7932035326957703,0.2935866117477417,-0.47066864371299744,0.2533697187900543,0.7925161123275757,0.2897655963897705,-0.47135910391807556,0.2548241913318634,0.7930455207824707,0.2835940718650818,-0.4715113639831543,0.25357577204704285,0.7955820560455322,0.2791890799999237,-0.46775704622268677,0.24955123662948608,0.8006129264831543,0.2772088944911957,-0.4580059349536896,0.24348948895931244,0.8087637424468994,0.2780115008354187,-0.44586625695228577,0.2385624349117279,0.8167012333869934,0.2809644043445587,-0.4349687695503235,0.23334205150604248,0.823050856590271,0.2813645303249359,-0.4256296157836914,0.22725704312324524,0.8294743895530701,0.2777712941169739,-0.4189513921737671,0.22413630783557892,0.8349166512489319,0.2732049822807312,-0.41762760281562805,0.22344836592674255,0.8372676372528076,0.2688145339488983,-0.4192489981651306,0.2213447093963623,0.8384364247322083,0.2645740509033203,-0.4207053482532501,0.21909700334072113,0.8396452069282532,0.2615015208721161,-0.4218975007534027,0.22002001106739044,0.8397682309150696,0.25833860039711,-0.42294156551361084,0.2211393564939499,0.8399279713630676,0.2581618130207062,-0.4214440584182739,0.2222185879945755,0.8404501080513,0.260354608297348,-0.4179733395576477,0.2220572978258133,0.8415488004684448,0.26379141211509705,-0.41283127665519714,0.22185780107975006,0.8430679440498352,0.2669742703437805,-0.4073420763015747,0.22164320945739746,0.8447908163070679,0.2697811424732208,-0.4014340043067932,0.22086763381958008,0.8469275832176208,0.2726856768131256,-0.39484450221061707,0.2193010449409485,0.8494983315467834,0.27613553404808044,-0.3875771760940552,0.21661102771759033,0.8524158596992493],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1,0.9999998211860657,0.9999997615814209,0.9999998807907104,0.9999997615814209,0.9999998807907104],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,2.2333333492279053],"values":[8.30560302734375,-6.711594581604004,5.719295978546143,8.305602073669434,-6.7115960121154785,5.719297885894775],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.6572067737579346,0.10270853340625763,-0.2329850196838379,0.7093998789787292,0.6520698666572571,0.10289090126752853,-0.22765517234802246,0.7158152461051941,0.6478481888771057,0.10424073040485382,-0.22025519609451294,0.7217439413070679,0.6458737254142761,0.10550284385681152,-0.2123444527387619,0.7256900668144226,0.6471955180168152,0.10470075160264969,-0.20295579731464386,0.7273133397102356,0.6519106030464172,0.10124347358942032,-0.19009193778038025,0.7270676493644714,0.6589365601539612,0.09274061024188995,-0.17343100905418396,0.7260326743125916,0.6661050319671631,0.08007925003767014,-0.15679705142974854,0.7247800230979919,0.6728061437606812,0.07067213952541351,-0.13797983527183533,0.7233940362930298,0.6785279512405396,0.06323356926441193,-0.11495234817266464,0.7227636575698853,0.6824713945388794,0.06107725203037262,-0.09180507808923721,0.7225470542907715,0.6885583996772766,0.06120935082435608,-0.06888881325721741,0.7193018198013306,0.6949412226676941,0.05857197940349579,-0.04330940917134285,0.715367317199707,0.7009516954421997,0.05545031279325485,-0.017425671219825745,0.7108364105224609,0.7003074884414673,0.05261959880590439,0.014809693209826946,0.7117453217506409,0.6956695914268494,0.04332873597741127,0.0489388145506382,0.7153820991516113,0.6906159520149231,0.0276530459523201,0.08469106256961823,0.7177132964134216,0.6813570857048035,0.008981537073850632,0.12288195639848709,0.7215067148208618,0.668458104133606,-0.01246055867522955,0.15494608879089355,0.7273240685462952,0.6556717157363892,-0.03428197652101517,0.17596936225891113,0.7334535717964172,0.6458824276924133,-0.05135060474276543,0.18691390752792358,0.738418698310852,0.6420442461967468,-0.05670035257935524,0.1905897557735443,0.7404322028160095,0.6384057998657227,-0.06050581857562065,0.19396919012069702,0.7423967719078064,0.6349097490310669,-0.06342743337154388,0.1967589557170868,0.744414210319519,0.6317583322525024,-0.06570902466773987,0.19864994287490845,0.7463926076889038,0.6289881467819214,-0.06692928075790405,0.19990204274654388,0.7482870817184448,0.6264092922210693,-0.06687024235725403,0.2010151594877243,0.7501550912857056,0.6242502331733704,-0.06628791242837906,0.20203715562820435,0.7517303824424744,0.6216362714767456,-0.06771779805421829,0.2018415927886963,0.7538187503814697,0.6252180933952332,-0.06371048092842102,0.19236701726913452,0.7536830902099609,0.6356605887413025,-0.050646886229515076,0.173409104347229,0.7505330443382263,0.6633355021476746,-0.03048195317387581,0.13373519480228424,0.7356438040733337,0.714580774307251,-0.0035974259953945875,0.05836955085396767,0.6971043348312378,0.7740387320518494,0.031110836192965508,-0.026272626593708992,0.6318274736404419,0.8275420665740967,0.07115624099969864,-0.09527166932821274,0.5486658811569214,0.8637173771858215,0.0966477319598198,-0.13634975254535675,0.47545793652534485,0.8784335255622864,0.10757852345705032,-0.15280978381633759,0.4398074746131897,0.8682727217674255,0.11426147073507309,-0.1889084130525589,0.4442525804042816,0.8294775485992432,0.10798329859972,-0.2123502790927887,0.5051869750022888,0.8000744581222534,0.11140725761651993,-0.18074654042720795,0.561070442199707,0.7863296866416931,0.08057521283626556,-0.1756981760263443,0.5867908000946045,0.7772908806800842,0.05754406750202179,-0.1847858428955078,0.5986332893371582,0.7743802070617676,0.052669622004032135,-0.188178151845932,0.6017891764640808,0.7778393626213074,0.051637109369039536,-0.19015130400657654,0.5967763662338257,0.7792861461639404,0.05140318721532822,-0.18869590759277344,0.5953693389892578,0.7764027118682861,0.048485275357961655,-0.18714378774166107,0.5998544096946716,0.7737483382225037,0.04484966769814491,-0.1912274807691574,0.6022741198539734,0.7749567031860352,0.0466187484562397,-0.1988052874803543,0.5981181263923645,0.777151882648468,0.05355655774474144,-0.20650801062583923,0.5920482277870178,0.7731123566627502,0.061739224940538406,-0.21257030963897705,0.5943899750709534,0.7622207403182983,0.0660756379365921,-0.21661454439163208,0.6064088344573975,0.7482540011405945,0.06900914758443832,-0.2210410088300705,0.6216868758201599,0.7327479124069214,0.0753662958741188,-0.22516222298145294,0.637732207775116,0.7177936434745789,0.08507352322340012,-0.2266826331615448,0.6528014540672302,0.7051292061805725,0.09232263267040253,-0.2280620038509369,0.6650241017341614,0.6960609555244446,0.09957434237003326,-0.23189692199230194,0.6721665859222412,0.6869176030158997,0.10502923280000687,-0.23223207890987396,0.6805742383003235,0.681258499622345,0.11008914560079575,-0.22924958169460297,0.6864487528800964,0.677412211894989,0.11137261986732483,-0.2286674976348877,0.6902318596839905,0.6768440008163452,0.11090564727783203,-0.2303551286458969,0.6903032660484314,0.676182746887207,0.11041492968797684,-0.229117751121521,0.6914408802986145,0.6748610734939575,0.11059356480836868,-0.2273634523153305,0.6932802796363831,0.6726086139678955,0.10960524529218674,-0.22665703296661377,0.6958526372909546,0.6702220439910889,0.10826539248228073,-0.2268882393836975,0.6982855796813965,0.6674714088439941,0.10671466588973999,-0.2279757261276245,0.7008002996444702,0.6632559895515442,0.10449560731649399,-0.2303917407989502,0.7043378353118896,0.6572060585021973,0.10270862281322479,-0.2329850047826767,0.7094005346298218],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999998807907104,1.000000238418579,0.9999992847442627,0.9999998807907104,1,0.9999994039535522],"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.0000016608510122750886,25.81270408630371,0.0000064036530602606945,-0.0000029921095574536594,25.812705993652344,3.053681325582147e-7],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.6703216433525085,0.01315265242010355,-0.0038397640455514193,0.7419441342353821,0.6704757809638977,0.012300172820687294,-0.006189706269651651,0.7418035864830017,0.6704860329627991,0.010433399118483067,-0.010317288339138031,0.7417770624160767,0.6680923104286194,0.009203732013702393,-0.013159411028027534,0.7439051270484924,0.6614614129066467,0.008156830444931984,-0.014785817824304104,0.7497890591621399,0.65024733543396,0.006872096098959446,-0.013125542551279068,0.7595781087875366,0.634268045425415,0.008135532028973103,-0.0122359748929739,0.7729735970497131,0.6143375635147095,0.016700878739356995,-0.00934585090726614,0.788811206817627,0.5920690894126892,0.023810910061001778,-0.0010532718151807785,0.8055347204208374,0.5678951740264893,0.03127843886613846,0.006184765603393316,0.8224831223487854,0.5430872440338135,0.042295362800359726,0.015313523821532726,0.8384705185890198,0.518794059753418,0.05417894944548607,0.026533611118793488,0.852768063545227,0.49283862113952637,0.06483391672372818,0.03614487871527672,0.8669488430023193,0.4668113589286804,0.07582248002290726,0.04583617299795151,0.879907488822937,0.4456244707107544,0.08549702912569046,0.05490854009985924,0.8894347548484802,0.428992360830307,0.09674932062625885,0.060197360813617706,0.8960922956466675,0.4164312481880188,0.1124521866440773,0.062133822590112686,0.9000439047813416,0.40856683254241943,0.1312541514635086,0.06296951323747635,0.9010440111160278,0.4069708287715912,0.14858309924602509,0.06299508363008499,0.899071455001831,0.4146323800086975,0.1480971872806549,0.05588825047016144,0.8961158990859985,0.4299022853374481,0.14591604471206665,0.051585856825113297,0.8895119428634644,0.4383470416069031,0.14439630508422852,0.05104878172278404,0.8856610655784607,0.4472121000289917,0.14284294843673706,0.050975143909454346,0.8814753293991089,0.45676755905151367,0.1414329707622528,0.050630200654268265,0.8768104910850525,0.4670812785625458,0.14013895392417908,0.04975898563861847,0.8716192841529846,0.4779287874698639,0.13873906433582306,0.04895145073533058,0.865990400314331,0.488889217376709,0.13715912401676178,0.04884399101138115,0.8601098656654358,0.4980473220348358,0.135829359292984,0.04913882911205292,0.8550348877906799,0.5189058780670166,0.13498128950595856,0.046922147274017334,0.8428019285202026,0.5432206392288208,0.13131390511989594,0.044309064745903015,0.8280729055404663,0.5547539591789246,0.12942400574684143,0.04614686220884323,0.8205900192260742,0.5525778532028198,0.13336196541786194,0.047773782163858414,0.8213343024253845,0.5330794453620911,0.14721307158470154,0.04353974387049675,0.8320209980010986,0.4867636561393738,0.12218282371759415,0.03221603482961655,0.8643463850021362,0.4233916699886322,0.022160988301038742,0.021306825801730156,0.9054250121116638,0.3658773899078369,-0.001032671774737537,0.05367877334356308,0.9291131496429443,0.34102848172187805,-0.004347668495029211,0.07320711761713028,0.9371880888938904,0.35670700669288635,0.004458554554730654,0.06560412049293518,0.93189936876297,0.40315234661102295,0.01557534746825695,0.024053186178207397,0.9146841764450073,0.44885292649269104,0.003941997420042753,0.037061259150505066,0.892828106880188,0.4713605046272278,-0.0009675318724475801,0.03724690526723862,0.8811532258987427,0.5015414357185364,-0.004808345343917608,0.01807708479464054,0.8649314045906067,0.5392419099807739,-0.010929293930530548,0.004741589538753033,0.8420666456222534,0.5673452615737915,-0.016768883913755417,0.001575356931425631,0.8233077526092529,0.5851402282714844,-0.02049924060702324,0.0002834568440448493,0.8106729388237,0.5951650142669678,-0.02006111666560173,-0.005132874473929405,0.8033366799354553,0.5980600714683533,-0.01863149367272854,-0.008184922859072685,0.8011928796768188,0.5974985361099243,-0.019030805677175522,-0.002511427504941821,0.8016402125358582,0.5988882780075073,-0.019197626039385796,0.0050339787267148495,0.8005866408348083,0.6083964109420776,-0.01813601702451706,0.005296391434967518,0.7934083342552185,0.6249008774757385,-0.016564413905143738,-0.002544957213103771,0.7805241942405701,0.6417617797851562,-0.014148620888590813,-0.010063701309263706,0.7667074799537659,0.6581405401229858,-0.008897126652300358,-0.01636457070708275,0.7526646852493286,0.6758854389190674,-0.0020708180963993073,-0.021332992240786552,0.7366949915885925,0.6933767199516296,0.0059338100254535675,-0.024649038910865784,0.7201290726661682,0.7058698534965515,0.018184533342719078,-0.02359907515347004,0.7077147364616394,0.7184653878211975,0.024483060464262962,-0.026632359251379967,0.6946213245391846,0.7266101241111755,0.02691664919257164,-0.02799859270453453,0.6859513521194458,0.7285153865814209,0.027955569326877594,-0.02783382683992386,0.6838926076889038,0.7242993712425232,0.028187744319438934,-0.02683718316257,0.6883862614631653,0.7169817686080933,0.025955146178603172,-0.023723609745502472,0.6962044835090637,0.7071579694747925,0.02267702855169773,-0.019849993288517,0.7064130306243896,0.6960190534591675,0.018965376541018486,-0.016410725191235542,0.7175851464271545,0.6855922341346741,0.015147832222282887,-0.012419755570590496,0.7277222871780396,0.6771762371063232,0.012019762769341469,-0.008968601934611797,0.7356680631637573,0.6720475554466248,0.011375751346349716,-0.006408271845430136,0.7403928637504578,0.6703221797943115,0.013153200969099998,-0.003839378012344241,0.7419436573982239],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,1,1,0.9999999403953552,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.000002680405259525287,22.742708206176758,0.0000011807426290033618,6.643128926953068e-7,22.742706298828125,-0.000001576986051077256],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[-0.6105950474739075,-0.12654082477092743,0.11272180825471878,0.7735987305641174,-0.6074991226196289,-0.12060955911874771,0.11350345611572266,0.776862382888794,-0.603993833065033,-0.11366202682256699,0.11576566845178604,0.7803016901016235,-0.5995314717292786,-0.10503975301980972,0.11685575544834137,0.7847760319709778,-0.5933135151863098,-0.09433822333812714,0.11726082116365433,0.7907776832580566,-0.5851555466651917,-0.0852956622838974,0.1155492290854454,0.798101544380188,-0.5749030113220215,-0.07964283972978592,0.11392039805650711,0.8063284754753113,-0.5633391737937927,-0.07799200713634491,0.10976281762123108,0.8151799440383911,-0.5504822134971619,-0.07552654296159744,0.1044144332408905,0.8248410224914551,-0.5363401770591736,-0.07423334568738937,0.10145243257284164,0.8345873355865479,-0.5215988755226135,-0.07299249619245529,0.09916036576032639,0.8442593812942505,-0.507885217666626,-0.07205692678689957,0.09736573696136475,0.8528659343719482,-0.49368685483932495,-0.07226040959358215,0.0973011776804924,0.8611528277397156,-0.47991764545440674,-0.07274919748306274,0.09803728014230728,0.8687780499458313,-0.46616941690444946,-0.07344313710927963,0.09969640523195267,0.8759867548942566,-0.453425794839859,-0.07447584718465805,0.10226952284574509,0.8822693824768066,-0.44246619939804077,-0.07608634233474731,0.10605025291442871,0.8872361183166504,-0.43248727917671204,-0.07804767042398453,0.11077836900949478,0.8913986086845398,-0.4248585104942322,-0.07532785832881927,0.11625847220420837,0.8945976495742798,-0.4184049963951111,-0.05023874342441559,0.1292591243982315,0.8976109623908997,-0.41672858595848083,-0.02477130852639675,0.13924214243888855,0.8979617357254028,-0.41754478216171265,-0.01467693131417036,0.14255066215991974,0.8972849249839783,-0.419279545545578,-0.005869830027222633,0.145396888256073,0.8961194157600403,-0.4218892753124237,0.002137189032509923,0.14806149899959564,0.8944734334945679,-0.4252742528915405,0.009485320188105106,0.15043845772743225,0.8924237489700317,-0.42936158180236816,0.015858381986618042,0.1521563082933426,0.8900818228721619,-0.4340996742248535,0.020693713799118996,0.15290606021881104,0.8875522613525391,-0.43850427865982056,0.02359490469098091,0.15293508768081665,0.8853068351745605,-0.4504050314426422,0.026558661833405495,0.15133723616600037,0.8795038461685181,-0.4663548171520233,0.0300618764013052,0.14528681337833405,0.8720672130584717,-0.4830915927886963,0.023675713688135147,0.1351928561925888,0.864745557308197,-0.5037363171577454,0.0008016927749849856,0.11868569999933243,0.8556650876998901,-0.5281022191047668,-0.03466695919632912,0.10096602886915207,0.842444121837616,-0.545899510383606,-0.06923941522836685,0.09993983060121536,0.8289822936058044,-0.5501672029495239,-0.08489304035902023,0.12242089211940765,0.8216583132743835,-0.5561299324035645,-0.17980776727199554,0.09253404289484024,0.8061179518699646,-0.5550227165222168,-0.26541993021965027,0.05987928807735443,0.7860767245292664,-0.5554342865943909,-0.29941877722740173,0.05160599946975708,0.7740659117698669,-0.563430666923523,-0.2750822603702545,0.07084400206804276,0.7757943272590637,-0.5845516324043274,-0.2557752728462219,0.06197275593876839,0.7674880027770996,-0.5994903445243835,-0.2464994341135025,0.054809316992759705,0.7595033049583435,-0.6156085729598999,-0.23005279898643494,0.0444134958088398,0.7524155974388123,-0.6302341818809509,-0.22095775604248047,0.038459017872810364,0.7433057427406311,-0.6430760622024536,-0.22033636271953583,0.03800712898373604,0.7324346899986267,-0.6557850241661072,-0.22351132333278656,0.0385400764644146,0.7200717926025391,-0.664074182510376,-0.22714798152446747,0.0382474809885025,0.7112991213798523,-0.6671656370162964,-0.22552534937858582,0.03870314732193947,0.7088937759399414,-0.669319748878479,-0.22036942839622498,0.043632425367832184,0.7081981897354126,-0.6706668734550476,-0.21508026123046875,0.05555342510342598,0.707714855670929,-0.6689748764038086,-0.20544826984405518,0.06897484511137009,0.7109895348548889,-0.6694221496582031,-0.18856596946716309,0.07375248521566391,0.714756965637207,-0.6743414402008057,-0.17239077389240265,0.07715240865945816,0.7138575315475464,-0.6786495447158813,-0.1619524359703064,0.08934835344552994,0.7107905149459839,-0.6787376999855042,-0.15578053891658783,0.10796824097633362,0.7095001339912415,-0.6788201332092285,-0.153877854347229,0.1245364099740982,0.7071177959442139,-0.677932620048523,-0.1604648381471634,0.14379921555519104,0.7028372287750244,-0.6775382161140442,-0.1633673757314682,0.14727985858917236,0.7018274068832397,-0.6755928993225098,-0.1668364405632019,0.14927035570144653,0.702465832233429,-0.6711860299110413,-0.16919390857219696,0.1488862931728363,0.7061980366706848,-0.6645649075508118,-0.16962967813014984,0.1466071605682373,0.7128012180328369,-0.6562922596931458,-0.1696886420249939,0.14254789054393768,0.7212255597114563,-0.6467002034187317,-0.16717615723609924,0.1376039683818817,0.7313660383224487,-0.636493444442749,-0.16124022006988525,0.1324458122253418,0.7425199151039124,-0.6270628571510315,-0.15390239655971527,0.12685053050518036,0.7530040740966797,-0.6193376779556274,-0.14476217329502106,0.12196016311645508,0.7619649171829224,-0.6139442920684814,-0.13523052632808685,0.11738574504852295,0.7687689661979675,-0.610595166683197,-0.12654049694538116,0.11272134631872177,0.7735987305641174],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,0.9999997019767761,0.9999998807907104,0.9999997615814209,0.9999996423721313,0.9999998211860657],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,2.2333333492279053],"values":[-2.578556177468272e-7,13.415108680725098,0.0000028177285003039287,5.969029643893009e-7,13.41511058807373,7.37249308713217e-7],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[-0.30698150396347046,-0.005662641022354364,-0.0018292159074917436,0.9516968727111816,-0.3069814443588257,-0.005662641488015652,-0.0018292269669473171,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,0.9999998807907104,0.9999999403953552,0.9999998807907104,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,2.2333333492279053],"values":[-11.139498710632324,-4.020103931427002,0.42029869556427,-11.139494895935059,-4.020098686218262,0.4202975332736969],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.6681930422782898,-0.19019095599651337,0.25158506631851196,0.6738327145576477,0.6696794629096985,-0.18781758844852448,0.25810477137565613,0.6705489754676819,0.6742856502532959,-0.18415133655071259,0.26559728384017944,0.6639919877052307,0.6842666268348694,-0.1797463446855545,0.2711295485496521,0.6526555418968201,0.6991998553276062,-0.17484965920448303,0.2736925780773163,0.6368984580039978,0.7158873677253723,-0.17106811702251434,0.27654126286506653,0.6178721189498901,0.7323580980300903,-0.16810496151447296,0.2821502387523651,0.5964760780334473,0.7491346597671509,-0.16470034420490265,0.28956642746925354,0.5725577473640442,0.7644669413566589,-0.1579805463552475,0.3029251992702484,0.546688973903656,0.7783006429672241,-0.14670699834823608,0.32202887535095215,0.5186739563941956,0.7927418947219849,-0.12809959053993225,0.34336456656455994,0.48708486557006836,0.8076791763305664,-0.10807272046804428,0.36285245418548584,0.4520096480846405,0.8202274441719055,-0.08979572355747223,0.37885457277297974,0.41908591985702515,0.8324974179267883,-0.07288879156112671,0.389039546251297,0.38766422867774963,0.8429437279701233,-0.05392003804445267,0.3985005021095276,0.3574015498161316,0.8506928086280823,-0.0398935005068779,0.409888356924057,0.32668301463127136,0.8551844358444214,-0.03280936926603317,0.4266566336154938,0.2924846112728119,0.8554257750511169,-0.022175893187522888,0.4498710036277771,0.2556777596473694,0.8520717620849609,-0.006456329021602869,0.47348475456237793,0.22303421795368195,0.8503255844116211,0.014446988701820374,0.4870252311229706,0.1988571435213089,0.8547560572624207,0.03241094574332237,0.4851226508617401,0.1816525012254715,0.8570103645324707,0.04001421108841896,0.48317039012908936,0.17458079755306244,0.8584814071655273,0.047587744891643524,0.4824669063091278,0.16724471747875214,0.859296977519989,0.054970789700746536,0.4829210638999939,0.15929295122623444,0.8597553968429565,0.06179630756378174,0.4840346872806549,0.15070590376853943,0.8600193858146667,0.06776464730501175,0.4854618310928345,0.14177940785884857,0.8601080179214478,0.07236531376838684,0.48708099126815796,0.1331525593996048,0.8600267767906189,0.07504826039075851,0.4886246919631958,0.12636308372020721,0.8604921698570251,0.0729193314909935,0.49150630831718445,0.11250565201044083,0.8640835881233215,0.06648273020982742,0.4896121919155121,0.0960184633731842,0.8670930862426758,0.05720609799027443,0.4860907196998596,0.09269794076681137,0.8735389709472656,0.03204209357500076,0.4739716947078705,0.10608406364917755,0.8830344080924988,-0.009229959920048714,0.4484166204929352,0.13815750181674957,0.8865241408348083,-0.0704563707113266,0.40539976954460144,0.2115698605775833,0.8727287650108337,-0.13511432707309723,0.34140509366989136,0.32176268100738525,0.8406251072883606,-0.16813801229000092,0.2870017886161804,0.42744478583335876,0.8096248507499695,-0.15240532159805298,0.2737441062927246,0.49633097648620605,0.8043867945671082,-0.11731195449829102,0.27787017822265625,0.5118476748466492,0.803840696811676,-0.08866485953330994,0.3044770061969757,0.503261923789978,0.7937964200973511,-0.09963275492191315,0.326183557510376,0.503552258014679,0.7730593085289001,-0.13207001984119415,0.32443422079086304,0.5288471579551697,0.7623070478439331,-0.14434336125850677,0.3167855739593506,0.5456188917160034,0.7555168867111206,-0.15309566259384155,0.3032500147819519,0.5601745247840881,0.7445375919342041,-0.16435712575912476,0.28921499848365784,0.5787964463233948,0.7332058548927307,-0.17030322551727295,0.28323325514793396,0.5942937135696411,0.7249936461448669,-0.1691265106201172,0.2854236662387848,0.6035841107368469,0.7211986184120178,-0.16884562373161316,0.28467458486557007,0.6085426807403564,0.720548689365387,-0.17347487807273865,0.27840372920036316,0.6109070777893066,0.7198981642723083,-0.17614665627479553,0.2747676372528076,0.612553596496582,0.7180444598197937,-0.17708338797092438,0.27480649948120117,0.6144388914108276,0.7147241234779358,-0.17837457358837128,0.2773532271385193,0.6167877912521362,0.7092990875244141,-0.18280571699142456,0.2779798209667206,0.6214532852172852,0.7058480381965637,-0.18611358106136322,0.2754748463630676,0.6255029439926147,0.7042007446289062,-0.18979869782924652,0.2705439031124115,0.6283978819847107,0.6985706686973572,-0.2028348296880722,0.2597191631793976,0.6351401805877686,0.6903955340385437,-0.2135457545518875,0.2443179041147232,0.646576464176178,0.6836925148963928,-0.21928003430366516,0.23237740993499756,0.6561110019683838,0.6785314083099365,-0.22279059886932373,0.22465665638446808,0.6629396080970764,0.6725063323974609,-0.224004864692688,0.21915419399738312,0.6704689264297485,0.6692368984222412,-0.22250205278396606,0.21707092225551605,0.6749038100242615,0.6668264865875244,-0.2182634472846985,0.2196306735277176,0.6778392195701599,0.6662219166755676,-0.2106117308139801,0.22567036747932434,0.6788696050643921,0.6660506129264832,-0.2033262550830841,0.23247244954109192,0.6789636611938477,0.6656948924064636,-0.1978001743555069,0.2390461415052414,0.6786622405052185,0.6657335758209229,-0.19385789334774017,0.24497714638710022,0.6776461005210876,0.666477620601654,-0.1915719211101532,0.24931442737579346,0.6759809255599976,0.6681933999061584,-0.19019094109535217,0.2515847384929657,0.6738324165344238],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1.000000238418579,1.0000001192092896,1.000000238418579,1.000000238418579,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,2.2333333492279053],"values":[-9.370167504130222e-7,25.527103424072266,-0.000005056447207607562,0.0000012344363540250924,25.527097702026367,1.5906070416349394e-7],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.6568431854248047,-0.13635334372520447,-0.030326761305332184,0.7409757375717163,0.6585056781768799,-0.13305392861366272,-0.02785811945796013,0.7401965260505676,0.6595696806907654,-0.12896722555160522,-0.023994319140911102,0.7401078343391418,0.6585195064544678,-0.12297750264406204,-0.016395676881074905,0.7422666549682617,0.6549341082572937,-0.11260156333446503,-0.002178376307711005,0.7472465634346008,0.6489779353141785,-0.09813421964645386,0.014844430610537529,0.7543056011199951,0.640569269657135,-0.08290595561265945,0.03146804869174957,0.7627630233764648,0.6296470761299133,-0.06430812925100327,0.047695185989141464,0.7727445960044861,0.6174303889274597,-0.06457985937595367,0.048982664942741394,0.7824383974075317,0.602820873260498,-0.06478800624608994,0.049433834850788116,0.7937039136886597,0.5851455330848694,-0.06096392869949341,0.05025734007358551,0.8070701956748962,0.5666064620018005,-0.05548575520515442,0.052593473345041275,0.8204342722892761,0.5493366122245789,-0.046441707760095596,0.05587499588727951,0.8324365019798279,0.5321908593177795,-0.03425702080130577,0.059633269906044006,0.8438265919685364,0.5176935791969299,-0.019698353484272957,0.0624915175139904,0.8530534505844116,0.505677342414856,-0.0006701644160784781,0.058438677340745926,0.8607409000396729,0.4985370934009552,0.009132754057645798,0.04455499351024628,0.8656744360923767,0.4907936751842499,0.01707308739423752,0.028184745460748672,0.870652437210083,0.4823649525642395,0.022321421653032303,0.01722760684788227,0.8755164742469788,0.4794771671295166,0.0269571915268898,0.014361941255629063,0.8770226240158081,0.4828624129295349,0.031477149575948715,0.01410294696688652,0.8750166893005371,0.4843800663948059,0.032575301826000214,0.01545137632638216,0.874114453792572,0.4846293032169342,0.03269319236278534,0.01771046593785286,0.8739290237426758,0.483604371547699,0.03235698118805885,0.019922107458114624,0.8744615316390991,0.48172733187675476,0.03233848884701729,0.02115725725889206,0.8754686713218689,0.47959083318710327,0.033066511154174805,0.02116045355796814,0.8766136169433594,0.4778940677642822,0.034503642469644547,0.02020510472357273,0.8775070309638977,0.4771176278591156,0.03610268235206604,0.018943704664707184,0.8778932094573975,0.4795874059200287,0.040441352874040604,0.014325928874313831,0.8764446377754211,0.4794822633266449,0.04211922734975815,0.010507848113775253,0.8764772415161133,0.4689130485057831,0.036220040172338486,0.016303660348057747,0.882350742816925,0.45418059825897217,0.02541336603462696,0.028470374643802643,0.8900918960571289,0.43383556604385376,0.011403662152588367,0.048392027616500854,0.8996193408966064,0.42263832688331604,0.0007048479747027159,0.07194917649030685,0.9034376740455627,0.41948291659355164,-0.08806964010000229,0.04363227263092995,0.9024267792701721,0.3945893943309784,-0.17359387874603271,0.005141423549503088,0.9022959470748901,0.3597077429294586,-0.17595866322517395,0.01712731085717678,0.9161635041236877,0.3123185634613037,-0.17248539626598358,0.01607665792107582,0.9340489506721497,0.26210397481918335,-0.16772210597991943,0.00170644570607692,0.9503514766693115,0.25631940364837646,-0.16208498179912567,-0.021925877779722214,0.9526532292366028,0.283338725566864,-0.16193848848342896,-0.031136315315961838,0.9447357654571533,0.3062293529510498,-0.16431283950805664,-0.02600821666419506,0.9373091459274292,0.3395083546638489,-0.16442131996154785,-0.018450386822223663,0.9259369969367981,0.375664085149765,-0.16556723415851593,-0.01002294197678566,0.9117913842201233,0.39950621128082275,-0.16630171239376068,-0.005484347697347403,0.9015034437179565,0.409578800201416,-0.16767080128192902,-0.00026908150175586343,0.8967338800430298,0.41648268699645996,-0.1680832803249359,0.0021709937136620283,0.8934682607650757,0.4277741611003876,-0.1666463166475296,-0.0006514672422781587,0.8883906602859497,0.44455406069755554,-0.1669761836528778,-0.0008436742937192321,0.8800510764122009,0.4657312333583832,-0.16864913702011108,0.0004777742433361709,0.8687068819999695,0.49080201983451843,-0.172035351395607,0.0036137590650469065,0.8541101813316345,0.5208139419555664,-0.17243357002735138,0.0005678250454366207,0.8360736966133118,0.5528050065040588,-0.1672181934118271,-0.009001549333333969,0.8163110613822937,0.5841548442840576,-0.16175906360149384,-0.017352988943457603,0.7951704263687134,0.6127637624740601,-0.16352227330207825,-0.027819693088531494,0.7726624011993408,0.6418210864067078,-0.15866932272911072,-0.03567924350500107,0.7494109869003296,0.6632038950920105,-0.15103240311145782,-0.04113081470131874,0.731886625289917,0.6767987012863159,-0.14598657190799713,-0.04452429711818695,0.7201729416847229,0.6845404505729675,-0.1442217081785202,-0.04649840667843819,0.7130514979362488,0.685918390750885,-0.14295950531959534,-0.046692464500665665,0.7119678854942322,0.6832942962646484,-0.14241516590118408,-0.045517463237047195,0.71467125415802,0.6773748397827148,-0.14313803613185883,-0.04272563382983208,0.7203119993209839,0.6702674031257629,-0.1431528925895691,-0.039842646569013596,0.7270911335945129,0.6636857986450195,-0.14315228164196014,-0.03826587274670601,0.733187735080719,0.6587772965431213,-0.14270563423633575,-0.036639731377363205,0.7377703785896301,0.6565382480621338,-0.14057093858718872,-0.034111469984054565,0.7402930855751038,0.6568431854248047,-0.13635273277759552,-0.03032645955681801,0.7409759163856506],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999998807907104,0.9999998807907104,0.9999998211860657,0.9999997615814209,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.000005401070666266605,23.109195709228516,-7.357123763540585e-7,-4.7439914396818494e-7,23.10919761657715,-0.000003337545194881386],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[-0.69681316614151,0.06415028125047684,-0.061681341379880905,0.7117103338241577,-0.699835479259491,0.06505269557237625,-0.06339270621538162,0.708505392074585,-0.7037932276725769,0.06663306057453156,-0.06617975980043411,0.7041700482368469,-0.7086936831474304,0.0699189081788063,-0.07224501669406891,0.6983160376548767,-0.7139576077461243,0.07581432163715363,-0.08353032916784286,0.6910422444343567,-0.7188647985458374,0.08318286389112473,-0.09738873690366745,0.6832491755485535,-0.7235831618309021,0.09064529091119766,-0.11116087436676025,0.6751697063446045,-0.727945864200592,0.09848691523075104,-0.12549617886543274,0.6668177247047424,-0.7352770566940308,0.11991174519062042,-0.12208452075719833,0.65580815076828,-0.7425917983055115,0.13808517158031464,-0.11562921851873398,0.6450735330581665,-0.7492117881774902,0.15257447957992554,-0.10611584037542343,0.6357218027114868,-0.7551193237304688,0.1679537445306778,-0.09683362394571304,0.6262664198875427,-0.7615964412689209,0.17942190170288086,-0.08475331217050552,0.6169242262840271,-0.7687952518463135,0.18356455862522125,-0.07128803431987762,0.6084208488464355,-0.779011607170105,0.18040944635868073,-0.0558919832110405,0.5978875160217285,-0.7910699844360352,0.17118607461452484,-0.04247710853815079,0.5857468247413635,-0.7976329922676086,0.17074574530124664,-0.03962472081184387,0.5771112442016602,-0.8026132583618164,0.17651253938674927,-0.03919493407011032,0.5684356093406677,-0.806609570980072,0.18772973120212555,-0.04113396257162094,0.5589691996574402,-0.8116241097450256,0.20283760130405426,-0.03449457883834839,0.5467479825019836,-0.8163894414901733,0.21614722907543182,-0.027178794145584106,0.53483647108078,-0.8183801174163818,0.22064831852912903,-0.0250693466514349,0.5300375819206238,-0.820038378238678,0.22427786886692047,-0.024334564805030823,0.5259698629379272,-0.8214274644851685,0.22731482982635498,-0.024737203493714333,0.5224681496620178,-0.8227092027664185,0.22972305119037628,-0.025282951071858406,0.51936274766922,-0.8239503502845764,0.23127424716949463,-0.025282708927989006,0.5166999697685242,-0.8251270651817322,0.23165875673294067,-0.02510637417435646,0.5146545171737671,-0.8260716795921326,0.2310943305492401,-0.02529853954911232,0.5133819580078125,-0.8274737596511841,0.22388917207717896,-0.032740239053964615,0.5138957500457764,-0.8256035447120667,0.21110567450523376,-0.046791888773441315,0.5211753845214844,-0.8218340277671814,0.199152410030365,-0.05319926515221596,0.5311280488967896,-0.8139812350273132,0.17928755283355713,-0.05748293176293373,0.5495327711105347,-0.7980432510375977,0.14758405089378357,-0.06009495258331299,0.5811493396759033,-0.7707555890083313,0.09175493568181992,-0.07524982839822769,0.6259827017784119,-0.724155068397522,0.06465110927820206,-0.052062027156353,0.6846234202384949,-0.6478310227394104,0.04607650637626648,-0.0599554181098938,0.7580220103263855,-0.5683914422988892,-0.011170287616550922,-0.11486119776964188,0.8146246671676636,-0.5174143314361572,-0.03732872009277344,-0.12358392775058746,0.845940887928009,-0.49109432101249695,-0.024887939915060997,-0.12174473702907562,0.8621978759765625,-0.50379878282547,0.006501478608697653,-0.1146249771118164,0.8561574816703796,-0.5199714303016663,0.027900954708456993,-0.114597387611866,0.8460016250610352,-0.5289366245269775,0.033242110162973404,-0.11521289497613907,0.8401470184326172,-0.5415310859680176,0.027197513729333878,-0.110674187541008,0.8329199552536011,-0.5474692583084106,0.016823599115014076,-0.11303108930587769,0.8289863467216492,-0.5470125079154968,0.005519258789718151,-0.11893275380134583,0.8286144733428955,-0.5444039106369019,-0.0010586174903437495,-0.12827345728874207,0.828956663608551,-0.5495395660400391,-0.0028654150664806366,-0.127433642745018,0.8256868720054626,-0.5603667497634888,0.003749151946976781,-0.11778739094734192,0.819817841053009,-0.5725873708724976,0.01502655167132616,-0.11042890697717667,0.812233567237854,-0.5883709192276001,0.019686255604028702,-0.10714475810527802,0.8012191653251648,-0.6055414080619812,0.02272837609052658,-0.1106000542640686,0.7877631187438965,-0.6266342997550964,0.029809990897774696,-0.11005275696516037,0.7709274888038635,-0.6580135226249695,0.039064403623342514,-0.09275725483894348,0.7462494969367981,-0.6885392069816589,0.049979887902736664,-0.07689984142780304,0.719376266002655,-0.7009145021438599,0.06707235425710678,-0.08724499493837357,0.7047045826911926,-0.7120288014411926,0.07368133962154388,-0.08708213269710541,0.692822277545929,-0.71821528673172,0.07363879680633545,-0.08382582664489746,0.6868168115615845,-0.7204328179359436,0.07245573401451111,-0.0825878158211708,0.6847671270370483,-0.7199503183364868,0.0703049898147583,-0.07904107868671417,0.6859163641929626,-0.7166084051132202,0.06753120571374893,-0.07498964667320251,0.6901366114616394,-0.7121748328208923,0.06586790084838867,-0.07075429707765579,0.6953144669532776,-0.707314133644104,0.064885213971138,-0.0665612667798996,0.7007611989974976,-0.7025370001792908,0.06376876682043076,-0.06320593506097794,0.7059605717658997,-0.698228657245636,0.06274211406707764,-0.06002406030893326,0.7105894088745117,-0.6954206228256226,0.06222449615597725,-0.05817713215947151,0.7135360836982727,-0.6949831247329712,0.0626702532172203,-0.05868307873606682,0.7138818502426147,-0.6968133449554443,0.06415065377950668,-0.061681631952524185,0.7117100954055786],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1,1.0000001192092896,1.0000001192092896,1,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,2.2333333492279053],"values":[8.230845196521841e-7,13.547401428222656,-0.0000021679509245586814,-3.5222919336774794e-7,13.547394752502441,-5.746210263168905e-7],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[-0.30788561701774597,0.014501907862722874,0.004691815935075283,0.9513012766838074,-0.30788561701774597,0.01450191531330347,0.004691844806075096,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1,1.0000001192092896,1.0000001192092896,0.9999998807907104,1,1],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,2.2333333492279053],"values":[2.8340001106262207,7.754301071166992,-6.139601230621338,2.8339998722076416,7.75430154800415,-6.1396002769470215],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[-0.33200064301490784,-0.09571805596351624,-0.11373621225357056,0.9314922094345093,-0.33200064301490784,-0.09571808576583862,-0.11373621225357056,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999998807907104,1,0.9999999403953552,0.9999998807907104,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.000001213513542097644,10.288599014282227,2.977907911372313e-7,-8.382004423879152e-9,10.288615226745605,-3.003531290346473e-8],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[-1.3285897715320516e-8,-5.96628346727357e-9,1.3140379451215267e-8,1,-1.3285897715320516e-8,-1.8189894035458565e-8,8.949427865445614e-9,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1,1,1,1,1,0.9999998807907104],"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,2.2333333492279053],"values":[-9.898838726485337e-8,10.288607597351074,-1.5654002538667555e-7,-2.8444338795452495e-7,10.28860855102539,-1.3550275923535082e-7],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.049173738807439804,-0.05758349969983101,0.0038379717152565718,0.9971215128898621,0.04956068843603134,-0.061415817588567734,0.006324463058263063,0.9968610405921936,0.0499863401055336,-0.06470606476068497,0.008879560977220535,0.9966120719909668,0.05066511034965515,-0.06616594642400742,0.010845229029655457,0.9964625239372253,0.051595523953437805,-0.06484055519104004,0.011761531233787537,0.9964914917945862,0.049909546971321106,-0.060286350548267365,0.010504220612347126,0.9968772530555725,0.04669522866606712,-0.05403076112270355,0.007460299879312515,0.9974189400672913,0.040813200175762177,-0.04493112117052078,0.005206505302339792,0.9981424808502197,0.0329282246530056,-0.03306935355067253,0.002737377304583788,0.9989067316055298,0.023840563371777534,-0.019090665504336357,0.002227955497801304,0.9995310306549072,0.015939882025122643,-0.0035278385039418936,0.0030957565177232027,0.9998619556427002,0.011728830635547638,0.01429959386587143,0.001841298770159483,0.9998272657394409,0.00808741059154272,0.03391131013631821,-0.0009161109337583184,0.9993916749954224,0.006319950334727764,0.05483870208263397,-0.004439056850969791,0.998465359210968,0.004676705226302147,0.07348166406154633,-0.00682004215195775,0.9972622990608215,0.0031756118405610323,0.09033810347318649,-0.009972156025469303,0.9958561658859253,0.0041916631162166595,0.10589892417192459,-0.012269970960915089,0.9942923784255981,0.006207355763763189,0.11914859712123871,-0.013177742250263691,0.9927695393562317,0.007045535370707512,0.13263379037380219,-0.01572990044951439,0.9910152554512024,0.009085692465305328,0.14723551273345947,-0.01618807390332222,0.9889272451400757,0.013384277001023293,0.1615746170282364,-0.014438419602811337,0.9866641163825989,0.015558226965367794,0.16799159348011017,-0.013402817770838737,0.9855745434761047,0.017347462475299835,0.1740979254245758,-0.012483366765081882,0.9844964146614075,0.018547585234045982,0.18001562356948853,-0.011629105545580387,0.9834201335906982,0.019240424036979675,0.18575021624565125,-0.01059002336114645,0.982351541519165,0.019603850319981575,0.191176176071167,-0.009158965200185776,0.9813172221183777,0.019689388573169708,0.19614586234092712,-0.007450960110872984,0.9803487658500671,0.0195508673787117,0.200078547000885,-0.005985045339912176,0.9795665144920349,0.019108764827251434,0.2072748839855194,-0.002195099601522088,0.9780936241149902,0.018048349767923355,0.20980076491832733,0.0027485103346407413,0.9775737524032593,0.011970305815339088,0.20401740074157715,0.0031580738723278046,0.978888988494873,0.00013260409468784928,0.1921614706516266,0.007082497235387564,0.9813377261161804,-0.011838959529995918,0.1750292181968689,0.018264489248394966,0.9843226075172424,-0.021418239921331406,0.15446510910987854,0.027457723394036293,0.9873843193054199,-0.028613608330488205,0.13146081566810608,0.03322722762823105,0.9903510808944702,-0.025710104033350945,0.10270079225301743,0.031692225486040115,0.9938748478889465,-0.014636804349720478,0.07182061672210693,0.023525305092334747,0.9970327019691467,-0.00596905592828989,0.0498259961605072,0.02369019016623497,0.9984591007232666,0.0018046159530058503,0.04297003149986267,0.034185588359832764,0.9984896779060364,0.004007488954812288,0.0360228568315506,0.04463433846831322,0.998345673084259,-0.000617154233623296,0.010381162166595459,0.04004424065351486,0.9991437792778015,0.0012710088631138206,-0.015428290702402592,0.03725250065326691,0.9991859793663025,0.008782901801168919,-0.034795768558979034,0.03582596033811569,0.998713493347168,0.01585945300757885,-0.04321722313761711,0.03144664689898491,0.9984447360038757,0.019694050773978233,-0.04873299598693848,0.029604818671941757,0.9981787204742432,0.02140861563384533,-0.05721506476402283,0.029738618060946465,0.9976891875267029,0.02257836051285267,-0.06408074498176575,0.02744418755173683,0.9973117113113403,0.02390441857278347,-0.06709667295217514,0.022999851033091545,0.9971948862075806,0.026392580941319466,-0.06734324991703033,0.017905982211232185,0.997219979763031,0.03013633005321026,-0.06734231114387512,0.013476766645908356,0.997183620929718,0.035146165639162064,-0.0686982199549675,0.010661579668521881,0.996961236000061,0.038785457611083984,-0.0698256865143776,0.008924955502152443,0.9967649579048157,0.04134052246809006,-0.07144604623317719,0.00846336130052805,0.9965514540672302,0.04462761431932449,-0.07328134030103683,0.00804478581994772,0.9962798357009888,0.04706631973385811,-0.0725787803530693,0.0052976650185883045,0.9962374567985535,0.04864924028515816,-0.06832441687583923,0.001916510984301567,0.9964745044708252,0.05131576210260391,-0.06237166002392769,0.0002350280701648444,0.9967328906059265,0.053977854549884796,-0.0567517913877964,0.0007199643878266215,0.9969278573989868,0.05474267899990082,-0.05144520848989487,0.0002420050004730001,0.9971743226051331,0.05527551472187042,-0.04720693454146385,-0.0009048404754139483,0.9973541498184204,0.05406491830945015,-0.04580068960785866,-0.0011105072917416692,0.9974858164787292,0.05267368629574776,-0.04637555778026581,-0.0007058856426738203,0.9975340962409973,0.05127064138650894,-0.047781802713871,-0.00024744431721046567,0.997541069984436,0.049516353756189346,-0.049500565975904465,0.0002064746367977932,0.9975458979606628,0.048892803490161896,-0.051550205796957016,0.0008195660775527358,0.9974724650382996,0.0488615557551384,-0.05420215055346489,0.0019413200207054615,0.9973319172859192,0.04917372018098831,-0.05758365988731384,0.003837921656668186,0.9971215128898621],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1,0.9999998807907104,1.0000001192092896,0.9999998807907104,0.9999998211860657,0.9999998211860657],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,2.2333333492279053],"values":[4.366398811340332,7.562909126281738,-0.1768004596233368,4.366400241851807,7.562911510467529,-0.17680050432682037],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.7073927521705627,0.46763208508491516,-0.34156015515327454,0.4052806794643402,0.7072304487228394,0.46829748153686523,-0.3312666416168213,0.41326144337654114,0.706551730632782,0.46960434317588806,-0.3207399845123291,0.42116785049438477,0.7045807242393494,0.47262677550315857,-0.31292855739593506,0.4269258379936218,0.7006047964096069,0.478238582611084,-0.31047487258911133,0.42900604009628296,0.6932938694953918,0.4851963520050049,-0.3153575658798218,0.4295088052749634,0.685034453868866,0.4916374981403351,-0.32345467805862427,0.4294152557849884,0.6736209392547607,0.4995895326137543,-0.33697572350502014,0.42789310216903687,0.6609809994697571,0.5070662498474121,-0.35269805788993835,0.4261361062526703,0.6516287922859192,0.511255145072937,-0.36938536167144775,0.42136988043785095,0.6411157250404358,0.5208372473716736,-0.386783629655838,0.40999719500541687,0.6269385814666748,0.5371211767196655,-0.40349000692367554,0.39451834559440613,0.6149821281433105,0.5501226186752319,-0.42255890369415283,0.37497472763061523,0.6056501269340515,0.5607312321662903,-0.44081997871398926,0.35276928544044495,0.5993431210517883,0.5689550042152405,-0.4533030688762665,0.33405745029449463,0.589956521987915,0.5781943202018738,-0.46687787771224976,0.3157018721103668,0.5827425122261047,0.5862694978713989,-0.47870972752571106,0.29586538672447205,0.5730654001235962,0.5959851741790771,-0.48920613527297974,0.27762436866760254,0.5568118095397949,0.6080331802368164,-0.501491129398346,0.26222720742225647,0.5402953028678894,0.6205881834030151,-0.5140555500984192,0.24227721989154816,0.516157329082489,0.6390366554260254,-0.5272938013076782,0.21719835698604584,0.503383457660675,0.6480917930603027,-0.5334807634353638,0.20489166676998138,0.49044671654701233,0.6566234827041626,-0.5394917726516724,0.19301900267601013,0.4770163595676422,0.6650336384773254,-0.5450572371482849,0.18192948400974274,0.46274513006210327,0.6738894581794739,-0.5496559143066406,0.1721002161502838,0.4478822946548462,0.6830692291259766,-0.5531255602836609,0.1639210432767868,0.43351173400878906,0.6917192935943604,-0.5556769371032715,0.1575285792350769,0.42222079634666443,0.6982913613319397,-0.5573716163635254,0.15315283834934235,0.405004620552063,0.7093231678009033,-0.5580569505691528,0.14630275964736938,0.39738279581069946,0.7163108587265015,-0.553530216217041,0.1502998024225235,0.4130418598651886,0.7071368098258972,-0.5481700301170349,0.16989292204380035,0.4332137405872345,0.6862037181854248,-0.5530317425727844,0.18869605660438538,0.4423225224018097,0.6669464707374573,-0.5597453713417053,0.2149847149848938,0.47355249524116516,0.6306530237197876,-0.5571202635765076,0.2600806951522827,0.4877316355705261,0.5967694520950317,-0.558938205242157,0.3058958947658539,0.509113609790802,0.5781009197235107,-0.5327324867248535,0.3504267632961273,0.5362095832824707,0.5743188261985779,-0.4745728075504303,0.3967592716217041,0.5398846864700317,0.5821272730827332,-0.41495493054389954,0.4443702697753906,0.5526623129844666,0.5812106132507324,-0.3770159184932709,0.4632684886455536,0.5910528302192688,0.5575668811798096,-0.3663026988506317,0.4534291625022888,0.6224449276924133,0.5213092565536499,-0.35260704159736633,0.4652605354785919,0.6239899396896362,0.5111925005912781,-0.348938912153244,0.47703298926353455,0.6254936456680298,0.5084832310676575,-0.3471357524394989,0.47926950454711914,0.6370997428894043,0.5028337836265564,-0.3391118049621582,0.4756736159324646,0.6468359231948853,0.4986203610897064,-0.32983842492103577,0.473484605550766,0.6542736887931824,0.49396443367004395,-0.3222099542617798,0.47339823842048645,0.6591299772262573,0.48928549885749817,-0.3185698390007019,0.47398385405540466,0.6606897711753845,0.4855097830295563,-0.3225262463092804,0.47301822900772095,0.6613652110099792,0.48276808857917786,-0.33172735571861267,0.46849557757377625,0.6626905202865601,0.4829101860523224,-0.3389635980129242,0.4612404704093933,0.6653660535812378,0.48597219586372375,-0.341154009103775,0.45247429609298706,0.6704139709472656,0.486876517534256,-0.3393091559410095,0.445382684469223,0.6761892437934875,0.48553499579429626,-0.3367922902107239,0.4399941563606262,0.6814333200454712,0.4842003285884857,-0.333388090133667,0.4359484910964966,0.6881629228591919,0.4800282120704651,-0.32959407567977905,0.4328654706478119,0.6946302056312561,0.47546786069869995,-0.3296847343444824,0.4274660348892212,0.699672520160675,0.47471553087234497,-0.3319064974784851,0.4182603359222412,0.7061411142349243,0.4729275405406952,-0.33685022592544556,0.40526077151298523,0.7113319039344788,0.4697282612323761,-0.34158024191856384,0.39583486318588257,0.7124240398406982,0.46922820806503296,-0.3459728956222534,0.39061447978019714,0.7110748291015625,0.4688413441181183,-0.3508760333061218,0.38916122913360596,0.7100554704666138,0.46854379773139954,-0.3541144132614136,0.3884468376636505,0.7085004448890686,0.46918898820877075,-0.35546234250068665,0.3892754018306732,0.7073240280151367,0.4685240089893341,-0.35594671964645386,0.39176517724990845,0.707475483417511,0.4674009084701538,-0.35404279828071594,0.394548624753952,0.7074794173240662,0.4672134220600128,-0.34939488768577576,0.3988831639289856,0.7073927521705627,0.4676319360733032,-0.3415600657463074,0.4052808880805969],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999996423721313,0.9999997615814209,0.9999998211860657,0.9999997019767761,0.9999997615814209,0.9999999403953552],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.000005704825525754131,17.630796432495117,0.000002120220642609638,-0.000004313861609261949,17.630796432495117,0.000006712490630889079],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.1987335979938507,0.16944418847560883,-0.06481166183948517,0.9631163477897644,0.2014549821615219,0.1768873929977417,-0.06878574937582016,0.960934579372406,0.20000845193862915,0.18210962414741516,-0.07619673013687134,0.959701418876648,0.1934053599834442,0.17836637794971466,-0.08482303470373154,0.9610332250595093,0.1840243637561798,0.16014611721038818,-0.0910382941365242,0.9655051827430725,0.1848818063735962,0.12226241827011108,-0.09255491197109222,0.970723569393158,0.19366714358329773,0.09057650715112686,-0.09893827885389328,0.971854031085968,0.2037198841571808,0.08254984021186829,-0.0949835330247879,0.9709077477455139,0.22482061386108398,0.0767238512635231,-0.0889366865158081,0.9672949314117432,0.24386128783226013,0.08431123942136765,-0.08635307103395462,0.962271511554718,0.26228567957878113,0.09936590492725372,-0.08123447746038437,0.9564170837402344,0.2938937842845917,0.10887360572814941,-0.07331068813800812,0.9467832446098328,0.316887766122818,0.1312032788991928,-0.0688004121184349,0.9368214011192322,0.3415319621562958,0.14702633023262024,-0.0653119906783104,0.9259986877441406,0.3694712221622467,0.16133123636245728,-0.06102113053202629,0.9130934476852417,0.39585405588150024,0.18507663905620575,-0.05297265574336052,0.8979087471961975,0.41599392890930176,0.20743891596794128,-0.04731655493378639,0.8841263055801392,0.42960885167121887,0.23155415058135986,-0.03265276551246643,0.8722113966941833,0.44186124205589294,0.2579452395439148,-0.01956856995820999,0.8589761257171631,0.44602978229522705,0.27886301279067993,-0.004749948624521494,0.8504530191421509,0.4577387273311615,0.28801682591438293,0.008609040640294552,0.841098964214325,0.46683210134506226,0.287875235080719,0.010833450593054295,0.8361090421676636,0.4763346314430237,0.2879612445831299,0.011581981554627419,0.8306921720504761,0.4854811131954193,0.29027849435806274,0.01230149157345295,0.8245575428009033,0.4946385622024536,0.2954271137714386,0.014058033935725689,0.8172256946563721,0.5038692951202393,0.30341747403144836,0.01661982573568821,0.8085650205612183,0.5123171806335449,0.3138890862464905,0.018586119636893272,0.7991616129875183,0.5184708833694458,0.3241218328475952,0.019159285351634026,0.7910537123680115,0.5270506739616394,0.3570820987224579,0.019906671717762947,0.7709174752235413,0.5343269109725952,0.4046073853969574,0.023066191002726555,0.7417921423912048,0.540126621723175,0.4373723566532135,0.029217230156064034,0.7184114456176758,0.5291492342948914,0.470502108335495,0.05687025189399719,0.7038426995277405,0.5265771150588989,0.5097223520278931,0.08793119341135025,0.6746612787246704,0.5423360466957092,0.5402096509933472,0.09503805637359619,0.6364061236381531,0.5405228137969971,0.5726684927940369,0.06857791543006897,0.6125218272209167,0.5257917046546936,0.5883316993713379,0.03119577281177044,0.6135436296463013,0.480523020029068,0.6178913116455078,0.027074432000517845,0.6217514276504517,0.4317420423030853,0.6009934544563293,0.008145395666360855,0.6725618243217468,0.33456575870513916,0.4915059506893158,-0.025800567120313644,0.803630530834198,0.2259088158607483,0.3380095064640045,-0.04036033898591995,0.9127353429794312,0.1981942355632782,0.2718752324581146,-0.06969822943210602,0.939119279384613,0.1983378529548645,0.267458438873291,-0.06831007450819016,0.9404582977294922,0.1952695995569229,0.27121827006340027,-0.057862382382154465,0.9407244324684143,0.191498801112175,0.25839999318122864,-0.05548375844955444,0.945240318775177,0.1861521452665329,0.24735844135284424,-0.05029863119125366,0.9495426416397095,0.18210142850875854,0.23610737919807434,-0.04822292551398277,0.953292727470398,0.18393786251544952,0.2198384702205658,-0.050938259810209274,0.9566834568977356,0.18304289877414703,0.21293209493160248,-0.05207708850502968,0.9583544135093689,0.1747758984565735,0.21028748154640198,-0.052487391978502274,0.9604570269584656,0.1655399650335312,0.2000163197517395,-0.05545688048005104,0.9641133546829224,0.15631461143493652,0.18638242781162262,-0.05626007542014122,0.9683296084403992,0.1516692042350769,0.16837267577648163,-0.06032315641641617,0.9721153378486633,0.1498223841190338,0.154158815741539,-0.06744711846113205,0.9742891192436218,0.15096355974674225,0.149602472782135,-0.07114561647176743,0.9745600819587708,0.15391011536121368,0.14921410381793976,-0.07459938526153564,0.973900318145752,0.15370714664459229,0.1487778127193451,-0.07606491446495056,0.973885715007782,0.151983380317688,0.14494742453098297,-0.06974776089191437,0.9752058982849121,0.14728303253650665,0.13711445033550262,-0.06151837110519409,0.9776107668876648,0.14776310324668884,0.13396935164928436,-0.05865761265158653,0.9781500697135925,0.1549733728170395,0.14065119624137878,-0.05383254587650299,0.9763721227645874,0.16475985944271088,0.15159669518470764,-0.05014180392026901,0.973323404788971,0.17247255146503448,0.1601116955280304,-0.05051856115460396,0.9706004858016968,0.180171400308609,0.163763627409935,-0.051414605230093,0.9685434103012085,0.1871708631515503,0.16379810869693756,-0.05364442244172096,0.9670882225036621,0.19131982326507568,0.1629507690668106,-0.05797012895345688,0.9661694169044495,0.19494877755641937,0.16443540155887604,-0.061372123658657074,0.9649815559387207,0.19873344898223877,0.1694442480802536,-0.06481136381626129,0.9631164073944092],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1.0000001192092896,1.0000001192092896,1,1.0000001192092896,1,1],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.0000032612795166642172,17.827804565429688,0.0000027274559215584304,-6.962273459976132e-7,17.827802658081055,-0.0000025245344659197144],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[-0.09403332322835922,-0.105583056807518,0.5174961686134338,0.8439239859580994,-0.09422236680984497,-0.10550191253423691,0.5185368061065674,0.8432739973068237,-0.09522313624620438,-0.10506775975227356,0.5240443348884583,0.8398040533065796,-0.09621058404445648,-0.10463313013315201,0.5294783711433411,0.8363301157951355,-0.09595920890569687,-0.1047443151473999,0.5280956625938416,0.8372188806533813,-0.0952242985367775,-0.10506722331047058,0.5240502953529358,0.8398002982139587,-0.09729452431201935,-0.10414861887693405,0.5354441404342651,0.8324580192565918,-0.1011042520403862,-0.10238412767648697,0.5564101338386536,0.8183540105819702,-0.10645171254873276,-0.09973634779453278,0.5858399868011475,0.7971901893615723,-0.11172770708799362,-0.09691395610570908,0.6148744821548462,0.7746315598487854,-0.11711102724075317,-0.09379936754703522,0.644501268863678,0.749736487865448,-0.12184765189886093,-0.09084393084049225,0.6705675721168518,0.7261127829551697,-0.12598149478435516,-0.08808301389217377,0.6933172941207886,0.7040463089942932,-0.12900033593177795,-0.08595044910907745,0.7099312543869019,0.687000036239624,-0.1310252547264099,-0.08446061611175537,0.721075713634491,0.6750916242599487,-0.131514772772789,-0.08409292995929718,0.72376948595047,0.6721531748771667,-0.13032357394695282,-0.0849827453494072,0.7172122597694397,0.6792645454406738,-0.1283608227968216,-0.08641066402196884,0.706412672996521,0.6906793117523193,-0.12665121257305145,-0.08761876076459885,0.697003185749054,0.7003349661827087,-0.1255059689283371,-0.0884096771478653,0.6907000541687012,0.706657886505127,-0.12474610656499863,-0.08892667293548584,0.686519205570221,0.7107896208763123,-0.12464503943920135,-0.08899521082639694,0.6859626173973083,0.7113358974456787,-0.12477951496839523,-0.08890412002801895,0.6867029070854187,0.7106090784072876,-0.12503144145011902,-0.08873330801725388,0.6880892515182495,0.7092437744140625,-0.12516820430755615,-0.08864033222198486,0.6888414025306702,0.7085007429122925,-0.12502163648605347,-0.0887397900223732,0.6880366206169128,0.7092958092689514,-0.12458226829767227,-0.08903743326663971,0.6856170892715454,0.7116746306419373,-0.12404973059892654,-0.08939531445503235,0.6826867461204529,0.7145341634750366,-0.1224508285522461,-0.09045184403657913,0.6738875508308411,0.7229798436164856,-0.12040706723928452,-0.09176498651504517,0.6626408100128174,0.7334770560264587,-0.11664383113384247,-0.0940796360373497,0.6419292092323303,0.7519774436950684,-0.1091940775513649,-0.09829656779766083,0.6009302139282227,0.785682737827301,-0.1001141145825386,-0.10285201668739319,0.550961971282959,0.8220946192741394,-0.09623178094625473,-0.1046237125992775,0.5295953154563904,0.8362548351287842,-0.09731372445821762,-0.10414012521505356,0.535548985004425,0.8323894143104553,-0.09974107891321182,-0.10302663594484329,0.5489085912704468,0.8234905004501343,-0.09936485439538956,-0.10320185869932175,0.5468376874923706,0.8248906135559082,-0.09465040266513824,-0.10531686246395111,0.5208925008773804,0.8417961001396179,-0.09159758687019348,-0.10661040991544724,0.504092276096344,0.8521355986595154,-0.09163536876440048,-0.10659494996070862,0.5042994022369385,0.8520109057426453,-0.09046013653278351,-0.10707785934209824,0.4978320896625519,0.8558706045150757,-0.08977137506008148,-0.10735690593719482,0.4940415024757385,0.8581017255783081,-0.08985794335603714,-0.1073220893740654,0.49451732635498047,0.8578228950500488,-0.08999734371900558,-0.10726581513881683,0.4952847361564636,0.8573724627494812,-0.09093844890594482,-0.10688238590955734,0.5004642009735107,0.8543078899383545,-0.0917743444442749,-0.10653712600469589,0.5050654411315918,0.8515493273735046,-0.092633917927742,-0.10617794096469879,0.5097954273223877,0.8486776947975159,-0.09351533651351929,-0.10580472648143768,0.5146462917327881,0.8456946611404419,-0.09419304132461548,-0.10551434010267258,0.5183759927749634,0.8433746099472046,-0.09533872455358505,-0.10501720756292343,0.5246807932853699,0.8393997550010681,-0.0967504158616066,-0.10439292341470718,0.53244948387146,0.8344094157218933,-0.09772901237010956,-0.10395220667123795,0.5378357768058777,0.8308885097503662,-0.09817074239253998,-0.10375139862298965,0.5402666330337524,0.8292829394340515,-0.09771361947059631,-0.10395937412977219,0.5377506017684937,0.8309445381164551,-0.0963970199227333,-0.10455047339200974,0.5305048227310181,0.8356682658195496,-0.09490101039409637,-0.10520810633897781,0.5222718119621277,0.8409264087677002,-0.09350431710481644,-0.10580946505069733,0.5145848393440247,0.8457326889038086,-0.09236658364534378,-0.1062900498509407,0.5083247423171997,0.8495745062828064,-0.09196998178958893,-0.10645592957735062,0.506141722202301,0.8508990406990051,-0.09148964285850525,-0.10665528476238251,0.5034979581832886,0.8524929285049438,-0.09117789566516876,-0.10678381472826004,0.5017829537391663,0.8535207509994507,-0.09156658500432968,-0.1066233217716217,0.5039209723472595,0.852238655090332,-0.09236384183168411,-0.10629130154848099,0.5083084106445312,0.8495844006538391,-0.09302836656570435,-0.10601136833429337,0.5119668841362,0.8473471403121948,-0.09368226677179337,-0.1057337149977684,0.5155647397041321,0.8451254963874817,-0.09395797550678253,-0.10561554878950119,0.517081618309021,0.8441823720932007,-0.0940331444144249,-0.10558323562145233,0.5174959301948547,0.8439241051673889],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999997019767761,1,0.9999998211860657,0.9999997019767761,0.9999999403953552,0.9999998211860657],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.000003083933506786707,18.7511043548584,0.000008227692887885496,0.000002591224074421916,18.751096725463867,-0.000009111054168897681],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[0.055037721991539,0.05196232721209526,-0.013945172540843487,0.9970337748527527,0.055037721991539,0.05196232348680496,-0.013945179060101509,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,1.0000001192092896,0.9999999403953552,0.9999999403953552,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,2.2333333492279053],"values":[-4.513198375701904,8.246397972106934,-0.081300750374794,-4.513200283050537,8.246406555175781,-0.08130308985710144],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.49666744470596313,-0.6770878434181213,0.3612236976623535,0.4054515063762665,0.4974427819252014,-0.6782218813896179,0.36455038189888,0.3995857834815979,0.5032144784927368,-0.6760953664779663,0.3674081265926361,0.3932957053184509,0.5142820477485657,-0.6699875593185425,0.3683635890483856,0.3885086476802826,0.527980387210846,-0.6611021757125854,0.3663787245750427,0.38723024725914,0.5375939011573792,-0.6527525186538696,0.3613712787628174,0.39283305406570435,0.5496454238891602,-0.6418893933296204,0.3500809669494629,0.4041178524494171,0.5651918053627014,-0.6248830556869507,0.3379085063934326,0.419401079416275,0.5811176300048828,-0.6044450998306274,0.32473814487457275,0.43759992718696594,0.5931506752967834,-0.5837831497192383,0.3102777600288391,0.4594533145427704,0.6084451079368591,-0.5621862411499023,0.2911688983440399,0.4784995913505554,0.6262713670730591,-0.5397151708602905,0.2662374675273895,0.495589941740036,0.6392270922660828,-0.5182316899299622,0.23863071203231812,0.51563560962677,0.6523795127868652,-0.4956530034542084,0.20820152759552002,0.5342109799385071,0.6685413718223572,-0.471228688955307,0.17589117586612701,0.5477758646011353,0.6833645105361938,-0.447398841381073,0.1446387618780136,0.5585041642189026,0.693252682685852,-0.4254993200302124,0.1151885986328125,0.5701601505279541,0.702707052230835,-0.4041057825088501,0.09204837679862976,0.5782979726791382,0.7147601842880249,-0.38349732756614685,0.0743425190448761,0.5801041722297668,0.725990355014801,-0.3604818284511566,0.05402373895049095,0.5831572413444519,0.7374765872955322,-0.3317624628543854,0.02341756969690323,0.5878041386604309,0.7426182627677917,-0.3172491490840912,0.0068688225001096725,0.5897659659385681,0.7476071119308472,-0.30283603072166443,-0.009428372606635094,0.59100341796875,0.7519278526306152,-0.28881990909576416,-0.024769948795437813,0.5920929312705994,0.7549009919166565,-0.2752414643764496,-0.03869350999593735,0.594028115272522,0.7563806176185608,-0.26210737228393555,-0.050770871341228485,0.5971687436103821,0.7567569017410278,-0.24986854195594788,-0.06080709025263786,0.6009885668754578,0.7566026449203491,-0.2402566820383072,-0.06822168827056885,0.6042971014976501,0.752238392829895,-0.22178444266319275,-0.08349380642175674,0.6147990822792053,0.7427849769592285,-0.21567240357398987,-0.0856659859418869,0.6280264854431152,0.7385741472244263,-0.23695264756679535,-0.059556908905506134,0.6283427476882935,0.7409385442733765,-0.2464146614074707,-0.04006810858845711,0.6234456896781921,0.7396363019943237,-0.2663416862487793,0.0009524408960714936,0.6180609464645386,0.7259278297424316,-0.293709397315979,0.0438983291387558,0.6203519105911255,0.6873419284820557,-0.330872505903244,0.10744939744472504,0.6376042366027832,0.6600836515426636,-0.36938321590423584,0.16312125325202942,0.6334327459335327,0.6395408511161804,-0.4229961335659027,0.21818387508392334,0.6037032604217529,0.634691596031189,-0.4415853023529053,0.2333938181400299,0.5896578431129456,0.6430473327636719,-0.44273439049720764,0.2367522120475769,0.5782946944236755,0.6314237713813782,-0.482560932636261,0.29158228635787964,0.5323708653450012,0.6027945280075073,-0.5226975679397583,0.33437347412109375,0.5016177892684937,0.5885868072509766,-0.5439389944076538,0.36646950244903564,0.47264787554740906,0.5827234983444214,-0.5598666667938232,0.380820095539093,0.44939813017845154,0.5696377754211426,-0.5807244181632996,0.3910028636455536,0.43056780099868774,0.5540195107460022,-0.5993592739105225,0.411038339138031,0.4060521423816681,0.5387912392616272,-0.6145740151405334,0.42864134907722473,0.38505756855010986,0.5230472683906555,-0.628473162651062,0.43974846601486206,0.3715701699256897,0.5071960687637329,-0.6410243511199951,0.4411439895629883,0.3704482913017273,0.48866868019104004,-0.6550155282020569,0.4346010684967041,0.37852293252944946,0.47429707646369934,-0.668053388595581,0.4252100884914398,0.3846340775489807,0.46571406722068787,-0.6801526546478271,0.41528984904289246,0.3847559094429016,0.46104148030281067,-0.6888745427131653,0.4077150821685791,0.3829635679721832,0.4607304632663727,-0.6933736801147461,0.40171918272972107,0.3815523087978363,0.4632704257965088,-0.6959660649299622,0.3966245651245117,0.37907880544662476,0.46510636806488037,-0.6974675059318542,0.3917410671710968,0.3791491687297821,0.47499334812164307,-0.6921523809432983,0.3851151168346405,0.38339635729789734,0.4911222755908966,-0.6832343339920044,0.37526971101760864,0.3887960910797119,0.5049066543579102,-0.6754883527755737,0.3636738657951355,0.39563387632369995,0.5157226324081421,-0.6680179834365845,0.3548242747783661,0.4023454487323761,0.5200754404067993,-0.6641813516616821,0.3473167419433594,0.4095799922943115,0.5176120400428772,-0.6638477444648743,0.34372884035110474,0.4162144958972931,0.515765905380249,-0.6641675233840942,0.3447415232658386,0.417157381772995,0.5127554535865784,-0.66583251953125,0.3479312062263489,0.41556334495544434,0.5078399777412415,-0.6680530309677124,0.3522523045539856,0.41439366340637207,0.5028384327888489,-0.6711838245391846,0.35543641448020935,0.4127114415168762,0.4989067316055298,-0.6743712425231934,0.35823988914489746,0.4098532795906067,0.4966673254966736,-0.6770879030227661,0.36122390627861023,0.40545138716697693],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999998807907104,0.9999998211860657,1,0.9999999403953552,0.9999998807907104,1],"interpolation":2300,"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.0000011546336509127286,17.921398162841797,-3.934212884360022e-7,0.000005248984507488785,17.921396255493164,0.000009156929081655107],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.43011873960494995,-0.3805334270000458,0.07614763081073761,0.8151035308837891,0.4255088269710541,-0.3828665316104889,0.07222521305084229,0.8167858123779297,0.4156104624271393,-0.3794012665748596,0.06704171001911163,0.8239102363586426,0.39971256256103516,-0.3667636215686798,0.06006390228867531,0.8379180431365967,0.37932533025741577,-0.3439512848854065,0.05007017403841019,0.8574979901313782,0.3601928651332855,-0.3100418448448181,0.034840911626815796,0.8791594505310059,0.33787310123443604,-0.2700861692428589,0.017361115664243698,0.9014398455619812,0.3080803155899048,-0.23235617578029633,0.0018203476211056113,0.922547459602356,0.27217361330986023,-0.20360277593135834,-0.010968801565468311,0.9403973817825317,0.23458188772201538,-0.18316547572612762,-0.0228442270308733,0.95441073179245,0.19748936593532562,-0.164968341588974,-0.03610220178961754,0.9656500816345215,0.1617945432662964,-0.14606662094593048,-0.0524965338408947,0.9745415449142456,0.12897536158561707,-0.1277749091386795,-0.0713425874710083,0.9807900786399841,0.09956135600805283,-0.11096333712339401,-0.08941866457462311,0.984773576259613,0.07348424941301346,-0.09693210572004318,-0.10532550513744354,0.9869705438613892,0.05194321647286415,-0.08903151005506516,-0.12194015830755234,0.9871706366539001,0.03613653406500816,-0.08806997537612915,-0.14197807013988495,0.9852817058563232,0.02466309256851673,-0.08901475369930267,-0.16495247185230255,0.9819667935371399,0.0144233088940382,-0.08593714237213135,-0.1895684152841568,0.9779931306838989,0.002629216993227601,-0.07676253467798233,-0.21556970477104187,0.9734630584716797,-0.013789276592433453,-0.06279896944761276,-0.24424244463443756,0.9675803780555725,-0.023714818060398102,-0.05554698035120964,-0.2593763768672943,0.9638859033584595,-0.034867025911808014,-0.04842103645205498,-0.27600786089897156,0.9593014717102051,-0.0467890165746212,-0.0416562519967556,-0.2944730222225189,0.9536043405532837,-0.05873612314462662,-0.035378918051719666,-0.31480032205581665,0.9466779828071594,-0.0699981302022934,-0.02951747179031372,-0.3368673324584961,0.9384825229644775,-0.08010256290435791,-0.0238877534866333,-0.3604064881801605,0.929042637348175,-0.08786514401435852,-0.019105546176433563,-0.38127195835113525,0.9200795888900757,-0.10518667846918106,-0.007156287785619497,-0.43713077902793884,0.893197238445282,-0.1367371827363968,0.005590331275016069,-0.5167831182479858,0.8451076745986938,-0.17052669823169708,0.008213508874177933,-0.5826210975646973,0.7946105599403381,-0.1680552065372467,-0.0012594102881848812,-0.6164668798446655,0.7692362666130066,-0.1269359588623047,-0.031403955072164536,-0.623599112033844,0.7707303166389465,-0.07759013772010803,-0.06896299123764038,-0.5974056124687195,0.7951921224594116,-0.03037864714860916,-0.08502040058374405,-0.5215928554534912,0.8484041690826416,-0.004377562552690506,-0.08470179885625839,-0.43722566962242126,0.8953436017036438,-0.024284910410642624,-0.09293536841869354,-0.41608092188835144,0.9042400121688843,-0.04046192392706871,-0.10524077713489532,-0.3757840394973755,0.919822633266449,0.0031554780434817076,-0.15357717871665955,-0.2263689488172531,0.9618529677391052,0.055951301008462906,-0.2698131203651428,-0.07329519838094711,0.9584874510765076,0.08571448177099228,-0.3335474133491516,-0.01762186549603939,0.9386631846427917,0.11192665994167328,-0.3365280032157898,-0.0003837017575278878,0.9349979758262634,0.14202353358268738,-0.32667848467826843,0.016756635159254074,0.9342535138130188,0.17477253079414368,-0.32614564895629883,0.03798834979534149,0.9282459616661072,0.20991645753383636,-0.3259272575378418,0.061619509011507034,0.9197334051132202,0.25337982177734375,-0.3138964772224426,0.08774732053279877,0.9108062982559204,0.3106745779514313,-0.30083799362182617,0.11334029585123062,0.8945008516311646,0.37838706374168396,-0.3066757023334503,0.13081945478916168,0.8635157942771912,0.4460477828979492,-0.33296462893486023,0.13523775339126587,0.8196869492530823,0.5032907724380493,-0.3670409619808197,0.1301722228527069,0.7713848352432251,0.5434510111808777,-0.40132734179496765,0.12149854004383087,0.7272107601165771,0.564907968044281,-0.4301931858062744,0.11297985166311264,0.6950168013572693,0.5709478855133057,-0.4430535137653351,0.1100822240114212,0.68235182762146,0.5649499297142029,-0.4342937171459198,0.1153399795293808,0.6920385360717773,0.5473976135253906,-0.41144701838493347,0.12200822681188583,0.7184575200080872,0.5198725461959839,-0.38829514384269714,0.12170668691396713,0.7510971426963806,0.4875921905040741,-0.37298500537872314,0.11332673579454422,0.7812126278877258,0.45738762617111206,-0.36506187915802,0.10135968774557114,0.8045201301574707,0.43523070216178894,-0.3608601987361908,0.08988631516695023,0.8199235200881958,0.42384395003318787,-0.35820648074150085,0.08030092716217041,0.8280073404312134,0.4211311340332031,-0.35647520422935486,0.07358678430318832,0.8307580947875977,0.4225795865058899,-0.35571256279945374,0.07136396318674088,0.8305433988571167,0.425256609916687,-0.3572721481323242,0.07343674451112747,0.8283239006996155,0.42827367782592773,-0.3625529110431671,0.07677248865365982,0.8241620063781738,0.43076279759407043,-0.3698538839817047,0.07873225957155228,0.81942218542099,0.43147122859954834,-0.37584158778190613,0.07849868386983871,0.8163416385650635,0.43011876940727234,-0.3805336058139801,0.07614777237176895,0.8151034116744995],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,0.9999999403953552,0.9999998807907104,1,0.9999999403953552,1],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.000005416594831331167,17.203702926635742,-0.0000011524932688189438,-0.000004652420102502219,17.20370101928711,-0.0000016363179611289524],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[-0.10678520798683167,0.1250356137752533,-0.5705930590629578,0.8046033382415771,-0.10690063238143921,0.1249670684337616,-0.5712084174156189,0.8041619658470154,-0.1078081801533699,0.12442248314619064,-0.5760589241981506,0.8006575703620911,-0.10983452945947647,0.12318137288093567,-0.5868866443634033,0.792670726776123,-0.1127372458577156,0.12134016305208206,-0.6023972630500793,0.7808229327201843,-0.11520272493362427,0.11971583217382431,-0.615570604801178,0.7703696489334106,-0.11661992222070694,0.11875586211681366,-0.6231427788734436,0.7641923427581787,-0.11703887581825256,0.11846812069416046,-0.6253819465637207,0.7623415589332581,-0.11689292639493942,0.11856868863105774,-0.6246013045310974,0.7629880309104919,-0.11665371060371399,0.11873254925012589,-0.6233237385749817,0.7640432119369507,-0.1163935661315918,0.1189102828502655,-0.621934175491333,0.7651867866516113,-0.11584341526031494,0.11928412318229675,-0.6189942955970764,0.7675921320915222,-0.11510779708623886,0.11977938562631607,-0.6150636672973633,0.7707787156105042,-0.11472777277231216,0.1200331598520279,-0.6130328178405762,0.772412121295929,-0.11485358327627182,0.1199493259191513,-0.6137046813964844,0.7718726992607117,-0.11499129980802536,0.11985720694065094,-0.6144406199455261,0.7712807655334473,-0.11488224565982819,0.1199302151799202,-0.6138584017753601,0.7717490792274475,-0.11486060172319412,0.11994466185569763,-0.6137427687644958,0.7718420624732971,-0.11509257555007935,0.11978960782289505,-0.6149817705154419,0.7708447575569153,-0.11523833870887756,0.11969194561243057,-0.6157610416412354,0.7702158093452454,-0.11489012837409973,0.11992489546537399,-0.613900363445282,0.7717154026031494,-0.11448902636766434,0.12019197642803192,-0.6117568016052246,0.7734337449073792,-0.11390894651412964,0.12057536095380783,-0.608657717704773,0.7759009003639221,-0.11312693357467651,0.12108735740184784,-0.6044790744781494,0.779195249080658,-0.1121320053935051,0.12173043936491013,-0.5991630554199219,0.7833337187767029,-0.11093738675117493,0.12249072641134262,-0.5927800536155701,0.788226306438446,-0.1095895767211914,0.12333337217569351,-0.5855774283409119,0.7936486005783081,-0.1083645150065422,0.12408529222011566,-0.5790313482284546,0.7984877228736877,-0.1053093671798706,0.1259048879146576,-0.5627075433731079,0.8101963996887207,-0.10093139857053757,0.12837930023670197,-0.539313018321991,0.8261193037033081,-0.0966198742389679,0.13067030906677246,-0.5162762403488159,0.8408619165420532,-0.10011895000934601,0.12882183492183685,-0.5349723100662231,0.8289667367935181,-0.1144527718424797,0.12021587789058685,-0.6115632653236389,0.7735884189605713,-0.12902754545211792,0.10944905132055283,-0.6894416809082031,0.7043032050132751,-0.13499608635902405,0.10431211441755295,-0.7213338017463684,0.6712470650672913,-0.1252754181623459,0.1124453991651535,-0.6693927645683289,0.7235851287841797,-0.09502752870321274,0.13148142397403717,-0.5077674388885498,0.8460819125175476,-0.05841061845421791,0.145610511302948,-0.31210967898368835,0.9370023012161255,-0.03254063427448273,0.15113554894924164,-0.17387647926807404,0.9725565314292908,-0.01884496957063675,0.15274952352046967,-0.1006959080696106,0.9829409122467041,-0.020439045503735542,0.15260660648345947,-0.10921365022659302,0.9820213317871094,-0.0292726531624794,0.15160056948661804,-0.1564147025346756,0.975548505783081,-0.04030677676200867,0.1498257964849472,-0.21537496149539948,0.9641271829605103,-0.05201778560876846,0.14728997647762299,-0.2779502272605896,0.9478098750114441,-0.06444116681814194,0.14382685720920563,-0.34433305263519287,0.9255247116088867,-0.07633457332849503,0.1397121697664261,-0.4078829288482666,0.899046778678894,-0.08538765460252762,0.13601209223270416,-0.4562576711177826,0.8752363324165344,-0.09019307792186737,0.13383333384990692,-0.4819347858428955,0.8612158298492432,-0.09125183522701263,0.13333219289779663,-0.4875919818878174,0.8579916954040527,-0.09057608246803284,0.13365289568901062,-0.4839809238910675,0.8600554466247559,-0.09052871912717819,0.13367518782615662,-0.4837290048599243,0.8601987361907959,-0.09243635088205338,0.13276252150535583,-0.49392053484916687,0.8543255925178528,-0.09617123752832413,0.13090074062347412,-0.5138776302337646,0.8423454761505127,-0.10095422714948654,0.12836666405200958,-0.5394349694252014,0.8260388374328613,-0.10593558102846146,0.1255384087562561,-0.5660525560379028,0.8078380227088928,-0.10999688506126404,0.12308050692081451,-0.5877532958984375,0.7920214533805847,-0.11201268434524536,0.12180694937705994,-0.5985243320465088,0.7838270664215088,-0.11194028705358505,0.12185334414243698,-0.5981380939483643,0.7841249704360962,-0.11081810295581818,0.12256598472595215,-0.5921413898468018,0.7887113094329834,-0.10955080389976501,0.12335755676031113,-0.5853692889213562,0.7938036918640137,-0.10845597833395004,0.12402978539466858,-0.5795189738273621,0.7981300950050354,-0.10761020332574844,0.12454193830490112,-0.5750012397766113,0.801425576210022,-0.10706944018602371,0.12486608326435089,-0.5721118450164795,0.803512692451477,-0.10682889074087143,0.12500979006290436,-0.5708252191543579,0.8044369220733643,-0.10686877369880676,0.12498623877763748,-0.5710386037826538,0.804283857345581,-0.10687261074781418,0.1249837726354599,-0.5710594058036804,0.8042688965797424,-0.10678530484437943,0.12503577768802643,-0.5705928802490234,0.8046035170555115],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,1,0.9999998807907104,0.9999998807907104,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.000007421367627102882,19.45051383972168,-0.000006413127266569063,0.000005382763447414618,19.45050811767578,0.000009427546501683537],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[0.14937160909175873,-0.14748123288154602,-0.019200174137949944,0.9775319695472717,0.14937159419059753,-0.14748123288154602,-0.019200172275304794,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999999403953552,1,0.9999998807907104,0.9999999403953552,1,0.9999997615814209],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.14689956605434418,6.409504413604736,0.25809937715530396,0.1469009965658188,6.409513473510742,0.25809839367866516],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[0.020259661599993706,0.012515298090875149,-0.011196221224963665,0.999653697013855,0.020259665325284004,0.012515276670455933,-0.011196224950253963,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1.0000001192092896,1.000000238418579,1.0000001192092896,1.0000001192092896,1.0000003576278687,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.000003081421937167761,6.815906524658203,0.000001158823124569608,0.0000022543122213392053,6.815891265869141,8.743759281060193e-7],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053],"values":[0.08975597470998764,0.4256119430065155,-0.06710249930620193,0.8979396820068359,0.08839660882949829,0.4198620319366455,-0.06642214208841324,0.9008274078369141,0.09014789760112762,0.41368985176086426,-0.06528127938508987,0.9035886526107788,0.0960996150970459,0.4082948863506317,-0.0639466792345047,0.9055224657058716,0.1046154797077179,0.4050544500350952,-0.06371843069791794,0.9060499668121338,0.10685607045888901,0.4035417437553406,-0.06962959468364716,0.9060284495353699,0.10555461794137955,0.40057259798049927,-0.08079946786165237,0.9065711498260498,0.10442114621400833,0.39300045371055603,-0.09317721426486969,0.9088261127471924,0.1030816063284874,0.3804452419281006,-0.09877394884824753,0.9137173295021057,0.11061467975378036,0.353174090385437,-0.09701374918222427,0.9239160418510437,0.1212460994720459,0.3176979720592499,-0.08502340316772461,0.9365566968917847,0.1306627094745636,0.28175032138824463,-0.06764928996562958,0.9481390118598938,0.14413902163505554,0.23776502907276154,-0.049094103276729584,0.9593130350112915,0.15940658748149872,0.19265519082546234,-0.029768388718366623,0.9677744507789612,0.17375722527503967,0.14224109053611755,-0.010387826710939407,0.9744064807891846,0.1884230673313141,0.08569367229938507,0.008624488487839699,0.9783040881156921,0.20403428375720978,0.02699904888868332,0.025142071768641472,0.9782683253288269,0.2201198786497116,-0.03511261194944382,0.044382769614458084,0.9738298654556274,0.23259174823760986,-0.09378854930400848,0.07168013602495193,0.965384304523468,0.23575717210769653,-0.13705673813819885,0.09598293900489807,0.957298994064331,0.24451813101768494,-0.16706927120685577,0.11584436148405075,0.9480922222137451,0.2495187669992447,-0.17448075115680695,0.12391051650047302,0.9444273710250854,0.25268951058387756,-0.17729651927947998,0.13143828511238098,0.9420392513275146,0.2549605667591095,-0.17817804217338562,0.13873422145843506,0.9402130246162415,0.25803518295288086,-0.17873139679431915,0.1452922374010086,0.9382767081260681,0.26233214139938354,-0.178695946931839,0.1502734273672104,0.9363052248954773,0.2668558657169342,-0.17722094058990479,0.1531408727169037,0.934841513633728,0.27015960216522217,-0.17470671236515045,0.1540491282939911,0.9342163801193237,0.2717013359069824,-0.1647641807794571,0.1516556292772293,0.9359656572341919,0.26954972743988037,-0.15529164671897888,0.1601770669221878,0.936787486076355,0.26584768295288086,-0.157423198223114,0.1723770946264267,0.9353229999542236,0.26234230399131775,-0.16008515655994415,0.16875280439853668,0.9365211129188538,0.25374510884284973,-0.1328716278076172,0.1437775045633316,0.9472521543502808,0.24225850403308868,-0.06896567344665527,0.09629160910844803,0.962955117225647,0.21288520097732544,0.030445940792560577,0.03624444454908371,0.9759299755096436,0.1550508737564087,0.14933668076992035,-0.005479061976075172,0.9765386581420898,0.13002915680408478,0.24678413569927216,-0.02080184780061245,0.960081934928894,0.13358284533023834,0.29604724049568176,-0.011525316163897514,0.9457160234451294,0.12077677994966507,0.2792591154575348,0.02588491328060627,0.9522380232810974,0.0876106470823288,0.26320409774780273,0.035018231719732285,0.9601154923439026,0.07052669674158096,0.2903220057487488,-0.002050852170214057,0.9543243050575256,0.0709218755364418,0.32911646366119385,-0.025219226256012917,0.9412844777107239,0.07818352431058884,0.3713657259941101,-0.03573428839445114,0.9244987368583679,0.08968593925237656,0.4028787314891815,-0.044768236577510834,0.9097477793693542,0.09728772193193436,0.41990402340888977,-0.04999718815088272,0.9009528756141663,0.10442277789115906,0.42970871925354004,-0.0540194995701313,0.8952810764312744,0.11225065588951111,0.43472805619239807,-0.059036023914813995,0.8915862441062927,0.1187816858291626,0.4378257691860199,-0.0629083588719368,0.8889555931091309,0.12094492465257645,0.44055187702178955,-0.06547895073890686,0.8871296048164368,0.11684514582157135,0.44475266337394714,-0.06833599507808685,0.8853657245635986,0.11116228252649307,0.447238028049469,-0.07099021226167679,0.8846363425254822,0.10511395335197449,0.4445801377296448,-0.07343113422393799,0.8865142464637756,0.10007768869400024,0.43959107995033264,-0.07265537977218628,0.8896434307098389,0.09947068989276886,0.43742066621780396,-0.06890064477920532,0.8910788297653198,0.1039915606379509,0.436788409948349,-0.06499584019184113,0.8911662101745605,0.10631151497364044,0.4363238215446472,-0.062477193772792816,0.8913001418113708,0.10559768229722977,0.43740910291671753,-0.061983462423086166,0.890887439250946,0.10422315448522568,0.4385400712490082,-0.06347224116325378,0.8903883695602417,0.10114637762308121,0.4409501850605011,-0.06665842980146408,0.8893194198608398,0.10096102952957153,0.4445759356021881,-0.06808142364025116,0.8874255418777466,0.10211968421936035,0.44534021615982056,-0.07048210501670837,0.8867220282554626,0.10141564905643463,0.44398465752601624,-0.07129120826721191,0.8874176740646362,0.09849617630243301,0.44153380393981934,-0.07096374779939651,0.8889941573143005,0.09498226642608643,0.43888407945632935,-0.0703614130616188,0.8907347917556763,0.09346788376569748,0.4350736439228058,-0.06882201135158539,0.8928819894790649,0.09184617549180984,0.4306333661079407,-0.06775512546300888,0.895281195640564,0.08975506573915482,0.4256119430065155,-0.06710261106491089,0.8979397416114807],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.9999998807907104,0.9999997615814209,0.9999997615814209,0.9999999403953552,0.9999998211860657,0.9999999403953552],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,2.2333333492279053],"values":[0.5746007561683655,51.81689453125,-20.522703170776367,0.5746005773544312,51.81690216064453,-20.5226993560791],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[-0.18743079900741577,9.724349325779258e-8,-0.0052477894350886345,0.9822638034820557,-0.18743081390857697,9.902125697180963e-8,-0.00524778850376606,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1.000000238418579,1.000000238418579,1.0000001192092896,1.000000238418579,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,2.2333333492279053],"values":[-0.574601948261261,4.330287456512451,20.522701263427734,-0.5745999217033386,4.330304145812988,20.522706985473633],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[0.6296831369400024,-7.974350779704764e-8,0.017631012946367264,0.7766519784927368,0.6296830773353577,-7.015029268586659e-8,0.01763099990785122,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,2.2333333492279053],"values":[1,1.0000003576278687,1.000000238418579,0.9999999403953552,1.0000003576278687,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,2.2333333492279053],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,2.2333333492279053],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,2.2333333492279053],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"9a057740-8514-45af-ade4-6ad942ec2957","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/sword-and-shield-slash.json b/public/anims/baked/sword_shield/sword-and-shield-slash.json new file mode 100644 index 0000000..f25c184 --- /dev/null +++ b/public/anims/baked/sword_shield/sword-and-shield-slash.json @@ -0,0 +1 @@ +{"name":"sword_shield/sword-and-shield-slash-mixamo","duration":1.5333333015441895,"tracks":[{"name":"mixamorigHips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.04599013924598694,-0.042483292520046234,-6.352517604827881,0.07469119131565094,-0.08294094353914261,-6.356009483337402,0.11561783403158188,-0.0764215886592865,-6.356579303741455,0.165139839053154,-0.04384167119860649,-6.3559346199035645,0.2175895720720291,0.01716504991054535,-6.3529052734375,0.26413771510124207,0.10895561426877975,-6.3415374755859375,0.2810644805431366,0.26787877082824707,-6.308513164520264,0.2791910469532013,0.4612480700016022,-6.273069381713867,0.2684565782546997,0.6602643728256226,-6.227454662322998,0.27607202529907227,0.8720841407775879,-6.171060562133789,0.3115127384662628,1.099321722984314,-6.111789226531982,0.35405945777893066,1.332561731338501,-6.098039150238037,0.3846522569656372,1.5946637392044067,-6.104922294616699,0.39758020639419556,1.8939019441604614,-6.1149001121521,0.3990916907787323,2.230337142944336,-6.050403118133545,0.4088248014450073,2.536344051361084,-5.919595718383789,0.4160514175891876,2.749762535095215,-5.757190227508545,0.41691824793815613,2.9288437366485596,-5.601572513580322,0.406356543302536,3.0986077785491943,-5.499574661254883,0.38913390040397644,3.2551658153533936,-5.448333740234375,0.3655170500278473,3.386028289794922,-5.426460266113281,0.335942804813385,3.4799795150756836,-5.425749778747559,0.30217233300209045,3.553077220916748,-5.440913200378418,0.2681155800819397,3.6201088428497314,-5.458433151245117,0.23560069501399994,3.6791763305664062,-5.476327896118164,0.2090250551700592,3.71878719329834,-5.494795799255371,0.20576834678649902,3.6963324546813965,-5.517647743225098,0.2093113213777542,3.656679630279541,-5.541645526885986,0.21929797530174255,3.6020076274871826,-5.566289901733398,0.24613910913467407,3.5110697746276855,-5.591713905334473,0.27940505743026733,3.40706205368042,-5.611250400543213,0.3294682502746582,3.250298261642456,-5.61549711227417,0.3589785397052765,3.082918882369995,-5.6146111488342285,0.3873971104621887,2.901970386505127,-5.63161039352417,0.3837195038795471,2.6795542240142822,-5.723923683166504,0.3551110625267029,2.3899264335632324,-5.823490619659424,0.3238595426082611,2.0683400630950928,-5.907636642456055,0.2912534475326538,1.7315367460250854,-5.990912437438965,0.25755923986434937,1.3911986351013184,-6.066224575042725,0.22003670036792755,1.071058988571167,-6.124321937561035,0.1776394248008728,0.7846628427505493,-6.184137344360352,0.14308369159698486,0.5803263783454895,-6.229952335357666,0.11485619097948074,0.4083241820335388,-6.26582670211792,0.0909317135810852,0.2555330991744995,-6.296230792999268,0.06873396039009094,0.10951278358697891,-6.324249744415283,0.045990146696567535,-0.0424831360578537,-6.352517604827881],"type":"vector"},{"name":"mixamorigHips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.5746455192565918,-0.3002378046512604,0.3436598777770996,0.6793656945228577,-0.5728325247764587,-0.29417943954467773,0.3558943271636963,0.6772448420524597,-0.5710993409156799,-0.2869534194469452,0.36789003014564514,0.6753963828086853,-0.5692316889762878,-0.28006866574287415,0.3800078332424164,0.6731500029563904,-0.5671006441116333,-0.27489861845970154,0.39268022775650024,0.6697984337806702,-0.5642299056053162,-0.27347174286842346,0.4060455560684204,0.6648194193840027,-0.5595490336418152,-0.27934932708740234,0.42107143998146057,0.6569381952285767,-0.5503419637680054,-0.27890855073928833,0.4364827573299408,0.6548409461975098,-0.5381320714950562,-0.2795563340187073,0.44768211245536804,0.6571474671363831,-0.5298002362251282,-0.2740978002548218,0.4478974938392639,0.6660104990005493,-0.5277405381202698,-0.262125164270401,0.43349361419677734,0.6818090677261353,-0.5331448912620544,-0.2527143955230713,0.4045809209346771,0.6987175941467285,-0.543838620185852,-0.24197864532470703,0.35903075337409973,0.7188761234283447,-0.5574019551277161,-0.22389115393161774,0.29560497403144836,0.7428280711174011,-0.5801987648010254,-0.17756974697113037,0.2104770392179489,0.7665101289749146,-0.6015244126319885,-0.11643273383378983,0.12561050057411194,0.7802780866622925,-0.6144793033599854,-0.04899998754262924,0.06720399856567383,0.7845367193222046,-0.6204741597175598,0.01667427271604538,0.020981162786483765,0.783768892288208,-0.6125099062919617,0.0665939673781395,-0.018251357600092888,0.7874412536621094,-0.5953221917152405,0.09981381148099899,-0.05219992622733116,0.7955525517463684,-0.5732914805412292,0.12163246423006058,-0.07886305451393127,0.8064261078834534,-0.549005925655365,0.13270309567451477,-0.09648098796606064,0.8195571303367615,-0.5227570533752441,0.1361849308013916,-0.10893117636442184,0.8344535827636719,-0.4959205090999603,0.13771416246891022,-0.12005360424518585,0.8489315509796143,-0.46964046359062195,0.13745586574077606,-0.1297973245382309,0.8623783588409424,-0.44710689783096313,0.13334573805332184,-0.13641095161437988,0.8739029765129089,-0.4402921795845032,0.11668344587087631,-0.13273869454860687,0.8802887201309204,-0.4378516972064972,0.096415214240551,-0.1259543001651764,0.8849437832832336,-0.43894651532173157,0.07282969355583191,-0.11556610465049744,0.8880688548088074,-0.4484962821006775,0.04097244516015053,-0.09526896476745605,0.8877478241920471,-0.4594461917877197,0.006133439484983683,-0.06830796599388123,0.8855538368225098,-0.48094016313552856,-0.04388447478413582,-0.02011989988386631,0.8754233121871948,-0.5081625580787659,-0.11456415057182312,0.03494827076792717,0.8528918623924255,-0.5306448936462402,-0.18588586151599884,0.09653343260288239,0.8213061690330505,-0.5500385165214539,-0.23288536071777344,0.1531583070755005,0.7872512936592102,-0.5548037886619568,-0.2558983564376831,0.19825008511543274,0.7664240002632141,-0.5554661750793457,-0.2625735402107239,0.23089124262332916,0.7544546127319336,-0.5559462904930115,-0.26610609889030457,0.26032620668411255,0.7431968450546265,-0.5564600825309753,-0.26822134852409363,0.28637275099754333,0.7323934435844421,-0.5570847988128662,-0.2707236111164093,0.3076384961605072,0.7223045229911804,-0.5586509704589844,-0.27749621868133545,0.3250347673892975,0.7108146548271179,-0.5616815686225891,-0.28332123160362244,0.33264654874801636,0.7025589942932129,-0.5649226903915405,-0.28794753551483154,0.3369026780128479,0.6960209608078003,-0.568238377571106,-0.2919926643371582,0.3392179608345032,0.6904901266098022,-0.5715345740318298,-0.2959090769290924,0.341007262468338,0.6852007508277893,-0.5746455192565918,-0.30023783445358276,0.3436598479747772,0.6793656945228577],"type":"quaternion"},{"name":"mixamorigHips.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1,1,1,0.9999999403953552,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"mixamorigSpine.position","times":[0.03333333507180214,1.5333333015441895],"values":[6.51925802230835e-9,0.7392991781234741,-0.023399941623210907,6.426125764846802e-8,0.7393001317977905,-0.02339996211230755],"type":"vector"},{"name":"mixamorigSpine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.047975506633520126,0.032151054590940475,-0.025256643071770668,0.9980114102363586,0.04649200290441513,0.028828836977481842,-0.014812570065259933,0.9983927011489868,0.04873182252049446,0.01949293538928032,-0.004824022762477398,0.9986100196838379,0.055053651332855225,0.005927929189056158,0.00434740399941802,0.9984563589096069,0.06259944289922714,-0.010106958448886871,0.012129013426601887,0.9979138374328613,0.06867116689682007,-0.026339473202824593,0.018851131200790405,0.9971134066581726,0.06886593252420425,-0.04063411429524422,0.02458026073873043,0.9964949488639832,0.05833696573972702,-0.054780807346105576,0.029724184423685074,0.9963495135307312,0.04623030126094818,-0.06622828543186188,0.033912304788827896,0.9961559176445007,0.03404190018773079,-0.07418940216302872,0.03683045506477356,0.99598228931427,0.021326767280697823,-0.07736463099718094,0.037820857018232346,0.9960569739341736,0.022699737921357155,-0.06790740042924881,0.03231753781437874,0.9969096779823303,0.028945015743374825,-0.04441460222005844,0.02306576631963253,0.998327374458313,0.03858301416039467,-0.009000885300338268,0.012739007361233234,0.9991336464881897,0.05227236822247505,0.02564947120845318,0.0008760713972151279,0.9983030557632446,0.06966068595647812,0.04932577535510063,-0.014185691252350807,0.9962495565414429,0.0926663801074028,0.06147273629903793,-0.039234280586242676,0.9930230379104614,0.11537802219390869,0.07261580228805542,-0.06632015854120255,0.9884414672851562,0.12885689735412598,0.0943557620048523,-0.08508452028036118,0.9834904670715332,0.13351976871490479,0.12651892006397247,-0.09635623544454575,0.9782028794288635,0.1354762762784958,0.15246319770812988,-0.10742012411355972,0.9730684161186218,0.13644647598266602,0.16549575328826904,-0.12034351378679276,0.9692837595939636,0.1358116865158081,0.1718049794435501,-0.1333198994398117,0.9665733575820923,0.13485100865364075,0.17646180093288422,-0.14614848792552948,0.9640109539031982,0.13361403346061707,0.17965443432331085,-0.15850698947906494,0.9616377353668213,0.13160429894924164,0.1797155886888504,-0.16924601793289185,0.9600721001625061,0.12682978808879852,0.16975030303001404,-0.1738886684179306,0.9616973400115967,0.12123485654592514,0.15701325237751007,-0.1766989529132843,0.9640676379203796,0.1150568351149559,0.14218786358833313,-0.1780342012643814,0.9668755531311035,0.10779853910207748,0.12281370162963867,-0.17586903274059296,0.9707555770874023,0.10080276429653168,0.1037508100271225,-0.17221824824810028,0.9743795394897461,0.09432767331600189,0.08336642384529114,-0.1671696901321411,0.9778581857681274,0.09278082847595215,0.06590181589126587,-0.16064469516277313,0.980429470539093,0.09430819004774094,0.04563651978969574,-0.15299533307552338,0.9826574921607971,0.09216657280921936,0.029813585802912712,-0.14466439187526703,0.9847277402877808,0.082773856818676,0.015539036132395267,-0.13556106388568878,0.9871829748153687,0.07136896997690201,0.006964528933167458,-0.125838503241539,0.9894557595252991,0.0591859333217144,-0.0008165847393684089,-0.11538984626531601,0.9915550947189331,0.04888380691409111,-0.005976507440209389,-0.10431204736232758,0.9933245778083801,0.045030128210783005,-0.0040662758983671665,-0.09287402033805847,0.9946507811546326,0.049123503267765045,0.00184047338552773,-0.08135202527046204,0.9954724311828613,0.05124368518590927,0.007805658970028162,-0.07028952240943909,0.9961789846420288,0.05141765996813774,0.013768069446086884,-0.05917045846581459,0.9968277812004089,0.050414130091667175,0.01983581855893135,-0.04794290289282799,0.9973797798156738,0.049009960144758224,0.025962457060813904,-0.03665381297469139,0.9977878332138062,0.04797551408410072,0.0321509875357151,-0.02525661699473858,0.9980114102363586],"type":"quaternion"},{"name":"mixamorigSpine.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999999403953552,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"mixamorigSpine1.position","times":[0.03333333507180214,1.5333333015441895],"values":[0,0.8630003929138184,-1.7508865823856468e-7,2.9802322387695312e-8,0.8630003929138184,-3.725307173851888e-9],"type":"vector"},{"name":"mixamorigSpine1.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.01080038957297802,0.011527778580784798,0.020791934803128242,0.9996590614318848,0.005328483413904905,0.010546036995947361,0.019118191674351692,0.9997473955154419,-0.0026019392535090446,0.007753899786621332,0.014900424517691135,0.9998555183410645,-0.00535432668402791,0.0035797760356217623,0.009231142699718475,0.9999366402626038,-0.006941731087863445,-0.0012899175053462386,0.003065863624215126,0.9999703764915466,-0.008300546556711197,-0.006237100809812546,-0.0026016314513981342,0.9999427199363708,-0.009058273397386074,-0.01065280195325613,-0.0067773559130728245,0.9998793005943298,-0.010553324595093727,-0.015152686275541782,-0.008476928807795048,0.9997935891151428,-0.011516979895532131,-0.019008517265319824,-0.009812133386731148,0.999704897403717,-0.012453390285372734,-0.02184106409549713,-0.010541661642491817,0.9996283054351807,-0.013980120420455933,-0.023143475875258446,-0.011025411076843739,0.9995735883712769,-0.01410338468849659,-0.020064648240804672,-0.009052963927388191,0.9996582269668579,-0.015062016434967518,-0.012916899286210537,-0.004937413614243269,0.9997909665107727,-0.016639843583106995,-0.0025171826127916574,0.0005948022590018809,0.9998582005500793,-0.015174523927271366,0.007522691041231155,0.0046414597891271114,0.9998458027839661,-0.012061589397490025,0.015141773968935013,0.004284470342099667,0.9998034238815308,-0.006420357618480921,0.02007463574409485,0.0013005982618778944,0.9997770190238953,0.00009778083767741919,0.024454394355416298,-0.002955680713057518,0.9996965527534485,0.006405235268175602,0.030920524150133133,-0.007920605130493641,0.9994699358940125,0.012859801761806011,0.03973154351115227,-0.013860128819942474,0.9990314841270447,0.018490249291062355,0.04662569239735603,-0.01985832490026951,0.9985438585281372,0.022650791332125664,0.04977496340870857,-0.025379732251167297,0.9981809854507446,0.025982484221458435,0.05095556378364563,-0.03059116192162037,0.9978941082954407,0.029101865366101265,0.051712360233068466,-0.03569904342293739,0.9975993633270264,0.03194770961999893,0.05213117226958275,-0.0406191349029541,0.997302234172821,0.03403607755899429,0.05180976539850235,-0.044970884919166565,0.997063159942627,0.033406805247068405,0.04905837029218674,-0.047264501452445984,0.9971175193786621,0.031989943236112595,0.04563487693667412,-0.04890690743923187,0.9972473382949829,0.02987571805715561,0.041695110499858856,-0.04971181973814964,0.9974455833435059,0.026107707992196083,0.03662577643990517,-0.048005662858486176,0.9978338479995728,0.0220066849142313,0.031620174646377563,-0.04390950873494148,0.9982924461364746,0.01655227318406105,0.02629144862294197,-0.0341455414891243,0.9989338517189026,0.012199489399790764,0.021916592493653297,-0.027427885681390762,0.9993090033531189,0.007061388809233904,0.016498547047376633,-0.02326316572725773,0.9995682835578918,0.0037427139468491077,0.01175295002758503,-0.01967049576342106,0.9997304081916809,0.0015346793225035071,0.0083328140899539,-0.017307406291365623,0.9998143315315247,-0.0004130742163397372,0.00559106795117259,-0.016247745603322983,0.9998522996902466,-0.002736787311732769,0.002936094766482711,-0.015929780900478363,0.9998650550842285,-0.004824365489184856,0.000972526497207582,-0.01588292606174946,0.9998617172241211,-0.005962517578154802,0.0010903934016823769,-0.01565553992986679,0.999859094619751,-0.005624928046017885,0.0024504647590219975,-0.014352911151945591,0.9998781681060791,-0.0031024059280753136,0.0042378101497888565,-0.009010362438857555,0.9999455809593201,0.00010459069017088041,0.006097849458456039,-0.0022028954699635506,0.9999789595603943,0.003691438352689147,0.007964091375470161,0.005449326243251562,0.999946653842926,0.007356119807809591,0.009785334579646587,0.013322963379323483,0.9998363256454468,0.010800348594784737,0.011527875438332558,0.020791908726096153,0.9996590614318848],"type":"quaternion"},{"name":"mixamorigSpine1.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999998807907104,0.9999999403953552,1,0.9999998807907104,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"mixamorigSpine2.position","times":[0.03333333507180214,1.5333333015441895],"values":[9.313225746154785e-9,0.986201286315918,5.960465898624534e-8,1.7695128917694092e-7,0.9862012267112732,-8.940696005765858e-8],"type":"vector"},{"name":"mixamorigSpine2.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.0019297668477520347,0.011020826175808907,0.009534244425594807,0.9998919367790222,-0.0021056663244962692,0.010202186182141304,0.009038758464157581,0.9999048709869385,-0.007621854543685913,0.007801090367138386,0.00661975285038352,0.9999186396598816,-0.008388032205402851,0.004052453208714724,0.001971531892195344,0.9999546408653259,-0.008167260326445103,-0.0005034843343310058,-0.003009449690580368,0.9999620318412781,-0.008255203254520893,-0.005374637898057699,-0.0064641195349395275,0.9999305605888367,-0.008983383886516094,-0.010219769552350044,-0.008218413218855858,0.9998736381530762,-0.010442409664392471,-0.014825131744146347,-0.009670249186456203,0.9997888207435608,-0.011388594284653664,-0.018841182813048363,-0.010739153251051903,0.9996999502182007,-0.012382752262055874,-0.021829960867762566,-0.011334072798490524,0.9996207356452942,-0.013723555020987988,-0.02308560721576214,-0.011387890204787254,0.9995744228363037,-0.015065941028296947,-0.02000540867447853,-0.009354151785373688,0.9996426105499268,-0.01616477034986019,-0.012884814292192459,-0.005124947056174278,0.9997732043266296,-0.016667820513248444,-0.002548991935327649,0.0005671613616868854,0.9998576641082764,-0.01523586455732584,0.0074597811326384544,0.004787654150277376,0.9998446702957153,-0.012321846559643745,0.015224047936499119,0.004478374030441046,0.9997981786727905,-0.006960785016417503,0.02040215954184532,0.0014152821386232972,0.9997666478157043,-0.0007134473999030888,0.025011621415615082,-0.0029968740418553352,0.9996824264526367,0.005333117209374905,0.03154757246375084,-0.008260523900389671,0.9994539022445679,0.01152072288095951,0.04027746990323067,-0.014669851399958134,0.9990144371986389,0.017063500359654427,0.04700218141078949,-0.021053964272141457,0.9985271096229553,0.021366050466895103,0.049986112862825394,-0.026791725307703018,0.9981618523597717,0.02496819570660591,0.05103680491447449,-0.032165493816137314,0.9978663325309753,0.028379768133163452,0.05167883634567261,-0.037423886358737946,0.9975587129592896,0.03151308000087738,0.052009738981723785,-0.04248128458857536,0.9972448348999023,0.033840976655483246,0.05167190358042717,-0.04693608358502388,0.9969863891601562,0.033251188695430756,0.04900258779525757,-0.04913286864757538,0.9970351457595825,0.03182050213217735,0.045667000114917755,-0.05061185359954834,0.9971662163734436,0.029665367677807808,0.041730549186468124,-0.05115572363138199,0.997377336025238,0.02582065761089325,0.03644239902496338,-0.048954516649246216,0.9978019595146179,0.02173098549246788,0.031408827751874924,-0.044477660208940506,0.9982800483703613,0.016425330191850662,0.026245499029755592,-0.03445408493280411,0.99892657995224,0.012036445550620556,0.02204776555299759,-0.027657758444547653,0.9993017911911011,0.006842008326202631,0.016833677887916565,-0.023423150181770325,0.9995604753494263,0.003507168497890234,0.012193531729280949,-0.0198102667927742,0.9997232556343079,0.0012294002808630466,0.008796431124210358,-0.01746951974928379,0.9998079538345337,-0.0006957422010600567,0.006006020121276379,-0.016407929360866547,0.9998471140861511,-0.0029394293669611216,0.003260623896494508,-0.016061214730143547,0.9998613595962524,-0.004932681564241648,0.001193116302601993,-0.015989750623703003,0.9998592734336853,-0.006021111272275448,0.0012314143823459744,-0.015738846734166145,0.9998572468757629,-0.00572394672781229,0.002552141435444355,-0.014467212371528149,0.9998757243156433,-0.004428993444889784,0.004201288800686598,-0.010652008466422558,0.9999246597290039,-0.002916525350883603,0.005903884302824736,-0.00597933866083622,0.9999604225158691,-0.0012870534555986524,0.007626963313668966,-0.0008136896067298949,0.9999697804450989,0.0003606713726185262,0.009340857155621052,0.004479570314288139,0.9999462962150574,0.0019298146944493055,0.011020750738680363,0.009534256532788277,0.9998919367790222],"type":"quaternion"},{"name":"mixamorigSpine2.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1.0000001192092896,1.0000001192092896,0.9999999403953552,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"mixamorigNeck.position","times":[0.03333333507180214,1.5333333015441895],"values":[-8.940696716308594e-8,1.1094982624053955,-2.547017530218909e-8,-1.9371509552001953e-7,1.1094982624053955,-8.551662489253431e-8],"type":"vector"},{"name":"mixamorigNeck.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.0015012508956715465,0.08531178534030914,-0.01367962546646595,0.9962592720985413,-0.005150339566171169,0.08725714683532715,-0.015386024489998817,0.9960536360740662,-0.00910775363445282,0.0893365666270256,-0.01724066399037838,0.9958106279373169,-0.0158118586987257,0.09267010539770126,-0.02029649168252945,0.9953644275665283,-0.025133421644568443,0.09697475284337997,-0.02440723404288292,0.9946700930595398,-0.035612933337688446,0.10133516788482666,-0.028801867738366127,0.9937974214553833,-0.046602148562669754,0.10526348650455475,-0.03303969278931618,0.9928022027015686,-0.05747134983539581,0.10830499976873398,-0.036647018045186996,0.9917782545089722,-0.06760530918836594,0.1100332960486412,-0.039121031761169434,0.990854024887085,-0.07639366388320923,0.11004332453012466,-0.03993989899754524,0.9901814460754395,-0.08321469277143478,0.10793156176805496,-0.03857696056365967,0.9899181127548218,-0.08576357364654541,0.09681026637554169,-0.03033331036567688,0.9911368489265442,-0.08334751427173615,0.07801364362239838,0.005348085425794125,0.9934477806091309,-0.07829036563634872,0.0566338412463665,0.013968676328659058,0.9952226877212524,-0.07335279136896133,0.03058026172220707,0.01943555474281311,0.9966476559638977,-0.068183034658432,0.0021323866676539183,0.023447811603546143,0.9973949790000916,-0.0628252848982811,-0.026951927691698074,0.02630756050348282,0.9973136186599731,-0.05737018957734108,-0.05492166429758072,0.028353514149785042,0.9964378476142883,-0.05193839967250824,-0.08004508167505264,0.02995973639190197,0.9949862360954285,-0.04666733741760254,-0.10061858594417572,0.031532809138298035,0.993329644203186,-0.04179583862423897,-0.11224813759326935,0.033187899738550186,0.9922459721565247,-0.04017673432826996,-0.11212663352489471,0.03903067111968994,0.9921139478683472,-0.042106546461582184,-0.11038785427808762,0.051803018897771835,0.9916440844535828,-0.044879503548145294,-0.1082974523305893,0.06592907756567001,0.9909141063690186,-0.04804111272096634,-0.10562274605035782,0.07978826016187668,0.9900352358818054,-0.05137848109006882,-0.10146927088499069,0.09076666086912155,0.9893561601638794,-0.05393930897116661,-0.09226826578378677,0.08977611362934113,0.990210771560669,-0.056040208786726,-0.08160420507192612,0.08534464240074158,0.9914215207099915,-0.057284142822027206,-0.06956344842910767,0.0783776044845581,0.9928425550460815,-0.056110844016075134,-0.05408136919140816,0.06573295593261719,0.9947894215583801,-0.05336673557758331,-0.03755909204483032,0.05196832865476608,0.9965142011642456,-0.04918196424841881,-0.020550711080431938,0.03783482313156128,0.997861385345459,-0.04377453401684761,-0.0036468286998569965,0.02406471036374569,0.9987449049949646,-0.03744806349277496,0.012527435086667538,0.01135973446071148,0.9991555213928223,-0.030581697821617126,0.027316521853208542,0.0003835076931864023,0.9991588592529297,-0.023611294105648994,0.04004143923521042,-0.008240918628871441,0.9988850355148315,-0.014431470073759556,0.051493387669324875,-0.015348976477980614,0.9984511137008667,-0.0018209560075774789,0.06271912902593613,-0.021859169006347656,0.9977901577949524,0.012411443516612053,0.07335537672042847,-0.027378499507904053,0.9968527555465698,0.02643851563334465,0.08297360688447952,-0.03161608427762985,0.9956991672515869,0.03842778876423836,0.09109591692686081,-0.03437208756804466,0.9945065975189209,0.03650696575641632,0.09066837280988693,-0.03276544436812401,0.9946722984313965,0.02928823046386242,0.08955485373735428,-0.028958801180124283,0.9951299428939819,0.019034625962376595,0.08813271671533585,-0.023873474448919296,0.9956406950950623,0.008015602827072144,0.08673315495252609,-0.018444545567035675,0.9960286021232605,-0.0014927898300811648,0.08560391515493393,-0.013628154993057251,0.9962349534034729],"type":"quaternion"},{"name":"mixamorigNeck.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999998211860657,0.9999999403953552,0.9999998807907104,0.9999999403953552,0.9999998807907104,1],"type":"vector"},{"name":"mixamorigHead.position","times":[0.03333333507180214,1.5333333015441895],"values":[-1.4901161193847656e-7,0.5444000363349915,0.05890008434653282,-2.9802322387695312e-8,0.5444000363349915,0.0589001439511776],"type":"vector"},{"name":"mixamorigHead.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.04057522863149643,0.20343996584415436,-0.04919806122779846,0.9770084023475647,-0.04315319284796715,0.20974202454090118,-0.05059085786342621,0.9754930734634399,-0.04596675559878349,0.21652139723300934,-0.05213533341884613,0.973800539970398,-0.0509541854262352,0.22758446633815765,-0.054777842015028,0.9708802103996277,-0.058257509022951126,0.24223245680332184,-0.05853734165430069,0.9666969180107117,-0.06702065467834473,0.2576131820678711,-0.06287145614624023,0.9618684649467468,-0.07701131701469421,0.27222660183906555,-0.06748255342245102,0.956769585609436,-0.08804170787334442,0.2846203148365021,-0.07199201732873917,0.951870322227478,-0.09996689110994339,0.2933867871761322,-0.07592733204364777,0.9477161765098572,-0.11267108470201492,0.29715630412101746,-0.07871610671281815,0.9448847770690918,-0.12603916227817535,0.2945772111415863,-0.07968524098396301,0.9439219236373901,-0.13615886867046356,0.27251482009887695,-0.07397396117448807,0.9495916366577148,-0.12539440393447876,0.2598108649253845,-0.08050183206796646,0.954093337059021,-0.11134546995162964,0.22191210091114044,-0.062380123883485794,0.9666777849197388,-0.11609958112239838,0.16669844090938568,-0.04522605985403061,0.9781038761138916,-0.12777915596961975,0.09759688377380371,-0.025810793042182922,0.9866514801979065,-0.14255870878696442,0.021897686645388603,-0.0041507757268846035,0.989535391330719,-0.1572388857603073,-0.05100911110639572,0.018367335200309753,0.9860713481903076,-0.1694486141204834,-0.11731301993131638,0.039949119091033936,0.9777161478996277,-0.1774953007698059,-0.17378436028957367,0.057999130338430405,0.9669180512428284,-0.1801057606935501,-0.2101057469844818,0.06554245203733444,0.9587083458900452,-0.18193978071212769,-0.22256959974765778,0.05425245687365532,0.9562516808509827,-0.1858188807964325,-0.23875172436237335,0.03625565022230148,0.9524465799331665,-0.1906650960445404,-0.2558583915233612,0.017247311770915985,0.947568416595459,-0.19648230075836182,-0.2721342146396637,-0.0016696930397301912,0.9419845342636108,-0.20377007126808167,-0.2844138741493225,-0.01906774565577507,0.9366017580032349,-0.21303990483283997,-0.2777373492717743,-0.028590844944119453,0.9363004565238953,-0.22219552099704742,-0.2644214630126953,-0.035861365497112274,0.9377763271331787,-0.2293633222579956,-0.2395271211862564,-0.0406220518052578,0.9425333142280579,-0.2310285121202469,-0.18059897422790527,-0.038748499006032944,0.9552530646324158,-0.2299816906452179,-0.10772888362407684,-0.03553658723831177,0.966560959815979,-0.2272728681564331,-0.03146212175488472,-0.030883578583598137,0.9728326797485352,-0.2237154096364975,0.03715541586279869,-0.024641357362270355,0.9736342430114746,-0.21957483887672424,0.08931227028369904,-0.0169665589928627,0.9713507890701294,-0.21329687535762787,0.13097728788852692,-0.01005949079990387,0.9681158065795898,-0.20411716401576996,0.16274325549602509,-0.005176895763725042,0.9653103351593018,-0.19225116074085236,0.1851119101047516,-0.0029339950997382402,0.9637242555618286,-0.17838743329048157,0.20019270479679108,-0.0031310736667364836,0.9633747935295105,-0.1628865748643875,0.20915183424949646,-0.005437863990664482,0.9642063975334167,-0.14617139101028442,0.21314243972301483,-0.009504741057753563,0.96597820520401,-0.12867404520511627,0.21334503591060638,-0.014964024536311626,0.9683506488800049,-0.11105494946241379,0.21152913570404053,-0.021361498162150383,0.9708068370819092,-0.09343137592077255,0.2093796581029892,-0.028087712824344635,0.9729552268981934,-0.07581057399511337,0.2071494460105896,-0.03504732623696327,0.9747377038002014,-0.05819397792220116,0.2050880491733551,-0.042127493768930435,0.9761033058166504,-0.04057525098323822,0.20344017446041107,-0.04919809475541115,0.9770083427429199],"type":"quaternion"},{"name":"mixamorigHead.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999999403953552,1,1.0000001192092896,1,1,0.9999999403953552],"type":"vector"},{"name":"mixamorigHeadTop_End.position","times":[0.03333333507180214,1.5333333015441895],"values":[-1.1920928955078125e-7,2.181598663330078,0.23610012233257294,-8.940696716308594e-8,2.1815989017486572,0.23610007762908936],"interpolation":2300,"type":"vector"},{"name":"mixamorigHeadTop_End.quaternion","times":[0.03333333507180214,1.5333333015441895],"values":[-3.725290076417309e-9,-4.540197551250458e-9,3.725290298461914e-9,1,8.847565347025466e-9,6.984921530062138e-10,1.862645371275562e-9,1],"interpolation":2300,"type":"quaternion"},{"name":"mixamorigHeadTop_End.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999999403953552,1,1,0.9999999403953552,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"mixamorigLeftShoulder.position","times":[0.03333333507180214,1.5333333015441895],"values":[0.5879998207092285,0.811498761177063,-0.010199797339737415,0.5879996418952942,0.8114990592002869,-0.01019963063299656],"type":"vector"},{"name":"mixamorigLeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.6426254510879517,0.32329800724983215,-0.5640764236450195,0.40537476539611816,0.6398106217384338,0.3322998583316803,-0.5633812546730042,0.4035106599330902,0.6362583041191101,0.3420790135860443,-0.5634132027626038,0.40090274810791016,0.6329272389411926,0.3513473868370056,-0.5631114840507507,0.3985770344734192,0.6308252215385437,0.358880877494812,-0.5614144802093506,0.3975900709629059,0.6309846043586731,0.36350366473197937,-0.5572391152381897,0.3990088403224945,0.6350196003913879,0.36269131302833557,-0.5504664778709412,0.4027304947376251,0.6398057341575623,0.3601965010166168,-0.5436596274375916,0.4066219925880432,0.6427844166755676,0.35841551423072815,-0.5395811796188354,0.408923864364624,0.6448128819465637,0.35664281249046326,-0.5373288989067078,0.4102439284324646,0.6432841420173645,0.3569032549858093,-0.5401893854141235,0.40865767002105713,0.6309871077537537,0.36351343989372253,-0.5576586723327637,0.39840957522392273,0.6170076727867126,0.3700438141822815,-0.5778840780258179,0.38525208830833435,0.60969477891922,0.3748641908168793,-0.5850216150283813,0.3814432621002197,0.6100271940231323,0.37801849842071533,-0.5778787732124329,0.3886193335056305,0.6130383610725403,0.37722858786582947,-0.5727183222770691,0.3922707438468933,0.5920144319534302,0.36346879601478577,-0.6327084302902222,0.34218311309814453,0.5624588131904602,0.3504369854927063,-0.6979842782020569,0.2713889181613922,0.5550520420074463,0.3398551344871521,-0.7227180600166321,0.23258201777935028,0.5671415328979492,0.325872004032135,-0.7240291237831116,0.2189515233039856,0.5884601473808289,0.30989891290664673,-0.7138105034828186,0.2194354087114334,0.6109933853149414,0.2948344647884369,-0.6988215446472168,0.2267337292432785,0.6274780631065369,0.28366026282310486,-0.6865885257720947,0.2332475483417511,0.6282437443733215,0.2828367352485657,-0.6860885620117188,0.2336570769548416,0.6211262941360474,0.28945571184158325,-0.6892734169960022,0.23520126938819885,0.6146034598350525,0.2963234484195709,-0.6912238597869873,0.23804306983947754,0.6120203137397766,0.301074743270874,-0.6899468302726746,0.24240152537822723,0.6133657097816467,0.3055461645126343,-0.683528482913971,0.2514217793941498,0.6174920797348022,0.3113221526145935,-0.6713781356811523,0.2665209174156189,0.6246364116668701,0.32128751277923584,-0.6475217342376709,0.29549843072891235,0.6359251737594604,0.32885676622390747,-0.6156939268112183,0.32920122146606445,0.6317846775054932,0.3526841104030609,-0.5792023539543152,0.37548214197158813,0.6038398742675781,0.3958750069141388,-0.5414599776268005,0.43067583441734314,0.5878096222877502,0.4128948152065277,-0.5337950587272644,0.4461621046066284,0.575094997882843,0.4231700599193573,-0.5413823127746582,0.4439573287963867,0.5677735209465027,0.4292917847633362,-0.5465088486671448,0.4412142336368561,0.5640329718589783,0.4337643086910248,-0.5434184670448303,0.4454343914985657,0.5628306865692139,0.43580496311187744,-0.5394182205200195,0.4498040974140167,0.5667831301689148,0.4317986071109772,-0.54017573595047,0.44779136776924133,0.5731585025787354,0.42551305890083313,-0.541351318359375,0.44425979256629944,0.5802387595176697,0.41880157589912415,-0.5429272055625916,0.4394978880882263,0.5891448259353638,0.4077245593070984,-0.5491470098495483,0.43024030327796936,0.599737823009491,0.3927813768386841,-0.5566155314445496,0.41978153586387634,0.6129745841026306,0.3720531463623047,-0.561637282371521,0.412798136472702,0.6281160712242126,0.34739306569099426,-0.563792884349823,0.40856578946113586,0.6426252722740173,0.32329824566841125,-0.5640764236450195,0.40537500381469727],"type":"quaternion"},{"name":"mixamorigLeftShoulder.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1.0000001192092896,1.000000238418579,1.0000001192092896,1.0000003576278687,1.000000238418579,1.0000001192092896],"type":"vector"},{"name":"mixamorigLeftArm.position","times":[0.03333333507180214,1.5333333015441895],"values":[-5.324812946128077e-8,1.3170000314712524,1.336787534000905e-7,-1.421788198285867e-7,1.3170000314712524,9.906158027206402e-8],"type":"vector"},{"name":"mixamorigLeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.19986096024513245,0.0379156656563282,0.12239284068346024,0.97141033411026,0.1777259260416031,-0.02423241175711155,0.14587056636810303,0.9729070663452148,0.1391112357378006,-0.10333605110645294,0.15988843142986298,0.9718052744865417,0.10094045847654343,-0.1824236959218979,0.1689460426568985,0.9633222818374634,0.066596120595932,-0.24860292673110962,0.16986818611621857,0.9512656927108765,0.03463068604469299,-0.2887497842311859,0.1590108871459961,0.9434722065925598,0.014198025688529015,-0.28657451272010803,0.07912629842758179,0.9546793103218079,0.0040735206566751,-0.27047136425971985,0.0019767454359680414,0.9627174139022827,-0.0026757963933050632,-0.24625752866268158,-0.055350445210933685,0.9676189422607422,-0.0012752091279253364,-0.21743299067020416,-0.11230126768350601,0.9695925116539001,0.007894261740148067,-0.18547847867012024,-0.16636553406715393,0.9684306383132935,0.023669037967920303,-0.14377933740615845,-0.21497797966003418,0.9656872153282166,0.046712394803762436,-0.10472183674573898,-0.2228068858385086,0.9680953025817871,0.0866057500243187,-0.07956749945878983,-0.19385185837745667,0.9739558100700378,0.14716081321239471,-0.06421728432178497,-0.12728498876094818,0.9787841439247131,0.17719964683055878,-0.051339488476514816,-0.06833205372095108,0.9804566502571106,0.16922615468502045,-0.04569536820054054,0.0005052859196439385,0.9845172762870789,0.15839914977550507,-0.04205859825015068,0.06519808620214462,0.9843221306800842,0.15687543153762817,-0.02509896457195282,0.08530240505933762,0.9836074709892273,0.15934938192367554,0.00651871832087636,0.0657062828540802,0.985011637210846,0.16878557205200195,0.04368593916296959,0.02716350182890892,0.9843094944953918,0.17929986119270325,0.07541760802268982,-0.01911153830587864,0.9807132482528687,0.18070240318775177,0.0952635332942009,-0.05932801961898804,0.9771139621734619,0.1686917543411255,0.09339191764593124,-0.06694433838129044,0.9789481163024902,0.15902069211006165,0.08115021884441376,-0.05577746033668518,0.9823521971702576,0.16419707238674164,0.055979762226343155,-0.04616696387529373,0.9837551712989807,0.17885605990886688,0.019937345758080482,-0.03238678351044655,0.9831399321556091,0.19317136704921722,0.004044141620397568,0.004778296686708927,0.9811450839042664,0.2111542671918869,0.005237944424152374,0.05702810361981392,0.9757736921310425,0.2412157952785492,0.0021364442072808743,0.09741397202014923,0.9655676484107971,0.2553785443305969,-0.012668974697589874,0.11097788065671921,0.9603672623634338,0.2607101500034332,-0.05922985076904297,0.11556670814752579,0.956643283367157,0.27863451838493347,-0.13508641719818115,0.10945205390453339,0.9445288777351379,0.24292175471782684,-0.18532350659370422,0.06180938705801964,0.9501703977584839,0.2010205090045929,-0.2031823843717575,0.03591795638203621,0.9576103687286377,0.17870977520942688,-0.207396999001503,0.026456156745553017,0.9614309072494507,0.16723540425300598,-0.22004373371601105,0.01923811063170433,0.9608553051948547,0.1570785790681839,-0.236729234457016,0.013989008031785488,0.9586917757987976,0.14665286242961884,-0.25390076637268066,0.012470368295907974,0.9559664726257324,0.14849761128425598,-0.26727887988090515,0.015617743134498596,0.9519802927970886,0.17291857302188873,-0.27433648705482483,0.025239460170269012,0.9456223249435425,0.20253902673721313,-0.25285229086875916,0.022983571514487267,0.9457882642745972,0.2246343046426773,-0.21381741762161255,0.016794858500361443,0.95054692029953,0.2334287315607071,-0.1295325756072998,0.025105388835072517,0.9633805155754089,0.23476052284240723,-0.024651983752846718,0.06638523191213608,0.9694703817367554,0.19986112415790558,0.037915732711553574,0.12239280343055725,0.9714102745056152],"type":"quaternion"},{"name":"mixamorigLeftArm.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999997615814209,0.9999997019767761,1,1,0.9999997615814209,1],"type":"vector"},{"name":"mixamorigLeftForeArm.position","times":[0.03333333507180214,1.5333333015441895],"values":[-5.722735618718389e-9,1.914500117301941,-3.6226157362762024e-7,1.0233289771122145e-7,1.9144997596740723,3.3990039582931786e-7],"type":"vector"},{"name":"mixamorigLeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.2183692902326584,-0.20941416919231415,0.6886574029922485,0.6589472889900208,0.1777089685201645,-0.21069954335689545,0.6101924180984497,0.7427586913108826,0.143377885222435,-0.23140187561511993,0.49262797832489014,0.8265674114227295,0.10859522223472595,-0.2530222535133362,0.36451104283332825,0.889560878276825,0.07297125458717346,-0.2646276652812958,0.23772841691970825,0.9317363500595093,0.03787989914417267,-0.2541674077510834,0.11806967109441757,0.9591785669326782,0.04446195811033249,-0.30247020721435547,0.097622811794281,0.9471033215522766,0.04313116893172264,-0.3645573556423187,0.061857670545578,0.9281224608421326,0.030217714607715607,-0.39701321721076965,0.00911081675440073,0.9172700643539429,0.029596596956253052,-0.40055084228515625,-0.014511467888951302,0.9156814217567444,0.04629559442400932,-0.39313775300979614,0.00005599111318588257,0.9183133840560913,0.12216154485940933,-0.3783698081970215,0.1497890055179596,0.9052492380142212,0.17630530893802643,-0.3552911579608917,0.3065316081047058,0.8652878403663635,0.19864600896835327,-0.3096162676811218,0.42813602089881897,0.8254557251930237,0.17754927277565002,-0.2467999905347824,0.5018928050994873,0.8097343444824219,0.1537485420703888,-0.1855117231607437,0.5984048843383789,0.764106273651123,0.11106604337692261,-0.12993472814559937,0.6491365432739258,0.741217315196991,0.0730290561914444,-0.09162873774766922,0.6672006845474243,0.7356046438217163,0.05053795874118805,-0.07532666623592377,0.6549283862113953,0.7502270340919495,0.0412299744784832,-0.07655245065689087,0.6180140972137451,0.7813439965248108,0.03780268132686615,-0.08743458241224289,0.5629631280899048,0.8209742307662964,0.03621726855635643,-0.10523806512355804,0.5188711285591125,0.8475765585899353,0.034234073013067245,-0.12434728443622589,0.5046586394309998,0.8536307215690613,0.0336071215569973,-0.1276128590106964,0.5053348541259766,0.8527732491493225,0.035931020975112915,-0.10402371734380722,0.5012749433517456,0.8582607507705688,0.04155672341585159,-0.07297298312187195,0.47963154315948486,0.8734424114227295,0.041041918098926544,-0.05004434660077095,0.4435051381587982,0.8939319849014282,0.03216258063912392,-0.05078085511922836,0.3996502459049225,0.9146947860717773,0.02479459159076214,-0.08942224085330963,0.36131155490875244,0.9278162121772766,0.030683210119605064,-0.12633752822875977,0.3638637661933899,0.9223342537879944,0.06055861711502075,-0.1628984957933426,0.43189993500709534,0.8850193023681641,0.09809329360723495,-0.16549775004386902,0.5210025906562805,0.8315915465354919,0.1776164025068283,-0.19390232861042023,0.599546492099762,0.7559089064598083,0.23429101705551147,-0.19787108898162842,0.6550315022468567,0.690571129322052,0.2479194849729538,-0.18608541786670685,0.6777594089508057,0.6667460799217224,0.25880295038223267,-0.17426589131355286,0.6704937219619751,0.6731201410293579,0.2769915461540222,-0.1620403677225113,0.651593804359436,0.6873456835746765,0.2949569821357727,-0.14745688438415527,0.6341854333877563,0.6993322968482971,0.31234848499298096,-0.12240272760391235,0.6253184080123901,0.7045798897743225,0.3492501676082611,-0.08608731627464294,0.6244333386421204,0.6933226585388184,0.3844901919364929,-0.04857669025659561,0.6267028450965881,0.6760556101799011,0.39418166875839233,-0.0013921295758336782,0.6527963280677795,0.6468970775604248,0.38181790709495544,0.03865057975053787,0.6852802634239197,0.6189606189727783,0.33380943536758423,0.014127504080533981,0.7139402627944946,0.6153544187545776,0.2653072476387024,-0.07278215140104294,0.7256260514259338,0.6306993365287781,0.21836936473846436,-0.20941410958766937,0.6886575222015381,0.658947229385376],"type":"quaternion"},{"name":"mixamorigLeftForeArm.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999998807907104,0.9999998807907104,0.9999998807907104,1,0.9999999403953552,0.9999998807907104],"type":"vector"},{"name":"mixamorigLeftHand.position","times":[0.03333333507180214,1.5333333015441895],"values":[1.4291320837855892e-7,1.5654995441436768,-2.848304632152576e-7,-3.0039632292755414e-7,1.5654995441436768,-2.9134972123756597e-7],"interpolation":2300,"type":"vector"},{"name":"mixamorigLeftHand.quaternion","times":[0.03333333507180214,1.5333333015441895],"values":[-1.2241800906309663e-9,2.1536843419767138e-9,-3.4924632075217232e-9,1,-9.313225746154785e-9,1.0477386735985306e-9,-6.984953726529852e-10,1],"interpolation":2300,"type":"quaternion"},{"name":"mixamorigLeftHand.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"mixamorigRightShoulder.position","times":[0.03333333507180214,1.5333333015441895],"values":[-0.5879999399185181,0.8114985823631287,-0.008699994534254074,-0.5879999995231628,0.8114984035491943,-0.008699748665094376],"type":"vector"},{"name":"mixamorigRightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.4637547433376312,-0.3899218440055847,0.7211704850196838,0.3358655571937561,0.48523449897766113,-0.39383628964424133,0.6889240145683289,0.367184042930603,0.5080944299697876,-0.39960724115371704,0.6541538834571838,0.39272981882095337,0.5294478535652161,-0.40928125381469727,0.6169649958610535,0.41415935754776,0.5464601516723633,-0.42428410053253174,0.577485978603363,0.433444619178772,0.5564429759979248,-0.4453794062137604,0.5357021689414978,0.452804297208786,0.5530344843864441,-0.47269725799560547,0.4923001527786255,0.477860689163208,0.5311571955680847,-0.5019348859786987,0.46946224570274353,0.4955187439918518,0.5018017292022705,-0.5251510143280029,0.4704917073249817,0.5010478496551514,0.4633614122867584,-0.5497886538505554,0.4856961667537689,0.49711963534355164,0.4343375563621521,-0.5619800090789795,0.5095763206481934,0.48565560579299927,0.4448537230491638,-0.5467888712882996,0.5426586866378784,0.4567808210849762,0.4648686349391937,-0.5205726027488708,0.5647042393684387,0.44046616554260254,0.47693729400634766,-0.46478626132011414,0.5912806987762451,0.45485347509384155,0.455478698015213,-0.3847571611404419,0.6249366402626038,0.503939688205719,0.4655059278011322,-0.32946208119392395,0.613601565361023,0.5461245179176331,0.5183842778205872,-0.35284027457237244,0.5776661038398743,0.5225735902786255,0.5773060917854309,-0.4024548828601837,0.5295088887214661,0.473675012588501,0.6261898875236511,-0.4071853458881378,0.45993009209632874,0.4801570177078247,0.6778420805931091,-0.35890552401542664,0.41905128955841064,0.4859146475791931,0.7096617817878723,-0.32000115513801575,0.3938208520412445,0.48875829577445984,0.7222291827201843,-0.3039613664150238,0.3756708800792694,0.49483734369277954,0.7260758876800537,-0.29748865962028503,0.36668145656585693,0.4998590648174286,0.7287310361862183,-0.2917599678039551,0.3581765294075012,0.5055065155029297,0.7313697934150696,-0.28675928711891174,0.35452333092689514,0.5071297883987427,0.7347262501716614,-0.28351470828056335,0.364035964012146,0.49726706743240356,0.7374374866485596,-0.28149324655532837,0.3774941563606262,0.4841959476470947,0.7377781271934509,-0.2809258699417114,0.3880326449871063,0.47559937834739685,0.7368908524513245,-0.28127622604370117,0.3986154794692993,0.4679543375968933,0.7354109287261963,-0.2820194661617279,0.4095100462436676,0.4603665769100189,0.7332245707511902,-0.2831518352031708,0.42153531312942505,0.4522331953048706,0.730169415473938,-0.2845781743526459,0.43594875931739807,0.44251182675361633,0.7268368601799011,-0.2861270308494568,0.4485606849193573,0.4343189001083374,0.7227977514266968,-0.2880484461784363,0.46134501695632935,0.42632418870925903,0.7175641655921936,-0.29060205817222595,0.4761498272418976,0.4170534014701843,0.711856484413147,-0.2932652533054352,0.4918632507324219,0.4066036343574524,0.7032716870307922,-0.2969962954521179,0.5113223791122437,0.3946537971496582,0.6928243637084961,-0.3015417456626892,0.5326389670372009,0.38126468658447266,0.6815944314002991,-0.3062041401863098,0.5534170269966125,0.36796435713768005,0.6708677411079407,-0.3102338910102844,0.5715271234512329,0.3564383089542389,0.6621050834655762,-0.31282296776771545,0.5850837230682373,0.3484761714935303,0.6357656717300415,-0.32433491945266724,0.610647439956665,0.3431016504764557,0.5999479293823242,-0.33960315585136414,0.6400636434555054,0.3391913175582886,0.5570399165153503,-0.35676971077919006,0.669996440410614,0.33693715929985046,0.5102763175964355,-0.37406206130981445,0.6976733207702637,0.33607667684555054,0.46375489234924316,-0.38992178440093994,0.7211704254150391,0.3358655273914337],"type":"quaternion"},{"name":"mixamorigRightShoulder.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1.000000238418579,1,1.0000001192092896,1.000000238418579,1.0000001192092896,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"mixamorigRightArm.position","times":[0.03333333507180214,1.5333333015441895],"values":[1.534274503001143e-7,1.3170007467269897,0.0000011920104725504643,6.770853389070908e-8,1.3169997930526733,-8.794634709374805e-7],"type":"vector"},{"name":"mixamorigRightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.24436306953430176,-0.02937716618180275,0.0107021015137434,0.969179630279541,0.22390170395374298,0.01075619738548994,0.007914239540696144,0.974520206451416,0.197734996676445,0.05187691003084183,-0.013242549262940884,0.9787923097610474,0.16037747263908386,0.07794753462076187,-0.03682539239525795,0.9832838773727417,0.12326447665691376,0.09221351891756058,-0.0494508296251297,0.9868420362472534,0.09075672924518585,0.1233963891863823,-0.060417693108320236,0.9863499999046326,0.08109935373067856,0.21055981516838074,-0.045451220124959946,0.973150372505188,0.06281992793083191,0.32070302963256836,-0.038992565125226974,0.9442895650863647,0.01936686411499977,0.42202499508857727,-0.042687080800533295,0.9053715467453003,-0.03162689879536629,0.49584081768989563,-0.03374246135354042,0.867181122303009,-0.06991787999868393,0.5514324307441711,-0.016966385766863823,0.8311112523078918,-0.11240940541028976,0.5731997489929199,0.007726824376732111,0.811631977558136,-0.14311738312244415,0.57448810338974,0.04473913833498955,0.804660975933075,-0.12554097175598145,0.6192308664321899,0.02086433582007885,0.7748273015022278,-0.10814107954502106,0.6734187006950378,-0.027884148061275482,0.7307771444320679,-0.04928058758378029,0.6962347626686096,-0.04214539751410484,0.7148792147636414,-0.009006258100271225,0.7328613996505737,-0.09500458836555481,0.6736521124839783,0.06609456986188889,0.7031991481781006,-0.1765689253807068,0.6855406165122986,0.06402144581079483,0.477004736661911,-0.3184623122215271,0.816669762134552,0.17364928126335144,0.4156309962272644,-0.4528292119503021,0.7694430351257324,0.3265809118747711,0.38360595703125,-0.4520961046218872,0.736070990562439,0.3780801296234131,0.3613450229167938,-0.40711510181427,0.7488273978233337,0.346338152885437,0.33567681908607483,-0.40660664439201355,0.775913655757904,0.28101643919944763,0.3025083541870117,-0.444263756275177,0.7950775027275085,0.2082746922969818,0.26850056648254395,-0.46280115842819214,0.818745493888855,0.1383490413427353,0.25560691952705383,-0.47023439407348633,0.8333092331886292,0.07981827855110168,0.24982339143753052,-0.4679814279079437,0.8439257740974426,0.05091215670108795,0.24679717421531677,-0.4573444724082947,0.852839469909668,0.037500783801078796,0.24442516267299652,-0.44393643736839294,0.8612610101699829,0.03272046893835068,0.23806384205818176,-0.43053337931632996,0.8699977397918701,0.03008105978369713,0.22486157715320587,-0.4145474433898926,0.8812960982322693,0.018250541761517525,0.1919274926185608,-0.3912332355976105,0.899870753288269,0.008070980198681355,0.15178164839744568,-0.367204874753952,0.9176371097564697,-0.0009961994364857674,0.10531911253929138,-0.34093809127807617,0.9341670870780945,-0.009042083285748959,0.053519152104854584,-0.3107762336730957,0.9489321112632751,-0.011298667639493942,-0.01531431544572115,-0.27506139874458313,0.9612382650375366,-0.018648946657776833,-0.043368346989154816,-0.23597678542137146,0.9706113338470459,-0.031034966930747032,-0.05444273725152016,-0.1939931958913803,0.9789991974830627,-0.041590847074985504,-0.05310598015785217,-0.151313915848732,0.9861815571784973,-0.04321940988302231,-0.04259636998176575,-0.11151553690433502,0.9919082522392273,-0.028470871970057487,-0.025022542104125023,-0.07846145331859589,0.9961963295936584,0.01468100119382143,-0.02195674180984497,-0.05386829376220703,0.9981986880302429,0.0678819790482521,-0.023961618542671204,-0.033211614936590195,0.996852457523346,0.12688720226287842,-0.027910513803362846,-0.01621156558394432,0.9913918972015381,0.18721385300159454,-0.030720219016075134,-0.0020651337690651417,0.9818365573883057,0.24436315894126892,-0.02937714196741581,0.010701988823711872,0.969179630279541],"type":"quaternion"},{"name":"mixamorigRightArm.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1,0.9999998807907104,1.0000001192092896,1,1],"type":"vector"},{"name":"mixamorigRightForeArm.position","times":[0.03333333507180214,1.5333333015441895],"values":[-2.9615944185934495e-7,1.9140008687973022,-7.160772952374828e-7,-9.736334050103324e-8,1.9139997959136963,5.326026553120755e-7],"type":"vector"},{"name":"mixamorigRightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.062320537865161896,0.03126848116517067,-0.602634608745575,0.7949654459953308,0.15451465547084808,0.13137607276439667,-0.6325432658195496,0.7474989295005798,0.19312472641468048,0.16307948529720306,-0.6615062355995178,0.7060577273368835,0.20908743143081665,0.17592796683311462,-0.6817246675491333,0.678662896156311,0.23866018652915955,0.20040373504161835,-0.69024258852005,0.6530274748802185,0.25691357254981995,0.21528108417987823,-0.6984952688217163,0.6322609186172485,0.24272561073303223,0.20190320909023285,-0.7163093686103821,0.6222702860832214,0.20892181992530823,0.17647171020507812,-0.7303454875946045,0.625943124294281,0.186950221657753,0.16179585456848145,-0.7365425825119019,0.629584550857544,0.19088637828826904,0.16400350630283356,-0.7392257452011108,0.6246684193611145,0.21020856499671936,0.18332652747631073,-0.7312352657318115,0.6224939823150635,0.23829606175422668,0.2346598207950592,-0.6970855593681335,0.6342093348503113,0.27271586656570435,0.298941969871521,-0.6571865081787109,0.6358974575996399,0.26594650745391846,0.324646532535553,-0.6316542029380798,0.6518359184265137,0.2433604896068573,0.33253422379493713,-0.6159704923629761,0.6713993549346924,0.24655333161354065,0.3675108551979065,-0.6117392778396606,0.6556845903396606,0.2027227282524109,0.4237479269504547,-0.5611600875854492,0.6814987063407898,0.16780823469161987,0.5058516263961792,-0.4619349539279938,0.7089221477508545,0.1866588592529297,0.7500200867652893,-0.23618420958518982,0.5889357924461365,0.11490271240472794,0.6844784617424011,-0.11803718656301498,0.7101787328720093,0.09434620290994644,0.561272919178009,-0.127581387758255,0.8122773766517639,0.12577694654464722,0.2861329913139343,-0.22511108219623566,0.9228397011756897,0.15117011964321136,0.021746521815657616,-0.2814384996891022,0.9473474025726318,0.1806858330965042,-0.19446879625320435,-0.28426045179367065,0.921265721321106,0.23082417249679565,-0.34780797362327576,-0.3203679025173187,0.8503612875938416,0.2876873314380646,-0.42571040987968445,-0.36794358491897583,0.7749994993209839,0.32627907395362854,-0.4656476378440857,-0.41167038679122925,0.7122091054916382,0.3153840899467468,-0.4906666874885559,-0.45264580845832825,0.674456000328064,0.27915170788764954,-0.5027837753295898,-0.48603692650794983,0.6580660939216614,0.22419320046901703,-0.5097493529319763,-0.5069230794906616,0.6579681634902954,0.15854130685329437,-0.5016716718673706,-0.5210214853286743,0.6721062660217285,0.09728020429611206,-0.45362088084220886,-0.5344905257225037,0.706459105014801,0.03225603327155113,-0.398059219121933,-0.5555147528648376,0.7293227910995483,-0.03993264585733414,-0.3437992334365845,-0.5824332237243652,0.7355127930641174,-0.10692870616912842,-0.28163233399391174,-0.6094605326652527,0.7333534955978394,-0.1792151778936386,-0.20646865665912628,-0.6346016526222229,0.7228647470474243,-0.1640087068080902,-0.12544424831867218,-0.6623907089233398,0.7201412916183472,-0.08981654793024063,-0.024505792185664177,-0.6926083564758301,0.7152804732322693,0.013946432620286942,0.07653845846652985,-0.7082292437553406,0.7016828060150146,0.11352018266916275,0.1605440229177475,-0.7038434147834778,0.6826003789901733,0.17604996263980865,0.21329958736896515,-0.690457284450531,0.668414831161499,0.17318861186504364,0.2051367163658142,-0.6765475869178772,0.6857171654701233,0.1503240466117859,0.17453649640083313,-0.6616254448890686,0.7135765552520752,0.11807462573051453,0.12897828221321106,-0.6441691517829895,0.7446268796920776,0.08606856316328049,0.07755521684885025,-0.6239388585090637,0.7728374600410461,0.0623205229640007,0.03126855939626694,-0.6026346683502197,0.794965386390686],"type":"quaternion"},{"name":"mixamorigRightForeArm.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999998211860657,1,1.0000001192092896,0.9999996423721313,0.9999998807907104],"type":"vector"},{"name":"mixamorigRightHand.position","times":[0.03333333507180214,1.5333333015441895],"values":[2.593515944226965e-7,1.5644996166229248,3.763335598705453e-8,4.977701451025496e-7,1.5644996166229248,1.5684264553783578e-7],"interpolation":2300,"type":"vector"},{"name":"mixamorigRightHand.quaternion","times":[0.03333333507180214,1.5333333015441895],"values":[7.4505766001209395e-9,-2.1420415663442327e-8,-7.450578820566989e-9,1,-3.725293851175593e-9,-1.955777406692505e-8,1.3241995348484722e-15,1],"interpolation":2300,"type":"quaternion"},{"name":"mixamorigRightHand.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1,1.0000001192092896,1,1,1],"interpolation":2300,"type":"vector"},{"name":"mixamorigLeftUpLeg.position","times":[0.03333333507180214,1.5333333015441895],"values":[0.8434999585151672,-0.41020074486732483,0.01090001966804266,0.8435001373291016,-0.41019973158836365,0.010899967513978481],"type":"vector"},{"name":"mixamorigLeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.36449480056762695,-0.4336153268814087,-0.801927924156189,0.18982353806495667,-0.36583468317985535,-0.4297460615634918,-0.8062821626663208,0.17717912793159485,-0.36644038558006287,-0.42840421199798584,-0.8099033236503601,0.16201210021972656,-0.3570808172225952,-0.4282241761684418,-0.8162283897399902,0.15128982067108154,-0.29533934593200684,-0.45075541734695435,-0.8251395225524902,0.16952571272850037,-0.25145071744918823,-0.45899516344070435,-0.8316948413848877,0.18541766703128815,-0.22272509336471558,-0.45415109395980835,-0.8351940512657166,0.21585014462471008,-0.21830546855926514,-0.4370616674423218,-0.8364796042442322,0.24823716282844543,-0.26157015562057495,-0.4138723313808441,-0.8231340646743774,0.28764742612838745,-0.289873331785202,-0.39646488428115845,-0.8206269145011902,0.29216527938842773,-0.29262834787368774,-0.38875171542167664,-0.8341103196144104,0.25980907678604126,-0.32614216208457947,-0.42053917050361633,-0.8127490878105164,0.23710137605667114,-0.34450626373291016,-0.46191951632499695,-0.7840168476104736,0.23078879714012146,-0.3306749761104584,-0.5018046498298645,-0.767606258392334,0.2227708101272583,-0.27991220355033875,-0.5462700724601746,-0.764636218547821,0.1963919848203659,-0.2167096585035324,-0.5845562219619751,-0.7661755681037903,0.1559038758277893,-0.16510437428951263,-0.6103394031524658,-0.7680805921554565,0.10138373076915741,-0.12287096679210663,-0.627286434173584,-0.7676877975463867,0.04549695551395416,-0.0928565189242363,-0.6439129114151001,-0.7594353556632996,0.0034428145736455917,0.07258780300617218,0.6610771417617798,0.7464385032653809,0.0231879074126482,0.05665193125605583,0.6784785985946655,0.7313702702522278,0.039432425051927567,0.04225017502903938,0.6961670517921448,0.7151572108268738,0.046005312353372574,0.030465232208371162,0.71436607837677,0.6976454257965088,0.045209091156721115,0.023214727640151978,0.7320153713226318,0.6795812845230103,0.042236074805259705,0.020373119041323662,0.748603105545044,0.6616281867027283,0.03777003288269043,0.020401792600750923,0.7628618478775024,0.6455361247062683,0.030144795775413513,0.025012677535414696,0.7695788741111755,0.637963593006134,0.011193607933819294,-0.03288056701421738,-0.7736310958862305,-0.6326853632926941,0.011093081906437874,-0.042714543640613556,-0.7750832438468933,-0.6293526887893677,0.036560263484716415,-0.05335837975144386,-0.7705439925193787,-0.6313393712043762,0.06946554034948349,-0.06490245461463928,-0.7628734707832336,-0.6347019672393799,0.10471473634243011,-0.08083297312259674,-0.7435805201530457,-0.6458473205566406,0.1530858725309372,-0.09503703564405441,-0.7080129384994507,-0.6625246405601501,0.2252705842256546,-0.11097703874111176,-0.6567779779434204,-0.6861187815666199,0.29251986742019653,-0.1380828619003296,-0.5964305996894836,-0.7197023630142212,0.3274633586406708,-0.1708652824163437,-0.5563808679580688,-0.7396373152732849,0.3379082977771759,-0.20670709013938904,-0.5311519503593445,-0.751349925994873,0.33260035514831543,-0.2199830561876297,-0.5002244114875793,-0.772851288318634,0.32262054085731506,-0.2316603809595108,-0.46796655654907227,-0.7942495346069336,0.31065818667411804,-0.21910740435123444,-0.43658480048179626,-0.8151296973228455,0.3113667666912079,-0.2597728967666626,-0.4328519105911255,-0.8141171336174011,0.287002831697464,-0.3163257837295532,-0.4566861689090729,-0.7931267023086548,0.2496514767408371,-0.34302806854248047,-0.4641447067260742,-0.7864183783531189,0.2201082408428192,-0.3656747341156006,-0.4587414562702179,-0.7868044972419739,0.19177350401878357,-0.37242215871810913,-0.44946715235710144,-0.7906858921051025,0.18465375900268555,-0.36449453234672546,-0.43361493945121765,-0.8019282817840576,0.18982329964637756],"type":"quaternion"},{"name":"mixamorigLeftUpLeg.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999995827674866,1.0000003576278687,1,0.9999996423721313,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"mixamorigLeftLeg.position","times":[0.03333333507180214,1.5333333015441895],"values":[5.640850986310397e-8,3.1736011505126953,-2.2253493625612464e-7,5.6456720187725296e-8,3.1736011505126953,-2.7228918497712584e-7],"type":"vector"},{"name":"mixamorigLeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.4032476544380188,0.13322244584560394,-0.009848573245108128,0.9052878618240356,-0.3919544816017151,0.12404356896877289,-0.00527639826759696,0.9115684628486633,-0.38909944891929626,0.11470842361450195,-0.0008311891579069197,0.9140256643295288,-0.3930509686470032,0.09904208034276962,0.0033146122004836798,0.9141611456871033,-0.4367709755897522,0.06624859571456909,0.004774020053446293,0.8971173167228699,-0.4605824053287506,0.05850253999233246,-0.004440813325345516,0.8856757879257202,-0.47615736722946167,0.06414717435836792,-0.027689477428793907,0.8765800595283508,-0.47364863753318787,0.069516122341156,-0.06432776153087616,0.8756063580513,-0.45741087198257446,0.05928172916173935,-0.10308769345283508,0.8812683820724487,-0.4122975766658783,0.007847565226256847,-0.0911196917295456,0.9064471125602722,-0.3292791545391083,-0.06985116004943848,-0.041193198412656784,0.9407439827919006,-0.34140095114707947,-0.027263812720775604,-0.03469301760196686,0.938881516456604,-0.3941095471382141,0.04194124788045883,-0.05610891431570053,0.9163898825645447,-0.4391864836215973,0.09270418435335159,-0.07303663343191147,0.89061039686203,-0.4896848201751709,0.13055606186389923,-0.08938970416784286,0.8574225306510925,-0.5380116105079651,0.15431427955627441,-0.10236749798059464,0.8223451375961304,-0.5738390684127808,0.16460731625556946,-0.10786315053701401,0.7949708700180054,-0.6011037826538086,0.17102734744548798,-0.11124677956104279,0.7726888656616211,-0.6184155941009521,0.17980171740055084,-0.11573418974876404,0.7562004327774048,-0.6277612447738647,0.191231369972229,-0.1220380961894989,0.7446161508560181,-0.6313747763633728,0.20090733468532562,-0.12773770093917847,0.7380279302597046,-0.6305617690086365,0.205674946308136,-0.13050855696201324,0.7369241714477539,-0.6264904737472534,0.20844803750514984,-0.1328388750553131,0.7391975522041321,-0.6208453178405762,0.21257787942886353,-0.13790875673294067,0.7418510317802429,-0.6141680479049683,0.21767418086528778,-0.1451406478881836,0.7445467114448547,-0.6075658202171326,0.22109702229499817,-0.15239833295345306,0.7474989295005798,-0.6038156747817993,0.2172989696264267,-0.15788871049880981,0.750505805015564,-0.6005887985229492,0.21236228942871094,-0.1642560362815857,0.7531369924545288,-0.5980979800224304,0.20546327531337738,-0.17054782807826996,0.7556303143501282,-0.5988547801971436,0.191142737865448,-0.17252886295318604,0.7583345174789429,-0.6016517877578735,0.1728934347629547,-0.1720115840435028,0.7606148719787598,-0.6118846535682678,0.14271605014801025,-0.1636795997619629,0.7605513334274292,-0.6272580027580261,0.10885360091924667,-0.15150758624076843,0.7561374306678772,-0.6357647180557251,0.07482968270778656,-0.13355772197246552,0.7565487623214722,-0.6163219213485718,0.05246642231941223,-0.1162521168589592,0.7770971655845642,-0.5764017701148987,0.040051739662885666,-0.10220091044902802,0.809760332107544,-0.5235744118690491,0.03804637864232063,-0.0925893560051918,0.8460789322853088,-0.4585801959037781,0.017402973026037216,-0.06789609044790268,0.8858845829963684,-0.3839724659919739,-0.004742167890071869,-0.047526683658361435,0.9221083521842957,-0.30767667293548584,-0.03545330464839935,-0.03015911392867565,0.9503518342971802,-0.30773240327835083,0.010739081539213657,-0.031628865748643875,0.9508864879608154,-0.39010024070739746,0.09597143530845642,-0.057723116129636765,0.9139361381530762,-0.4314686954021454,0.12792640924453735,-0.05526450648903847,0.8912999033927917,-0.441270649433136,0.14023348689079285,-0.027107834815979004,0.8859345316886902,-0.43498334288597107,0.14333096146583557,-0.017451543360948563,0.8887863755226135,-0.4032473862171173,0.1332223266363144,-0.009848490357398987,0.90528804063797],"type":"quaternion"},{"name":"mixamorigLeftLeg.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999999403953552,1.0000001192092896,1,0.9999998807907104,0.9999998807907104],"type":"vector"},{"name":"mixamorigLeftFoot.position","times":[0.03333333507180214,1.5333333015441895],"values":[-1.5920299745175726e-7,3.1322011947631836,-5.6470668141628266e-8,7.837611093464147e-8,3.1322009563446045,-2.507738017243355e-8],"type":"vector"},{"name":"mixamorigLeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.4811789095401764,-0.09651048481464386,0.006091438699513674,0.8712723255157471,0.4740031361579895,-0.09740609675645828,0.003762422362342477,0.8751108050346375,0.47346967458724976,-0.09742375463247299,0.003706305054947734,0.875397801399231,0.4724366366863251,-0.09749159216880798,-0.0005895377253182232,0.8759558796882629,0.4159814715385437,-0.0948200523853302,-0.01595723070204258,0.9042753577232361,0.3946633040904999,-0.10400736331939697,-0.03218473494052887,0.9123527407646179,0.4105795621871948,-0.12422160804271698,-0.057737965136766434,0.9014764428138733,0.4434755742549896,-0.12433240562677383,-0.06244470924139023,0.8854216933250427,0.46028009057044983,-0.0788792073726654,0.022859107702970505,0.8839670419692993,0.459823340177536,-0.06952660530805588,0.040467940270900726,0.8843590617179871,0.41410550475120544,-0.08815877139568329,0.0029558816459029913,0.9059447646141052,0.4123287498950958,-0.09975402802228928,-0.014671260491013527,0.9054385423660278,0.4494483470916748,-0.08714388310909271,0.014300638809800148,0.88893061876297,0.4814537465572357,-0.07399741560220718,0.04087221994996071,0.8723853230476379,0.5122186541557312,-0.059026289731264114,0.06705383956432343,0.8541964888572693,0.5371451377868652,-0.04387308284640312,0.08872706443071365,0.8376621007919312,0.5526823401451111,-0.03225182369351387,0.10106594115495682,0.8266122341156006,0.5638170838356018,-0.022416360676288605,0.1088792085647583,0.8183845281600952,0.5720742344856262,-0.014725368469953537,0.11489380896091461,0.8119813203811646,0.5780093669891357,-0.008951343595981598,0.12049726396799088,0.807034969329834,0.5822583436965942,-0.006127013359218836,0.12345434725284576,0.8035525679588318,0.5854426026344299,-0.007222865242511034,0.12197060883045197,0.8014536499977112,0.5875877737998962,-0.010222325101494789,0.1189088299870491,0.800310492515564,0.5883265733718872,-0.012231945991516113,0.11765158176422119,0.7999252080917358,0.5877498388290405,-0.012981722131371498,0.11800413578748703,0.8002853989601135,0.5863849520683289,-0.013220264576375484,0.11864137649536133,0.8011879920959473,0.5843331813812256,-0.013830144889652729,0.11950614303350449,0.802547037601471,0.581634521484375,-0.013821356929838657,0.1213759109377861,0.8042252063751221,0.5789580345153809,-0.013889417983591557,0.12342334538698196,0.8058420419692993,0.5782063603401184,-0.015904894098639488,0.12313583493232727,0.806388258934021,0.5791857242584229,-0.019499704241752625,0.12083856016397476,0.8059539198875427,0.5853108763694763,-0.028191817924380302,0.11185785382986069,0.8025610446929932,0.596634566783905,-0.043611202389001846,0.09311164170503616,0.7958992123603821,0.6047787070274353,-0.06308672577142715,0.06687449663877487,0.7910692691802979,0.5946723818778992,-0.07798837870359421,0.044080186635255814,0.7989615201950073,0.5650931000709534,-0.08645056933164597,0.02794126607477665,0.8200093507766724,0.5237432718276978,-0.08826661109924316,0.02012731321156025,0.8470518589019775,0.4755975902080536,-0.09370093792676926,0.0006209441926330328,0.8746580481529236,0.42329826951026917,-0.08835257589817047,0.0023776039015501738,0.9016688466072083,0.32191696763038635,-0.08974587917327881,-0.008387901820242405,0.9424673914909363,0.3270741105079651,-0.09340513497591019,-0.02030211128294468,0.9401520490646362,0.41798099875450134,-0.0914367213845253,-0.01208016462624073,0.9037617444992065,0.4672898054122925,-0.08184244483709335,0.011328615248203278,0.8802350163459778,0.5008009076118469,-0.08746200799942017,0.0173900555819273,0.8609567284584045,0.5037546753883362,-0.09044735878705978,0.018263300880789757,0.8589044809341431,0.48117896914482117,-0.09651050716638565,0.0060914321802556515,0.8712723255157471],"type":"quaternion"},{"name":"mixamorigLeftFoot.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1,1,1,1,1],"interpolation":2300,"type":"vector"},{"name":"mixamorigLeftToeBase.position","times":[0.03333333507180214,1.5333333015441895],"values":[2.757829165034309e-8,1.5069996118545532,-5.072899256219898e-8,-7.351201869454371e-9,1.507000207901001,1.5046485657421727e-7],"type":"vector"},{"name":"mixamorigLeftToeBase.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.3016718327999115,0.008150625973939896,-0.02045878767967224,0.9531574845314026,0.30167195200920105,0.00815036240965128,-0.020459024235606194,0.9531574845314026,0.3016720414161682,0.008150091394782066,-0.020458906888961792,0.953157365322113,0.3111622929573059,0.008535725995898247,-0.01835356093943119,0.950141191482544,0.37853676080703735,-0.011023866944015026,-0.011484594084322453,0.9254493713378906,0.33819153904914856,-0.03094400465488434,-0.01035254541784525,0.9405114650726318,0.30129870772361755,0.009382757358253002,0.0027601136825978756,0.9534796476364136,0.2986583113670349,-0.0026197992265224457,0.003907161299139261,0.9543485641479492,0.35793882608413696,-0.018475975841283798,0.004727255553007126,0.9335502982139587,0.3563709855079651,-0.04288120195269585,0.01505268644541502,0.9332386255264282,0.3022104501724243,-0.07959011942148209,0.03138888627290726,0.9493939876556396,0.25316017866134644,-0.08797667175531387,0.033747598528862,0.9628245830535889,0.25315701961517334,-0.08797649294137955,0.03374723717570305,0.9628254175186157,0.2531491816043854,-0.08797509968280792,0.033746909350156784,0.9628276228904724,0.2531393766403198,-0.08797328919172287,0.03374667093157768,0.9628303647041321,0.2531273663043976,-0.08797094970941544,0.03374624624848366,0.962833821773529,0.2531141936779022,-0.08796827495098114,0.03374604135751724,0.9628374576568604,0.2530996799468994,-0.08796534687280655,0.033745765686035156,0.9628415703773499,0.2530822455883026,-0.08796162158250809,0.033745426684617996,0.9628465175628662,0.25305885076522827,-0.08795630186796188,0.033744633197784424,0.962853193283081,0.25303465127944946,-0.08795084804296494,0.03374375402927399,0.9628600478172302,0.2530166208744049,-0.08794685453176498,0.03374350443482399,0.9628651738166809,0.2530040442943573,-0.08794452250003815,0.033743683248758316,0.9628686904907227,0.25299200415611267,-0.08794233947992325,0.033743955194950104,0.9628720283508301,0.2529808282852173,-0.0879402905702591,0.03374418243765831,0.9628751277923584,0.2529705762863159,-0.08793864399194717,0.033744532614946365,0.9628779888153076,0.2529616057872772,-0.08793705701828003,0.03374466300010681,0.9628804922103882,0.25295430421829224,-0.08793593943119049,0.03374481573700905,0.9628824591636658,0.2529484033584595,-0.08793530613183975,0.03374494984745979,0.9628840684890747,0.25294432044029236,-0.08793491125106812,0.033744972199201584,0.9628852009773254,0.2529423236846924,-0.08793521672487259,0.03374498337507248,0.9628856778144836,0.25294268131256104,-0.0879359319806099,0.03374489024281502,0.9628855586051941,0.25294554233551025,-0.08793723583221436,0.033744655549526215,0.9628846645355225,0.2529507875442505,-0.08793913573026657,0.0337442010641098,0.9628831744194031,0.2529590129852295,-0.08794180303812027,0.03374370560050011,0.9628807902336121,0.2529776990413666,-0.08794710785150528,0.033743828535079956,0.9628753662109375,0.25301188230514526,-0.0879564955830574,0.033744797110557556,0.9628654718399048,0.2530515193939209,-0.08796725422143936,0.0337459035217762,0.9628540277481079,0.26282456517219543,-0.09117063134908676,0.03398460894823074,0.95992511510849,0.3068426549434662,-0.10485635697841644,0.03468254208564758,0.9453305602073669,0.315679669380188,-0.07823395729064941,0.023323141038417816,0.9453474879264832,0.3195244371891022,-0.024319205433130264,0.004951791372150183,0.9472529888153076,0.32673215866088867,0.0021743604447692633,-0.007589948363602161,0.9450839757919312,0.3045212924480438,0.007617767434567213,-0.018947234377264977,0.9522866010665894,0.30167272686958313,0.008150553330779076,-0.020458856597542763,0.9531571865081787,0.30167266726493835,0.00815048161894083,-0.020458878949284554,0.9531571865081787],"type":"quaternion"},{"name":"mixamorigLeftToeBase.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999997019767761,0.9999999403953552,1,0.9999998211860657,0.9999998807907104],"type":"vector"},{"name":"mixamorigLeftToe_End.position","times":[0.03333333507180214,1.5333333015441895],"values":[-3.4458938813486384e-8,0.654699444770813,3.352762334429826e-8,-8.102506399154663e-8,0.6547000408172607,4.09781861776537e-8],"interpolation":2300,"type":"vector"},{"name":"mixamorigLeftToe_End.quaternion","times":[0.03333333507180214,1.5333333015441895],"values":[-1.506123226135969e-8,1.0710209608078003e-8,3.3915057429112494e-9,1,-1.4129909686744213e-8,1.3969838619232178e-8,1.6452748052842026e-9,1],"interpolation":2300,"type":"quaternion"},{"name":"mixamorigLeftToe_End.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,1,0.9999999403953552,1.0000001192092896,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"mixamorigRightUpLeg.position","times":[0.03333333507180214,1.5333333015441895],"values":[-0.8434998393058777,-0.41020047664642334,0.002299943473190069,-0.8434998989105225,-0.41019952297210693,0.002299883868545294],"type":"vector"},{"name":"mixamorigRightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.24353067576885223,0.2213505655527115,0.9115588665008545,0.24648979306221008,-0.23790021240711212,0.2407965511083603,0.905849277973175,0.25467145442962646,-0.2347278892993927,0.2559893727302551,0.8991315960884094,0.26633572578430176,-0.23283697664737701,0.26882174611091614,0.8918587565422058,0.27948126196861267,-0.23252058029174805,0.27920353412628174,0.8842135071754456,0.2935064435005188,-0.2333049178123474,0.2868897318840027,0.8766742944717407,0.3077423572540283,-0.23578369617462158,0.2889934778213501,0.8699591159820557,0.32258328795433044,-0.23681508004665375,0.28862088918685913,0.8645098209381104,0.3365105986595154,-0.2368779331445694,0.284927099943161,0.8623520135879517,0.3450425863265991,-0.2298203408718109,0.2860824465751648,0.8587947487831116,0.35750675201416016,-0.2196253389120102,0.2680074870586395,0.8614009022712708,0.3713827133178711,-0.20151877403259277,0.23491480946540833,0.8743848204612732,0.37370631098747253,-0.184933602809906,0.18287105858325958,0.8973394632339478,0.35656657814979553,-0.1840275526046753,0.12986421585083008,0.9169871807098389,0.3292471468448639,-0.19464565813541412,0.046849239617586136,0.931265115737915,0.30440691113471985,-0.19569532573223114,-0.038975492119789124,0.9404521584510803,0.2751980721950531,-0.1958550661802292,-0.08887819200754166,0.9419928789138794,0.25766444206237793,-0.19624017179012299,-0.13229243457317352,0.9422119855880737,0.2371184080839157,-0.20154063403606415,-0.16057373583316803,0.9445796608924866,0.20338812470436096,-0.20943374931812286,-0.1712210327386856,0.9484788775444031,0.1649506390094757,-0.2167491912841797,-0.16227388381958008,0.95331871509552,0.13368049263954163,-0.230504110455513,-0.14606520533561707,0.9552799463272095,0.1138993501663208,-0.2403806895017624,-0.11145496368408203,0.9588706493377686,0.10179396718740463,-0.24924336373806,-0.07352461665868759,0.9613152146339417,0.09135039150714874,-0.2575635313987732,-0.03892406448721886,0.9620828032493591,0.08088701218366623,-0.2640400230884552,-0.01287743542343378,0.9617813229560852,0.07136991620063782,-0.25652840733528137,0.00045026891166344285,0.9637113213539124,0.07384728640317917,-0.23791241645812988,0.012145143002271652,0.9681158065795898,0.07747220247983932,-0.2157585620880127,0.02306399866938591,0.9727084636688232,0.08218604326248169,-0.18619967997074127,0.03666818514466286,0.9770002365112305,0.0972404032945633,-0.14554958045482635,0.048340894281864166,0.9817013740539551,0.11287567019462585,-0.08102965354919434,0.05967475473880768,0.9852953553199768,0.1380804032087326,-0.007566547952592373,0.06795728951692581,0.9856374859809875,0.1544131487607956,0.03376048430800438,0.09596401453018188,0.9790366888046265,0.17646092176437378,0.02755303494632244,0.12264781445264816,0.9712461829185486,0.20218585431575775,-0.00914869923144579,0.14991171658039093,0.9628890156745911,0.22424888610839844,-0.0887138620018959,0.14349126815795898,0.9530510902404785,0.25146323442459106,-0.16063931584358215,0.12923623621463776,0.9397076368331909,0.2728416621685028,-0.21727624535560608,0.10601423680782318,0.9287256002426147,0.2811063528060913,-0.2619086503982544,0.08848875761032104,0.920585036277771,0.2758564352989197,-0.2762921452522278,0.12953078746795654,0.9121038317680359,0.2737717926502228,-0.27073681354522705,0.1637110859155655,0.9074094295501709,0.27660152316093445,-0.2639889419078827,0.1844833940267563,0.9067009687423706,0.2723400592803955,-0.2569475471973419,0.1987103968858719,0.9079785346984863,0.2647020220756531,-0.24987970292568207,0.21009501814842224,0.9100086092948914,0.25554758310317993,-0.24353067576885223,0.2213502675294876,0.911558985710144,0.2464895397424698],"type":"quaternion"},{"name":"mixamorigRightUpLeg.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999998807907104,0.9999997615814209,1.000000238418579,1,1.0000001192092896,1.000000238418579],"type":"vector"},{"name":"mixamorigRightLeg.position","times":[0.03333333507180214,1.5333333015441895],"values":[7.336937102309093e-8,3.173100471496582,1.0194865751600446e-7,-1.6459185303574486e-7,3.1731011867523193,-3.146242590901238e-7],"type":"vector"},{"name":"mixamorigRightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[-0.3734981417655945,-0.005870241671800613,-0.04710831865668297,0.9264153838157654,-0.383802592754364,-0.006429423578083515,-0.048527177423238754,0.922116756439209,-0.3857807517051697,-0.006370968651026487,-0.04961032792925835,0.9212336540222168,-0.3826500475406647,-0.006926597561687231,-0.050024278461933136,0.9225121140480042,-0.3748985826969147,-0.009469756856560707,-0.04883858934044838,0.9257301092147827,-0.3630025386810303,-0.01667095720767975,-0.044767219573259354,0.9305628538131714,-0.34374937415122986,-0.032896142452955246,-0.03600693866610527,0.9377940893173218,-0.3133738040924072,-0.04121505096554756,-0.030660154297947884,0.9482395052909851,-0.2784610986709595,-0.05342612415552139,-0.02421914041042328,0.958654522895813,-0.2804531157016754,-0.07485354691743851,-0.01480876561254263,0.9568300247192383,-0.276962548494339,-0.05656030774116516,-0.010636369697749615,0.9591556787490845,-0.27640393376350403,0.02117668092250824,-0.023049378767609596,0.9605317115783691,-0.2653432786464691,0.11341746151447296,-0.04621661454439163,0.9563437700271606,-0.260824978351593,0.1870267689228058,-0.06349186599254608,0.9449657201766968,-0.2489878088235855,0.23764483630657196,-0.07093090564012527,0.9362151622772217,-0.25099995732307434,0.21907004714012146,-0.06276838481426239,0.9407802820205688,-0.2884558141231537,0.19576755166053772,-0.06142282113432884,0.9352515935897827,-0.319132924079895,0.17703872919082642,-0.057024035602808,0.9292791485786438,-0.32549774646759033,0.1571229249238968,-0.0485554039478302,0.9311316013336182,-0.31610625982284546,0.133846253156662,-0.038211625069379807,0.938457190990448,-0.310868501663208,0.11555763334035873,-0.031306494027376175,0.9428823590278625,-0.2997155785560608,0.13137055933475494,-0.03579123318195343,0.9442623257637024,-0.30797767639160156,0.1398455798625946,-0.03955334052443504,0.9402279257774353,-0.31828638911247253,0.14541597664356232,-0.04220861196517944,0.935823917388916,-0.32279396057128906,0.1475657820701599,-0.04246098920702934,0.9339301586151123,-0.31963396072387695,0.14528605341911316,-0.03942432254552841,0.9355062246322632,-0.3208043575286865,0.13127124309539795,-0.026444977149367332,0.9376316666603088,-0.3242673873901367,0.12245064973831177,-0.01365121640264988,0.937907338142395,-0.3288212716579437,0.11060266196727753,-0.003445905866101384,0.9378868937492371,-0.34516483545303345,0.09160804003477097,0.009145956486463547,0.9340158104896545,-0.35839101672172546,0.08091282099485397,0.01857338286936283,0.9298731684684753,-0.3787569999694824,0.05908798426389694,0.027014251798391342,0.9232128858566284,-0.39818504452705383,0.03888031840324402,0.031724728643894196,0.9159315228462219,-0.4342697560787201,0.031239820644259453,0.023925861343741417,0.8999230265617371,-0.4591684341430664,0.04805490002036095,-0.00251904409378767,0.8870449662208557,-0.4587338864803314,0.08255143463611603,-0.03632615879178047,0.8839846849441528,-0.3977119028568268,0.11870810389518738,-0.05723685026168823,0.9079964756965637,-0.32357749342918396,0.15290485322475433,-0.0607079342007637,0.9317898154258728,-0.23676441609859467,0.14949706196784973,-0.04604531079530716,0.9588915705680847,-0.156109020113945,0.09113512933254242,-0.032285068184137344,0.9829964637756348,-0.2002226561307907,0.03212560713291168,-0.03790175914764404,0.9784898161888123,-0.2619069814682007,0.0072882394306361675,-0.02840338833630085,0.9646475315093994,-0.3007577359676361,-0.0021001147106289864,-0.02872977964580059,0.9532654285430908,-0.3289297819137573,-0.0047066183760762215,-0.03415440022945404,0.9437248110771179,-0.3519788086414337,-0.004705735482275486,-0.0415789932012558,0.9350721836090088,-0.37349745631217957,-0.0058698877692222595,-0.047108180820941925,0.9264156818389893],"type":"quaternion"},{"name":"mixamorigRightLeg.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999998211860657,0.9999998807907104,0.9999999403953552,1,0.9999999403953552,1],"type":"vector"},{"name":"mixamorigRightFoot.position","times":[0.03333333507180214,1.5333333015441895],"values":[-1.7721662004532845e-7,3.1326987743377686,9.694657165937315e-8,-1.1870790217471949e-7,3.1326987743377686,7.259859557962045e-8],"type":"vector"},{"name":"mixamorigRightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.5676805377006531,0.1938842535018921,0.15676967799663544,0.784583330154419,0.5682913064956665,0.192844420671463,0.157014861702919,0.7843484282493591,0.563197910785675,0.19226734340190887,0.16127672791481018,0.7872936129570007,0.554608941078186,0.19191168248653412,0.16706378757953644,0.7922553420066833,0.5454071760177612,0.1906331181526184,0.17062470316886902,0.7981712222099304,0.5347505211830139,0.189880833029747,0.1734398454427719,0.8049259781837463,0.5222902297973633,0.19105137884616852,0.17660917341709137,0.8121092915534973,0.5049103498458862,0.1969253122806549,0.1838461011648178,0.8200528025627136,0.4853152930736542,0.20073027908802032,0.18423707783222198,0.8308027386665344,0.5196189284324646,0.1753063201904297,0.12946201860904694,0.8261376619338989,0.531777024269104,0.15147076547145844,0.09203323721885681,0.8281302452087402,0.503948986530304,0.1265868842601776,0.07066550850868225,0.8514796495437622,0.46452122926712036,0.1060519888997078,0.04102855175733566,0.8782309889793396,0.40405386686325073,0.07077401876449585,-0.026306912302970886,0.9116137027740479,0.3293502628803253,0.02156464010477066,-0.08385271579027176,0.9402298331260681,0.3092648386955261,0.018642164766788483,-0.0896591767668724,0.9465563297271729,0.36337050795555115,0.047861844301223755,-0.057624559849500656,0.9286283254623413,0.4442627727985382,0.07970267534255981,-0.020448870956897736,0.8921098113059998,0.5221232771873474,0.10208156704902649,0.005133750848472118,0.8467232584953308,0.5784724950790405,0.11073941737413406,0.012724418193101883,0.8080497980117798,0.6053210496902466,0.11026448011398315,0.009275244548916817,0.7882526516914368,0.578460693359375,0.10942955315113068,0.011283177882432938,0.808258056640625,0.5472300052642822,0.10527855902910233,0.008573156781494617,0.8302904963493347,0.5125451683998108,0.09803399443626404,0.001345464843325317,0.8530445694923401,0.4800863564014435,0.08923094719648361,-0.008610190823674202,0.8726286888122559,0.4595273733139038,0.08324718475341797,-0.01573387160897255,0.8841136693954468,0.4585795998573303,0.08776487410068512,-0.01104344055056572,0.8842398524284363,0.4594312310218811,0.09490011632442474,-0.0035413105506449938,0.8831220269203186,0.4639539420604706,0.10419484227895737,0.006904805079102516,0.8796831965446472,0.4739542007446289,0.11330089718103409,0.016688859090209007,0.8730703592300415,0.4923681318759918,0.12520258128643036,0.03067050874233246,0.8607887625694275,0.5337262153625488,0.13829702138900757,0.04556457698345184,0.8330271244049072,0.5748413801193237,0.14663657546043396,0.05586216598749161,0.8030781745910645,0.585551381111145,0.15537337958812714,0.06945636123418808,0.7925683259963989,0.5588987469673157,0.16252900660037994,0.07877160608768463,0.8093278408050537,0.5290722846984863,0.16966287791728973,0.0895940363407135,0.826601505279541,0.49961647391319275,0.17342065274715424,0.10752953588962555,0.8418706059455872,0.4675884246826172,0.16525869071483612,0.10872832685709,0.8615270256996155,0.44746121764183044,0.1775328367948532,0.1338365375995636,0.8662264943122864,0.43650951981544495,0.2066657692193985,0.18570126593112946,0.8557240962982178,0.4726196229457855,0.21425913274288177,0.2017568051815033,0.8306731581687927,0.506801426410675,0.21061095595359802,0.18497176468372345,0.8152182102203369,0.528007984161377,0.2061479538679123,0.17496097087860107,0.8050461411476135,0.5431765913963318,0.20204320549964905,0.1686931550502777,0.7972956895828247,0.5555928349494934,0.19815543293952942,0.16370797157287598,0.790727972984314,0.567680299282074,0.19388414919376373,0.1567692756652832,0.7845836281776428],"type":"quaternion"},{"name":"mixamorigRightFoot.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999999403953552,1,1,0.9999998807907104,1],"interpolation":2300,"type":"vector"},{"name":"mixamorigRightToeBase.position","times":[0.03333333507180214,1.5333333015441895],"values":[2.874124334084627e-7,1.5069999694824219,3.297717654504595e-8,2.8401785812093294e-7,1.5069997310638428,9.48696836644558e-8],"type":"vector"},{"name":"mixamorigRightToeBase.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895],"values":[0.2796005308628082,0.006120674312114716,-0.0016165671404451132,0.9600955247879028,0.27960044145584106,0.006120679434388876,-0.0016163106774911284,0.9600955843925476,0.2796766757965088,0.0061443098820745945,-0.0016171898460015655,0.9600731730461121,0.27975714206695557,0.006169191561639309,-0.0016172307077795267,0.960049569606781,0.27983784675598145,0.006194086279720068,-0.0016178276855498552,0.96002596616745,0.2799137234687805,0.006217621732503176,-0.0016182021936401725,0.960003674030304,0.27998045086860657,0.006238216068595648,-0.0016185317654162645,0.9599841237068176,0.2800340950489044,0.006254910957068205,-0.0016186757711693645,0.9599683284759521,0.27817195653915405,0.022279463708400726,-0.005309459753334522,0.9602581858634949,0.2720908522605896,0.10767976194620132,-0.023725928738713264,0.9559334516525269,0.27558889985084534,0.1702679693698883,-0.026080340147018433,0.945716381072998,0.2979719638824463,0.22597548365592957,0.03896554931998253,0.9266226291656494,0.3257066309452057,0.21402180194854736,0.11146192252635956,0.9141587018966675,0.32943716645240784,0.26049739122390747,0.13887007534503937,0.8968430161476135,0.2744012176990509,0.2801764905452728,0.0636589452624321,0.91768878698349,0.21384356915950775,0.24719874560832977,-0.06835243850946426,0.9425984025001526,0.26110783219337463,0.27637842297554016,-0.10980589687824249,0.9183574318885803,0.30497291684150696,0.2890678942203522,-0.12415600568056107,0.8988974094390869,0.3525436818599701,0.3032930791378021,-0.12186145782470703,0.8768557906150818,0.4145265817642212,0.339128315448761,-0.10740439593791962,0.8376299738883972,0.46063318848609924,0.366455078125,-0.09525638818740845,0.8027788996696472,0.4799743890762329,0.3771663010120392,-0.08600389212369919,0.7873839735984802,0.505857527256012,0.39012959599494934,-0.07491971552371979,0.7656984329223633,0.5322257280349731,0.4024207890033722,-0.06361164897680283,0.7421232461929321,0.5535553097724915,0.4118943512439728,-0.05387420207262039,0.7218152284622192,0.5642857551574707,0.4171467423439026,-0.04778343811631203,0.7108354568481445,0.5611839890480042,0.4186917543411255,-0.04556182399392128,0.7125264406204224,0.5563197731971741,0.4206908643245697,-0.043903373181819916,0.715262234210968,0.5492686629295349,0.42153406143188477,-0.04416624829173088,0.7201821208000183,0.5407801866531372,0.42131781578063965,-0.04567126929759979,0.7266101241111755,0.5252678394317627,0.41567865014076233,-0.050911225378513336,0.7407516837120056,0.49291518330574036,0.4036874771118164,-0.06912820041179657,0.7676538228988647,0.4542649984359741,0.3825284242630005,-0.08452309668064117,0.8001070022583008,0.43574386835098267,0.3519671857357025,-0.062242552638053894,0.8260583281517029,0.4414031505584717,0.3135707676410675,-0.0031684183049947023,0.840729832649231,0.42764610052108765,0.24866054952144623,0.0550764724612236,0.8673253655433655,0.3634699583053589,0.14395448565483093,0.0936875194311142,0.9156360626220703,0.31956881284713745,0.06224813684821129,0.11076288670301437,0.9390061497688293,0.29788926243782043,0.05262249708175659,0.09462857246398926,0.9484399557113647,0.2855489253997803,0.035370759665966034,0.029946235939860344,0.9572429060935974,0.27954941987991333,0.006214434280991554,-0.0015892181545495987,0.9601098895072937,0.2795441746711731,0.0062084621749818325,-0.0015926258638501167,0.9601114392280579,0.27954405546188354,0.006208582315593958,-0.001592917600646615,0.9601114988327026,0.2795441746711731,0.006208617240190506,-0.0015929645160213113,0.9601114392280579,0.2795441448688507,0.006208532955497503,-0.001593011780641973,0.9601114392280579,0.2795441150665283,0.006208476610481739,-0.0015925791813060641,0.9601114392280579],"type":"quaternion"},{"name":"mixamorigRightToeBase.scale","times":[0.03333333507180214,1.5333333015441895],"values":[1,0.9999999403953552,1,0.9999998807907104,0.9999998807907104,0.9999999403953552],"type":"vector"},{"name":"mixamorigRightToe_End.position","times":[0.03333333507180214,1.5333333015441895],"values":[-5.2154067731180476e-8,0.6553000211715698,7.96280659187687e-8,1.1734664440155029e-7,0.6552997827529907,1.1641531294515062e-8],"interpolation":2300,"type":"vector"},{"name":"mixamorigRightToe_End.quaternion","times":[0.03333333507180214,1.5333333015441895],"values":[-1.0670191841199994e-8,-1.8102582544088364e-8,7.621566489035558e-9,1,-1.1834345947647762e-8,-1.83063093572855e-8,5.45704037513417e-11,1],"interpolation":2300,"type":"quaternion"},{"name":"mixamorigRightToe_End.scale","times":[0.03333333507180214,1.5333333015441895],"values":[0.9999999403953552,0.9999999403953552,1,1,1,1],"interpolation":2300,"type":"vector"}],"uuid":"14fb47bd-3981-4afc-8dc5-00e6800f97da","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/baked/sword_shield/two-hand-sword-combo.json b/public/anims/baked/sword_shield/two-hand-sword-combo.json new file mode 100644 index 0000000..7bf01f4 --- /dev/null +++ b/public/anims/baked/sword_shield/two-hand-sword-combo.json @@ -0,0 +1 @@ +{"name":"sword_shield/two-hand-sword-combo","duration":3.299999952316284,"tracks":[{"name":"Hips.position","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.09472280740737915,60.4478645324707,0.6213815808296204,-0.027022123336791992,60.43745803833008,0.7230977416038513,0.09897416830062866,60.475677490234375,0.9166277647018433,0.28745412826538086,60.54171371459961,1.204103946685791,0.5130873918533325,60.63006591796875,1.6098384857177734,0.7627038955688477,60.73182678222656,2.1580870151519775,1.0261032581329346,60.87794876098633,2.7697975635528564,1.263706088066101,61.004154205322266,3.5538454055786133,1.4645116329193115,61.11774826049805,4.512465476989746,1.6086103916168213,61.125732421875,5.509494304656982,1.7138580083847046,60.90583419799805,6.615047931671143,1.8362151384353638,60.35069274902344,7.936254024505615,1.959971308708191,59.67501449584961,9.373824119567871,2.1116323471069336,59.182926177978516,10.862919807434082,2.2494778633117676,58.920406341552734,12.369566917419434,2.2756643295288086,59.01626968383789,14.152926445007324,2.2158312797546387,59.48412322998047,16.118209838867188,2.0681731700897217,60.13978576660156,18.221513748168945,1.8600800037384033,60.733421325683594,20.50165367126465,1.6967006921768188,61.493492126464844,23.19751739501953,1.5248249769210815,62.01447296142578,25.75332260131836,1.3910439014434814,62.471466064453125,28.304731369018555,1.2912037372589111,62.967864990234375,31.056900024414062,1.2332687377929688,63.53024673461914,33.815284729003906,1.220757246017456,64.19512176513672,36.412269592285156,1.450446367263794,64.7667007446289,39.04461669921875,1.7897849082946777,65.09388732910156,41.57309341430664,2.230189323425293,65.14141845703125,43.916053771972656,2.7783565521240234,65.01720428466797,46.1004524230957,3.2372024059295654,64.7475357055664,48.098060607910156,3.435607671737671,64.35608673095703,49.90260314941406,3.4748079776763916,63.878536224365234,51.68782424926758,3.344895124435425,63.29854965209961,53.65800857543945,3.0077390670776367,62.61142349243164,55.84779357910156,2.406097888946533,61.82414245605469,57.96764373779297,1.5360493659973145,60.83281707763672,59.92565155029297,0.44173383712768555,59.860923767089844,61.59400177001953,-0.7096638679504395,58.82035827636719,63.028480529785156,-1.6798176765441895,58.134464263916016,64.25531768798828,-2.331007957458496,58.358314514160156,64.99506378173828,-3.0174570083618164,59.08958053588867,65.7961196899414,-3.681094169616699,59.84239196777344,66.85740661621094,-4.113582134246826,60.201904296875,67.77018737792969,-4.303977012634277,60.46234130859375,68.7746353149414,-4.169311046600342,60.724403381347656,70.14411163330078,-3.942847728729248,60.701725006103516,71.66089630126953,-3.8553500175476074,60.33480453491211,73.57289123535156,-3.63832426071167,59.734458923339844,75.67058563232422,-3.3533339500427246,59.469482421875,77.82556915283203,-3.0233049392700195,59.597042083740234,80.208251953125,-2.780170440673828,59.98543167114258,82.74952697753906,-2.6401472091674805,60.447654724121094,85.11015319824219,-2.5064268112182617,60.80394744873047,87.32275390625,-2.362684726715088,61.068389892578125,89.24251556396484,-2.2526841163635254,61.633094787597656,91.02389526367188,-2.110091209411621,62.28065490722656,93.10001373291016,-1.9064159393310547,62.96781539916992,95.25941467285156,-1.5811491012573242,63.44308853149414,97.339111328125,-1.138354778289795,63.67107009887695,99.42529296875,-0.6624083518981934,63.56671142578125,101.55726623535156,-0.16021490097045898,62.99470520019531,103.77549743652344,0.35109519958496094,62.179969787597656,106.08833312988281,0.7714390754699707,61.37322998046875,108.07072448730469,1.0592432022094727,60.54241943359375,109.80179595947266,1.159471035003662,59.65393829345703,111.7026596069336,1.1734609603881836,58.707664489746094,114.11273193359375,1.0605297088623047,58.32339096069336,117.1363754272461,0.929168701171875,58.191497802734375,119.26152038574219,0.8082857131958008,57.60266876220703,121.52392578125,0.5985307693481445,57.34656524658203,123.43819427490234,0.399658203125,57.34366989135742,124.92512512207031,-0.0719137191772461,57.37226867675781,125.93219757080078,-0.7941570281982422,57.492591857910156,126.46365356445312,-1.497574806213379,57.75048065185547,127.0396957397461,-2.1105241775512695,58.056312561035156,127.48420715332031,-2.574398994445801,58.20995330810547,127.68373107910156,-2.8059120178222656,58.24892807006836,127.87227630615234,-2.9972267150878906,58.243011474609375,128.07510375976562,-3.0768823623657227,58.23799514770508,128.2771759033203,-3.1069068908691406,58.290401458740234,128.48927307128906,-3.055553436279297,58.44484329223633,128.61978149414062,-2.908823013305664,58.7025146484375,128.6310577392578,-2.7021427154541016,59.11008834838867,128.50799560546875,-2.3941307067871094,59.61415100097656,128.2831268310547,-2.014484405517578,60.14495849609375,127.98944091796875,-1.5273246765136719,60.63459014892578,127.62310028076172,-1.0160608291625977,61.023353576660156,127.16202545166016,-0.5972299575805664,61.2563591003418,126.64464569091797,-0.33911609649658203,61.289676666259766,126.04853057861328,-0.24114418029785156,61.24190902709961,125.48592376708984,-0.2263784408569336,61.16447830200195,125.0450210571289,-0.2140798568725586,61.070945739746094,124.68449401855469,-0.23477554321289062,60.9604377746582,124.42807006835938,-0.2915639877319336,60.842506408691406,124.27583312988281,-0.3318624496459961,60.72791290283203,124.18756103515625,-0.33329105377197266,60.622352600097656,124.10513305664062,-0.2847614288330078,60.533565521240234,123.99249267578125,-0.19913768768310547,60.47224426269531,123.85795593261719,-0.09459590911865234,60.44786834716797,123.72616577148438],"type":"vector"},{"name":"Hips.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.23096609115600586,-0.5011129379272461,0.26530832052230835,0.7906655073165894,0.22536584734916687,-0.4998190999031067,0.2686088979244232,0.7919850945472717,0.22104628384113312,-0.49862751364707947,0.2725440263748169,0.7926089763641357,0.21622958779335022,-0.49777039885520935,0.27713504433631897,0.792884349822998,0.210139662027359,-0.4979322552680969,0.2833896577358246,0.7922090291976929,0.2050536423921585,-0.5003334283828735,0.2893374264240265,0.7898755669593811,0.2025325447320938,-0.5037782192230225,0.29371243715286255,0.7867153882980347,0.20459559559822083,-0.506187379360199,0.2973715662956238,0.7832529544830322,0.21179558336734772,-0.5056214928627014,0.29737430810928345,0.7817020416259766,0.2226128727197647,-0.5012803077697754,0.2945101857185364,0.7825761437416077,0.23796217143535614,-0.49093642830848694,0.2883853018283844,0.7868859171867371,0.25680139660835266,-0.47224873304367065,0.279272198677063,0.795638918876648,0.2760220766067505,-0.44406771659851074,0.2676762640476227,0.8092991709709167,0.29482758045196533,-0.4018649756908417,0.24860656261444092,0.8305276036262512,0.3076440095901489,-0.34672749042510986,0.22839608788490295,0.8561369776725769,0.31033071875572205,-0.27690479159355164,0.21407006680965424,0.8838509917259216,0.3124682903289795,-0.2064114212989807,0.19859455525875092,0.9057141542434692,0.3150673806667328,-0.14288046956062317,0.18559697270393372,0.9197127223014832,0.320044606924057,-0.08467802405357361,0.1733851134777069,0.9275444149971008,0.327958345413208,-0.03176596015691757,0.1587298959493637,0.9307196736335754,0.3366558849811554,0.01918976753950119,0.14219769835472107,0.9306312203407288,0.34446024894714355,0.06248505413532257,0.12440139055252075,0.9284218549728394,0.35013601183891296,0.09639977663755417,0.10520686954259872,0.9257663488388062,0.3501857817173004,0.1149580180644989,0.08433016389608383,0.9257661700248718,0.3425631523132324,0.11330282688140869,0.06845550984144211,0.9301219582557678,0.3302972614765167,0.09934744238853455,0.05943089723587036,0.9367506504058838,0.3157927989959717,0.07622519135475159,0.06040576472878456,0.9438304305076599,0.3057720959186554,0.05680017173290253,0.06266262382268906,0.9483409523963928,0.2979072034358978,0.051537130028009415,0.06467268615961075,0.9510061740875244,0.293423056602478,0.060557298362255096,0.07075393944978714,0.9514355659484863,0.2918616831302643,0.08281127363443375,0.07250478863716125,0.9501063823699951,0.2928597629070282,0.10641659796237946,0.07126602530479431,0.9475388526916504,0.2971009612083435,0.13727468252182007,0.06940468400716782,0.9423745274543762,0.3032436668872833,0.17840524017810822,0.06356047093868256,0.9339030385017395,0.30574947595596313,0.22134074568748474,0.05363323166966438,0.9244723320007324,0.30460062623023987,0.26648300886154175,0.043071284890174866,0.9134277105331421,0.3002457916736603,0.30832669138908386,0.03168352693319321,0.9020993709564209,0.2963162660598755,0.3481713831424713,0.026461316272616386,0.88897305727005,0.30149245262145996,0.39672601222991943,0.013165605254471302,0.8669126033782959,0.29865768551826477,0.4459128975868225,-0.022288916632533073,0.843485951423645,0.2976611256599426,0.48403096199035645,-0.038978781551122665,0.8219443559646606,0.3056413233280182,0.50993812084198,-0.03150876984000206,0.8034635782241821,0.30451637506484985,0.5294497609138489,-0.02903047204017639,0.7912711501121521,0.304323673248291,0.54221510887146,-0.023954931646585464,0.782825767993927,0.30465564131736755,0.5759370923042297,-0.038077253848314285,0.7576487064361572,0.2960663437843323,0.6473723649978638,-0.061078112572431564,0.6996594071388245,0.2836432158946991,0.7027493119239807,-0.07703638821840286,0.6478853821754456,0.2826268970966339,0.725588321685791,-0.09014669805765152,0.6209003925323486,0.28611162304878235,0.7331838607788086,-0.10095777362585068,0.6085960865020752,0.28500479459762573,0.7271299958229065,-0.11145687848329544,0.6145173907279968,0.27486690878868103,0.7098789811134338,-0.1154230609536171,0.6381203532218933,0.26581549644470215,0.6810007691383362,-0.10592272877693176,0.6740627288818359,0.2639305889606476,0.6506655812263489,-0.08863860368728638,0.706483006477356,0.25350263714790344,0.6228249073028564,-0.06996891647577286,0.7368378639221191,0.23868867754936218,0.5960070490837097,-0.048715364187955856,0.7651340961456299,0.2327614724636078,0.5677152872085571,-0.02562021091580391,0.789218008518219,0.23115423321723938,0.5411678552627563,0.0005592265515588224,0.8085201382637024,0.2293930947780609,0.5073085427284241,0.02565114013850689,0.830276370048523,0.22910182178020477,0.45519447326660156,0.05255098268389702,0.8588066101074219,0.22652257978916168,0.3916257321834564,0.0850510448217392,0.8877404928207397,0.224039688706398,0.3156965970993042,0.11871252954006195,0.9143572449684143,0.22135913372039795,0.21748705208301544,0.16087377071380615,0.9369200468063354,0.2172347903251648,0.09283566474914551,0.2151055634021759,0.9475865364074707,0.2089938223361969,-0.05052317678928375,0.2766522765159607,0.9366069436073303,0.19054237008094788,-0.21019108593463898,0.3347410559654236,0.8985887765884399,0.167805016040802,-0.3619546592235565,0.3749382495880127,0.8368104100227356,0.14994782209396362,-0.47884926199913025,0.38446566462516785,0.7748582363128662,0.1411508172750473,-0.5677348375320435,0.3652670383453369,0.724108874797821,0.14785224199295044,-0.6150786280632019,0.3318447768688202,0.6997836232185364,0.1669512242078781,-0.6207302808761597,0.30937427282333374,0.700791597366333,0.1742599606513977,-0.6176225543022156,0.3085744380950928,0.7021094560623169,0.1792544424533844,-0.6520887017250061,0.31092825531959534,0.6678112745285034,0.18485811352729797,-0.6915761232376099,0.3167150020599365,0.6222874522209167,0.18442508578300476,-0.7117102146148682,0.32266297936439514,0.5961079001426697,0.18121495842933655,-0.7225319147109985,0.3260156214237213,0.5820847153663635,0.17765206098556519,-0.7359912395477295,0.3212812840938568,0.5688014030456543,0.17049184441566467,-0.7445674538612366,0.3136790096759796,0.5640543699264526,0.1632542759180069,-0.7474743723869324,0.3085819184780121,0.5651613473892212,0.1616142988204956,-0.7472477555274963,0.3034275770187378,0.5687119364738464,0.16650202870368958,-0.7409065961837769,0.2975412607192993,0.5786222815513611,0.1727796494960785,-0.7277624607086182,0.2896806001663208,0.5971550941467285,0.182026669383049,-0.7076255679130554,0.28430303931236267,0.6207287311553955,0.1965368688106537,-0.6822323203086853,0.28240257501602173,0.6451210379600525,0.21221746504306793,-0.6537491679191589,0.2830923795700073,0.66890549659729,0.22575214505195618,-0.6258401870727539,0.28138428926467896,0.6915077567100525,0.23458757996559143,-0.6013169884681702,0.2790968418121338,0.7109793424606323,0.23814013600349426,-0.582703173160553,0.27699047327041626,0.7259632349014282,0.2369428277015686,-0.5666300058364868,0.2763860821723938,0.7391883134841919,0.23457302153110504,-0.5518330335617065,0.27606210112571716,0.7511628270149231,0.23488707840442657,-0.5390090942382812,0.2739943563938141,0.7610679268836975,0.23422975838184357,-0.5288320183753967,0.2703342139720917,0.7696704268455505,0.23146046698093414,-0.5212854743003845,0.2680233120918274,0.7764348983764648,0.22954316437244415,-0.5149840116500854,0.2675434350967407,0.781359076499939,0.2284330278635025,-0.5101925730705261,0.26786258816719055,0.7847111225128174,0.22814887762069702,-0.5062749981880188,0.26770856976509094,0.7873790860176086,0.22874698042869568,-0.5035911798477173,0.2671462893486023,0.7891157269477844,0.22993674874305725,-0.5018560886383057,0.2664145231246948,0.7901220917701721,0.23068754374980927,-0.5010959506034851,0.26583075523376465,0.7905821800231934,0.23096606135368347,-0.5011130571365356,0.2653089165687561,0.790665328502655],"type":"quaternion"},{"name":"Hips.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999997615814209,0.9999998211860657,0.9999997615814209,0.9999998807907104,0.9999998211860657,0.9999998807907104],"type":"vector"},{"name":"LeftUpLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[8.305602073669434,-6.711602210998535,5.719299793243408,8.305596351623535,-6.711589813232422,5.719297885894775],"type":"vector"},{"name":"LeftUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.8220342993736267,0.09007623046636581,-0.357753187417984,0.43377238512039185,0.8266195058822632,0.08714386820793152,-0.35153573751449585,0.4307304620742798,0.8235250115394592,0.09146063774824142,-0.3488635718822479,0.4378763437271118,0.8208228349685669,0.09690751880407333,-0.3462308645248413,0.44382768869400024,0.819255530834198,0.0988098531961441,-0.3456050455570221,0.4467819035053253,0.8185820579528809,0.09997375309467316,-0.34831690788269043,0.4456501007080078,0.825737714767456,0.09969223290681839,-0.3549238443374634,0.4269048273563385,0.8380354642868042,0.08642138540744781,-0.36292093992233276,0.3981410264968872,0.8329869508743286,0.07066129148006439,-0.3837812542915344,0.3922392427921295,0.8137404918670654,0.0504768081009388,-0.41678422689437866,0.4019569754600525,0.780278205871582,0.045253653079271317,-0.44302061200141907,0.4391476809978485,0.7568097710609436,0.05581206455826759,-0.4487340748310089,0.47197631001472473,0.7402149438858032,0.07089944928884506,-0.44476088881492615,0.4992421567440033,0.7261074185371399,0.0861547663807869,-0.4239509105682373,0.5344257354736328,0.717644989490509,0.0709734633564949,-0.3786025643348694,0.580179750919342,0.7295215725898743,0.03328704461455345,-0.31812047958374023,0.6045573353767395,0.7486587166786194,0.00010742086305981502,-0.25880688428878784,0.6103516221046448,0.767963707447052,-0.030233444646000862,-0.20560717582702637,0.6058410406112671,0.783388614654541,-0.05222335457801819,-0.1578471064567566,0.598881721496582,0.7978233695030212,-0.06668688356876373,-0.11309332400560379,0.5884221792221069,0.8051587343215942,-0.07655275613069534,-0.06885064393281937,0.5840537548065186,0.8108015060424805,-0.07869311422109604,-0.03185955807566643,0.579131543636322,0.818011462688446,-0.07504314929246902,-0.0023715603165328503,0.5702807903289795,0.8301162719726562,-0.06364890933036804,0.013243982568383217,0.553787350654602,0.8514459729194641,-0.04834878072142601,0.008855191059410572,0.5221338868141174,0.8774716258049011,-0.03650045394897461,-0.007106744218617678,0.47818487882614136,0.9040589928627014,-0.03171345964074135,-0.03284851461648941,0.42496195435523987,0.9231526255607605,-0.03329318389296532,-0.053578779101371765,0.3792229890823364,0.9377148747444153,-0.04150044173002243,-0.05810503289103508,0.339988648891449,0.9490072727203369,-0.053227052092552185,-0.04892951622605324,0.30685165524482727,0.9554174542427063,-0.06016732007265091,-0.030154766514897346,0.28748592734336853,0.9593470692634583,-0.05988924205303192,-0.024239618331193924,0.27473440766334534,0.9618621468544006,-0.0566437728703022,-0.023490266874432564,0.26657262444496155,0.9626719355583191,-0.04757802188396454,-0.022134022787213326,0.2655356526374817,0.9621520042419434,-0.031083859503269196,-0.0257908683270216,0.26950350403785706,0.9590989351272583,-0.010762757621705532,-0.030716897919774055,0.28119367361068726,0.9527120590209961,0.00474128033965826,-0.04261917993426323,0.3008335828781128,0.9392409324645996,0.015647385269403458,-0.07679536193609238,0.33419179916381836,0.9199414849281311,0.034021344035863876,-0.11061369627714157,0.374586284160614,0.8996171355247498,0.06491804867982864,-0.13755041360855103,0.409334272146225,0.8793615102767944,0.06993283331394196,-0.16401377320289612,0.4415113031864166,0.8619770407676697,0.0010061231441795826,-0.18880301713943481,0.470476359128952,0.8427958488464355,-0.06598550081253052,-0.21011346578598022,0.4911145269870758,0.8435693979263306,-0.08275757730007172,-0.22220028936862946,0.48183900117874146,0.8469679355621338,-0.08543393015861511,-0.1869245022535324,0.49031171202659607,0.8479373455047607,-0.08959104120731354,-0.10346488654613495,0.5121237635612488,0.8454409241676331,-0.08504773676395416,-0.0403127521276474,0.5257104635238647,0.837945818901062,-0.07471153140068054,-0.022224392741918564,0.5401583909988403,0.8372735977172852,-0.05902813374996185,-0.021315470337867737,0.5431706309318542,0.8436280488967896,-0.03250815346837044,-0.04240650683641434,0.5342626571655273,0.8506228923797607,-0.003906627651304007,-0.08442167192697525,0.5189397931098938,0.8647260665893555,0.025876956060528755,-0.1101747527718544,0.4893268346786499,0.8735230565071106,0.01663830876350403,-0.07561763375997543,0.4805857241153717,0.8668310642242432,-0.016258718445897102,-0.020023275166749954,0.4979344308376312,0.8623189330101013,-0.04556583985686302,0.01618432253599167,0.5040515065193176,0.8454765677452087,-0.09417285770177841,0.03298652544617653,0.5246073007583618,0.8313384652137756,-0.15119829773902893,0.040080465376377106,0.5333001613616943,0.820277750492096,-0.19554977118968964,0.03530377149581909,0.5363379716873169,0.8089848160743713,-0.22400881350040436,0.022363489493727684,0.5430133938789368,0.7970705628395081,-0.24502956867218018,0.0127490209415555,0.5517939925193787,0.7859571576118469,-0.25117647647857666,0.014054039493203163,0.564786970615387,0.7861325144767761,-0.24217087030410767,0.008961111307144165,0.5685672760009766,0.7958400845527649,-0.2164042592048645,-0.034273527562618256,0.5644760131835938,0.8138995170593262,-0.18033288419246674,-0.08955229818820953,0.5450028777122498,0.8212041854858398,-0.14380419254302979,-0.17212195694446564,0.5247076749801636,0.8269757628440857,-0.09964925795793533,-0.26687905192375183,0.48472335934638977,0.8092594742774963,-0.04329987242817879,-0.3473080098628998,0.47180649638175964,0.8267291784286499,0.0008988686022348702,-0.4036915600299835,0.39185598492622375,0.8368411064147949,0.04837252199649811,-0.4092191457748413,0.3604118227958679,0.834708034992218,0.07771781831979752,-0.3754141628742218,0.3953310251235962,0.8359208703041077,0.06867189705371857,-0.35525956749916077,0.41268765926361084,0.8334949612617493,0.09178584069013596,-0.3788618743419647,0.3915676176548004,0.8270161151885986,0.11653342097997665,-0.4204166531562805,0.3545619547367096,0.8254595398902893,0.11582650989294052,-0.4466496407985687,0.3251228332519531,0.8273935317993164,0.11027055233716965,-0.46227720379829407,0.29926592111587524,0.8264115452766418,0.11760228127241135,-0.48010095953941345,0.2696603834629059,0.8280386924743652,0.12405796349048615,-0.48866939544677734,0.24528318643569946,0.83119797706604,0.12522289156913757,-0.49121227860450745,0.22834116220474243,0.831497311592102,0.12686218321323395,-0.4917425215244293,0.22518335282802582,0.8293720483779907,0.13085214793682098,-0.48940742015838623,0.23558464646339417,0.8297964334487915,0.13386549055576324,-0.47887811064720154,0.253364622592926,0.830379068851471,0.1329229772090912,-0.46308082342147827,0.2799253761768341,0.8283029198646545,0.12922391295433044,-0.44643262028694153,0.31291118264198303,0.8247222900390625,0.12126750499010086,-0.4303548038005829,0.3462977111339569,0.8216841816902161,0.11421502381563187,-0.41563379764556885,0.372878760099411,0.8202917575836182,0.10722669959068298,-0.40441128611564636,0.3899683356285095,0.8198273181915283,0.09900514036417007,-0.3982754349708557,0.39932164549827576,0.8213704228401184,0.08822355419397354,-0.39233189821243286,0.404527872800827,0.8235044479370117,0.08133324980735779,-0.38565829396247864,0.4080355167388916,0.8244826197624207,0.0812794640660286,-0.37870705127716064,0.41255664825439453,0.8266130089759827,0.08450871706008911,-0.37079036235809326,0.41482964158058167,0.829145610332489,0.08472207188606262,-0.36475610733032227,0.4150814712047577,0.8304176330566406,0.08392708748579025,-0.3607226312160492,0.41622352600097656,0.8303623795509338,0.08325091004371643,-0.35909441113471985,0.41787415742874146,0.8294113278388977,0.08386392891407013,-0.35777878761291504,0.42075884342193604,0.8274351954460144,0.08556514978408813,-0.35739192366600037,0.4246182143688202,0.824918270111084,0.08762866258621216,-0.3575472831726074,0.42894166707992554,0.8231741786003113,0.0892525315284729,-0.3573220372200012,0.43213334679603577,0.8220794200897217,0.09011173993349075,-0.357779860496521,0.4336574375629425],"type":"quaternion"},{"name":"LeftUpLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1.0000001192092896,0.9999992847442627,0.9999998807907104,1,0.9999992251396179],"interpolation":2300,"type":"vector"},{"name":"LeftLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[0.0000030365079055627575,25.81269645690918,9.318720231021871e-7,0.00000849286971060792,25.812707901000977,-0.000004227493718644837],"type":"vector"},{"name":"LeftLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.2524700462818146,0.07982819527387619,0.08678113669157028,0.9603933691978455,0.26209983229637146,0.0871877446770668,0.0954052284359932,0.9563471078872681,0.2812671661376953,0.0956592783331871,0.11238119751214981,0.9482133388519287,0.294501394033432,0.10328812897205353,0.13187535107135773,0.9408556818962097,0.3021170198917389,0.11236831545829773,0.1513432115316391,0.9344484210014343,0.3036605417728424,0.12152683734893799,0.16560237109661102,0.9303748607635498,0.26456117630004883,0.10930955410003662,0.15554216504096985,0.9454445838928223,0.19499626755714417,0.061120931059122086,0.1166401356458664,0.9719237089157104,0.17856477200984955,-0.008632165379822254,0.08751192688941956,0.9799907207489014,0.18295437097549438,-0.06888736039400101,0.06255292147397995,0.978708028793335,0.23415759205818176,-0.098751962184906,0.042850151658058167,0.9662205576896667,0.2697364091873169,-0.11060522496700287,0.05846203491091728,0.9547727704048157,0.29278114438056946,-0.1067999079823494,0.10892422497272491,0.9439324736595154,0.3363637626171112,-0.09226149320602417,0.1543513387441635,0.9244040846824646,0.3782752454280853,-0.0740516185760498,0.14144302904605865,0.9118212461471558,0.39120322465896606,-0.05837814137339592,0.12638269364833832,0.9097139835357666,0.38133081793785095,-0.04528788849711418,0.12577520310878754,0.9147220253944397,0.36324426531791687,-0.031200839206576347,0.12313580513000488,0.9229938983917236,0.3476907014846802,-0.017269650474190712,0.1241612434387207,0.929191529750824,0.33127909898757935,-0.0025315003003925085,0.12423843890428543,0.9353141784667969,0.3259632885456085,0.012711895629763603,0.11691541224718094,0.9380390048027039,0.3259839713573456,0.02719883620738983,0.1109650507569313,0.9384462833404541,0.3304845988750458,0.04184741526842117,0.10161177814006805,0.9373920559883118,0.3375920355319977,0.056356437504291534,0.08982564508914948,0.9353004693984985,0.33559563755989075,0.06920989602804184,0.08093604445457458,0.9359673857688904,0.32729607820510864,0.07889621704816818,0.07461275160312653,0.9386616349220276,0.31122130155563354,0.08597826212644577,0.07401333749294281,0.9435418248176575,0.2980816066265106,0.09082596749067307,0.06941305100917816,0.9476707577705383,0.2850703299045563,0.09338421374559402,0.05836785212159157,0.9521594047546387,0.26748907566070557,0.09526316821575165,0.052395161241292953,0.9574075937271118,0.261273056268692,0.09991395473480225,0.04434945061802864,0.9590551257133484,0.26194044947624207,0.11784390360116959,0.04468061402440071,0.9568195939064026,0.26395726203918457,0.14790771901607513,0.048215992748737335,0.9519060254096985,0.2709479331970215,0.18709564208984375,0.0504724495112896,0.9428865313529968,0.28590330481529236,0.23225350677967072,0.051972683519124985,0.9282329678535461,0.31071314215660095,0.28002631664276123,0.05530627816915512,0.9066332578659058,0.3523198664188385,0.30490556359291077,0.048154737800359726,0.8835068941116333,0.40613844990730286,0.3270814120769501,0.05474236235022545,0.8515119552612305,0.431654691696167,0.3426218330860138,0.07720392942428589,0.8308574557304382,0.4358687698841095,0.33479198813438416,0.08441329002380371,0.831148087978363,0.4340658187866211,0.3312021493911743,0.10644380748271942,0.8310004472732544,0.45660027861595154,0.28293177485466003,0.1164856106042862,0.835402250289917,0.4653884172439575,0.27414470911026,0.09601146727800369,0.8360862135887146,0.4445499777793884,0.29869917035102844,0.08997669816017151,0.8396775722503662,0.4254315197467804,0.3138796389102936,0.07091180980205536,0.8458481431007385,0.4066913425922394,0.33062881231307983,0.05482546612620354,0.8498710989952087,0.3924088776111603,0.3511154353618622,0.041254620999097824,0.8491356372833252,0.38829630613327026,0.37221670150756836,0.03155107423663139,0.8424282073974609,0.36720728874206543,0.39043062925338745,0.01739204302430153,0.8440499305725098,0.34515833854675293,0.40194687247276306,0.0023349395487457514,0.8481149673461914,0.3618486523628235,0.3791767358779907,0.000032098283554660156,0.8516399264335632,0.3636610507965088,0.32255005836486816,-0.023899726569652557,0.8735793828964233,0.39440008997917175,0.19515882432460785,-0.12103796750307083,0.8897817134857178,0.4348098933696747,0.046229686588048935,-0.21038289368152618,0.8743810653686523,0.49092981219291687,0.059581223875284195,-0.17799673974514008,0.8507380485534668,0.5653290748596191,0.06293461471796036,-0.14733262360095978,0.8091571927070618,0.5864688158035278,0.0248006172478199,-0.14345647394657135,0.7967807054519653,0.5777825713157654,0.01005987823009491,-0.11717656999826431,0.8076730370521545,0.5462321639060974,0.012650416232645512,-0.07061856240034103,0.8345558643341064,0.48576754331588745,0.019431816413998604,-0.020788170397281647,0.8736246824264526,0.38951289653778076,0.04904960095882416,0.026809148490428925,0.919323205947876,0.2540450394153595,0.13725844025611877,0.06981924176216125,0.9548541903495789,0.1504526436328888,0.14610041677951813,0.0756242424249649,0.9748331308364868,0.08439026772975922,0.10641957074403763,0.06682541221380234,0.9884774088859558,0.08191148936748505,0.06618822365999222,0.046532727777957916,0.993350088596344,0.08859824389219284,0.029140572994947433,0.04097629711031914,0.9947975277900696,0.2532685101032257,0.022405436262488365,0.017327094450592995,0.9669812917709351,0.269517183303833,0.021974977105855942,0.002672238042578101,0.9627411365509033,0.3137893080711365,0.0041951630264520645,-0.005330447573214769,0.9494684338569641,0.38686925172805786,-0.013730171136558056,-0.030611585825681686,0.9215241074562073,0.4166683852672577,-0.022378787398338318,-0.050525251775979996,0.9073774814605713,0.43203839659690857,-0.0386282317340374,-0.02718929387629032,0.900617241859436,0.4275529682636261,-0.04983053728938103,0.010822994634509087,0.902550995349884,0.41368111968040466,-0.05137111619114876,0.028969837352633476,0.9085096120834351,0.3950502574443817,-0.049991268664598465,0.04311012476682663,0.9162847399711609,0.37843161821365356,-0.04570571333169937,0.06130087375640869,0.9224655628204346,0.36823803186416626,-0.04118041694164276,0.0733129009604454,0.9259212613105774,0.3611622154712677,-0.03640015050768852,0.08045019209384918,0.9283127784729004,0.362580806016922,-0.03050755150616169,0.07885434478521347,0.9281091094017029,0.36977851390838623,-0.021152319386601448,0.07480204850435257,0.9258623719215393,0.37604889273643494,-0.01197571586817503,0.06606443971395493,0.9241641163825989,0.3801638185977936,-0.0027586589567363262,0.05654424801468849,0.9231850504875183,0.37807193398475647,0.006158157717436552,0.05167357251048088,0.9243124723434448,0.36856594681739807,0.01503080502152443,0.051016759127378464,0.9280789494514465,0.3521144390106201,0.024046223610639572,0.05454220250248909,0.9340569376945496,0.32989975810050964,0.03226077929139137,0.06289298832416534,0.9413659572601318,0.3066640794277191,0.0409875251352787,0.06788380444049835,0.9485087990760803,0.2861596941947937,0.049650419503450394,0.06755547970533371,0.954507052898407,0.2697139382362366,0.05667448788881302,0.06911439448595047,0.9587833881378174,0.2573184370994568,0.06141921132802963,0.07307276129722595,0.9616004228591919,0.24868905544281006,0.06394604593515396,0.07672084122896194,0.9634202718734741,0.24251192808151245,0.06632135808467865,0.07660119235515594,0.9648428559303284,0.23946410417556763,0.06885802000761032,0.07745379954576492,0.9653581976890564,0.23960688710212708,0.07207485288381577,0.07943151146173477,0.9649271965026855,0.24271154403686523,0.07478548586368561,0.08188395947217941,0.9637392163276672,0.24704836308956146,0.07719007134437561,0.08431652933359146,0.9622367024421692,0.25079554319381714,0.07921987026929855,0.08621135354042053,0.9609336256980896,0.252732515335083,0.07959236949682236,0.08685009926557541,0.9603376388549805,0.25224339962005615,0.07973641902208328,0.08676247298717499,0.960462212562561],"type":"quaternion"},{"name":"LeftLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1,0.9999999403953552,1,0.9999999403953552,1],"type":"vector"},{"name":"LeftFoot.position","times":[0.03333333507180214,3.299999952316284],"values":[8.821469918984803e-7,22.742708206176758,-0.0000021680427835235605,0.0000015886026858424884,22.742706298828125,3.958824095207092e-7],"type":"vector"},{"name":"LeftFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.4764857292175293,-0.09430567920207977,0.12425436824560165,0.865233302116394,-0.48587191104888916,-0.09688436985015869,0.11616523563861847,0.8608411550521851,-0.4759840667247772,-0.09503345191478729,0.10716276615858078,0.8677119016647339,-0.45707863569259644,-0.09262960404157639,0.09880779683589935,0.8790540099143982,-0.43546435236930847,-0.0895492434501648,0.09060011804103851,0.8911471962928772,-0.42085000872612,-0.09330271929502487,0.09759705513715744,0.897025465965271,-0.39763346314430237,-0.1068299412727356,0.13233476877212524,0.9016443490982056,-0.37029916048049927,-0.11758551746606827,0.16282758116722107,0.9069395661354065,-0.39262640476226807,-0.10086585581302643,0.13308727741241455,0.9044104814529419,-0.42057833075523376,-0.07998742908239365,0.09636484086513519,0.8985709547996521,-0.44700631499290466,-0.06423549354076385,0.07021080702543259,0.8894546627998352,-0.4273536503314972,-0.0496501699090004,0.04522831365466118,0.9015865325927734,-0.3984346091747284,-0.027937987819314003,0.0037398668937385082,0.9167635440826416,-0.42346593737602234,-0.009677279740571976,-0.03049619123339653,0.9053468704223633,-0.4479424059391022,-0.0043374402448534966,-0.03841433674097061,0.8932262659072876,-0.4624703526496887,0.0006150246481411159,-0.04514961689710617,0.8854842185974121,-0.4675309360027313,0.008396041579544544,-0.05717868730425835,0.8820855617523193,-0.47006040811538696,0.013576282188296318,-0.0647348165512085,0.8801524639129639,-0.4748391807079315,0.017900751903653145,-0.07093601673841476,0.8770264387130737,-0.482768714427948,0.02063489519059658,-0.07491420954465866,0.8722937703132629,-0.49384137988090515,0.020054621621966362,-0.07329593598842621,0.8662253022193909,-0.5082681775093079,0.019318528473377228,-0.07094305008649826,0.8580543994903564,-0.528370201587677,0.016883548349142075,-0.06598562002182007,0.8462775945663452,-0.5529116988182068,0.01343491394072771,-0.0592663437128067,0.8310208916664124,-0.5761757493019104,0.01075209304690361,-0.05388808622956276,0.8154765367507935,-0.5993019938468933,0.009989183396100998,-0.051779601722955704,0.7987841963768005,-0.6193884015083313,0.011672206223011017,-0.05339779332280159,0.783179759979248,-0.6377405524253845,0.01313809584826231,-0.05396420136094093,0.7682462334632874,-0.6524412631988525,0.013531792908906937,-0.05271578207612038,0.7558825016021729,-0.6608553528785706,0.01548142172396183,-0.05401410907506943,0.7484070658683777,-0.6700926423072815,0.01594076305627823,-0.05282916501164436,0.7402235269546509,-0.6805872917175293,0.018536390736699104,-0.05567051097750664,0.730313777923584,-0.6908246874809265,0.021821174770593643,-0.05976900830864906,0.7202171683311462,-0.7024620175361633,0.023965420201420784,-0.06189306825399399,0.7086198329925537,-0.7164384722709656,0.02478305622935295,-0.06191270053386688,0.694455623626709,-0.7325445413589478,0.02567652426660061,-0.061811938881874084,0.677420437335968,-0.7461988925933838,0.018333962187170982,-0.049162738025188446,0.6636521220207214,-0.7422521114349365,0.004641053732484579,-0.024917712435126305,0.6696413159370422,-0.7293223738670349,-0.003967758268117905,-0.008750895038247108,0.6841028332710266,-0.7095466256141663,-0.022241035476326942,0.02256842330098152,0.7039457559585571,-0.6731563806533813,-0.01943349651992321,0.014973928220570087,0.7390931248664856,-0.669792652130127,0.035418301820755005,-0.08290709555149078,0.7370547652244568,-0.6474704146385193,0.0528440922498703,-0.09938954561948776,0.7537316083908081,-0.6324282884597778,0.04751458019018173,-0.08940401673316956,0.7679738402366638,-0.6269676089286804,0.03491200506687164,-0.06936037540435791,0.7751657366752625,-0.6143779158592224,0.021110985428094864,-0.04686881974339485,0.7873356342315674,-0.6084080934524536,0.006098151206970215,-0.020776960998773575,0.7933289408683777,-0.6139551997184753,-0.00615988252684474,0.0011201133020222187,0.7893160581588745,-0.6174065470695496,-0.020844701677560806,0.026415741071105003,0.7859242558479309,-0.6248045563697815,-0.026283564046025276,0.03241471201181412,0.7796651124954224,-0.6423417925834656,0.005422699730843306,-0.03451554849743843,0.765621542930603,-0.6267081499099731,-0.003914961125701666,-0.027197139337658882,0.7787694931030273,-0.5504120588302612,-0.04452206566929817,0.03439727798104286,0.8329953551292419,-0.4078291952610016,-0.019379086792469025,-0.017516642808914185,0.9126845002174377,-0.3504942059516907,0.025399629026651382,-0.08855392783880234,0.9320229887962341,-0.3880688548088074,0.045953668653964996,-0.11805944889783859,0.91288161277771,-0.4391513168811798,0.05182359740138054,-0.12543411552906036,0.8881029486656189,-0.46786779165267944,0.04852287098765373,-0.11828501522541046,0.8745021224021912,-0.49893301725387573,0.028548475354909897,-0.08183692395687103,0.8622955083847046,-0.4983568489551544,0.011074341833591461,-0.05058061331510544,0.865424394607544,-0.41002580523490906,-0.004981619771569967,-0.02317889779806137,0.9117657542228699,-0.3165610432624817,-0.00921404268592596,-0.012985517270863056,0.9484385251998901,-0.24431292712688446,-0.01774483732879162,0.0006966745713725686,0.9695338606834412,-0.19851547479629517,-0.025428976863622665,0.008875629864633083,0.9797276258468628,-0.14525115489959717,-0.036454178392887115,0.01615193858742714,0.9885910749435425,-0.08816859871149063,-0.04803062230348587,0.028145279735326767,0.9945487976074219,-0.2612956762313843,-0.08411092311143875,0.09475157409906387,0.9569075703620911,-0.39286720752716064,-0.1142687126994133,0.1779388040304184,0.8949501514434814,-0.5028936266899109,-0.12273302674293518,0.21344365179538727,0.8285387754440308,-0.5464087724685669,-0.13054777681827545,0.2295960783958435,0.7947832345962524,-0.5664191842079163,-0.134223073720932,0.23642241954803467,0.7779833078384399,-0.5816138386726379,-0.13578379154205322,0.23716787993907928,0.7661850452423096,-0.5871607661247253,-0.13650831580162048,0.23377032577991486,0.762862503528595,-0.5875034928321838,-0.13764186203479767,0.2304440438747406,0.7634067535400391,-0.5844797492027283,-0.13827399909496307,0.22517701983451843,0.7671760320663452,-0.5803704261779785,-0.13872948288917542,0.22006525099277496,0.7716836333274841,-0.5784754753112793,-0.1389051228761673,0.21611055731773376,0.7741883993148804,-0.5788371562957764,-0.13857918977737427,0.2125157117843628,0.7749712467193604,-0.5821752548217773,-0.13927483558654785,0.21171829104423523,0.7725605964660645,-0.5879184603691101,-0.1387280821800232,0.20948787033557892,0.7689091563224792,-0.5922698974609375,-0.13773664832115173,0.20701920986175537,0.7664124369621277,-0.594403862953186,-0.13570454716682434,0.20219285786151886,0.7664114236831665,-0.5922835469245911,-0.13149890303611755,0.19315747916698456,0.7711021304130554,-0.5854975581169128,-0.12529458105564117,0.1806563138961792,0.7802930474281311,-0.5747321844100952,-0.11814145743846893,0.16664299368858337,0.7924365401268005,-0.5608083605766296,-0.11019579321146011,0.15153466165065765,0.8064664602279663,-0.5439791679382324,-0.10419308394193649,0.14067800343036652,0.8206340074539185,-0.5266565680503845,-0.10069141536951065,0.13490460813045502,0.8332436084747314,-0.5115625262260437,-0.09835141152143478,0.13122834265232086,0.843451201915741,-0.4990711212158203,-0.09697304666042328,0.12875880300998688,0.8514372706413269,-0.49011072516441345,-0.09659924358129501,0.12750665843486786,0.8568559885025024,-0.4834824204444885,-0.09680656343698502,0.12814655900001526,0.8604950308799744,-0.47946056723594666,-0.09662806987762451,0.12812574207782745,0.8627655506134033,-0.47805026173591614,-0.09616774320602417,0.12743905186653137,0.8637007474899292,-0.4784194231033325,-0.09538109600543976,0.12640060484409332,0.8637362122535706,-0.4792160093784332,-0.09466312825679779,0.12532365322113037,0.8635304570198059,-0.4792865216732025,-0.09436304867267609,0.12440396100282669,0.8636571764945984,-0.47835108637809753,-0.09424415975809097,0.12423092126846313,0.8642135262489319,-0.476341187953949,-0.09432889521121979,0.12432844936847687,0.8652997016906738],"type":"quaternion"},{"name":"LeftFoot.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,0.9999998211860657,0.9999999403953552,0.9999997019767761,0.9999996423721313,0.9999997019767761],"type":"vector"},{"name":"LeftToeBase.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000013104170193400932,13.415114402770996,-0.0000017488330286141718,-0.00001033236549119465,13.415109634399414,0.000007092404757713666],"interpolation":2300,"type":"vector"},{"name":"LeftToeBase.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.30698150396347046,-0.005662649869918823,-0.0018292285967618227,0.9516968727111816,-0.3069814443588257,-0.0056626442819833755,-0.001829214277677238,0.9516969323158264],"interpolation":2300,"type":"quaternion"},{"name":"LeftToeBase.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1.0000001192092896,1.0000001192092896,0.9999999403953552,1,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"RightUpLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[-11.139495849609375,-4.020106792449951,0.42030227184295654,-11.139498710632324,-4.020097255706787,0.42030349373817444],"type":"vector"},{"name":"RightUpLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.7945910096168518,-0.15564197301864624,0.2397511750459671,0.5356492400169373,0.7922808527946472,-0.16506223380565643,0.2401474267244339,0.5360735058784485,0.7897309064865112,-0.1762809157371521,0.2404596209526062,0.5361243486404419,0.786651611328125,-0.19032612442970276,0.2410660684108734,0.5355767607688904,0.7822197675704956,-0.20799602568149567,0.24208691716194153,0.5350362658500671,0.7777964472770691,-0.22536306083202362,0.2436356246471405,0.5337470173835754,0.7751205563545227,-0.23926223814487457,0.245886892080307,0.5305482745170593,0.7738322019577026,-0.25026655197143555,0.24879470467567444,0.525976836681366,0.7767840623855591,-0.25589990615844727,0.25469356775283813,0.515997052192688,0.7823203206062317,-0.2562553584575653,0.26160600781440735,0.5038555860519409,0.7881037592887878,-0.2532600164413452,0.27123576402664185,0.491103857755661,0.7924630045890808,-0.2514120638370514,0.28339582681655884,0.4779970049858093,0.7982445955276489,-0.2512434124946594,0.3004637062549591,0.4576066732406616,0.8174453973770142,-0.2467215359210968,0.31957414746284485,0.4108332395553589,0.8403254151344299,-0.24098408222198486,0.3394266963005066,0.3472311496734619,0.8509942293167114,-0.25042852759361267,0.36411529779434204,0.2837508022785187,0.857867419719696,-0.24560223519802094,0.3783699572086334,0.24612851440906525,0.8614434599876404,-0.23306670784950256,0.38837525248527527,0.22969503700733185,0.8653579354286194,-0.21551206707954407,0.40232110023498535,0.20699739456176758,0.8679471015930176,-0.18455706536769867,0.41840997338294983,0.19375145435333252,0.8666329383850098,-0.15905830264091492,0.4322722256183624,0.19180342555046082,0.8610000610351562,-0.13979870080947876,0.44608640670776367,0.2003551721572876,0.8554826378822327,-0.12014445662498474,0.4568183422088623,0.21220703423023224,0.8493272066116333,-0.10665871948003769,0.46716612577438354,0.22141149640083313,0.8419507145881653,-0.1040271446108818,0.4771827161312103,0.22933395206928253,0.8331970572471619,-0.10715827345848083,0.48637455701828003,0.24029086530208588,0.8199935555458069,-0.11828240007162094,0.4953797459602356,0.26118722558021545,0.7913872003555298,-0.11463598161935806,0.49151304364204407,0.3449345827102661,0.7612180113792419,-0.10636914521455765,0.48326459527015686,0.4191517233848572,0.7304186820983887,-0.10205495357513428,0.47334906458854675,0.48167845606803894,0.705899178981781,-0.09647808969020844,0.46090537309646606,0.5291168689727783,0.6840511560440063,-0.08802290260791779,0.44325679540634155,0.5725813508033752,0.6696042418479919,-0.08632058650255203,0.4251221716403961,0.6028681993484497,0.6629380583763123,-0.09523827582597733,0.41120514273643494,0.6183472871780396,0.6641612648963928,-0.11015941202640533,0.39957016706466675,0.6221724152565002,0.6830000281333923,-0.12885130941867828,0.39654847979545593,0.5997146964073181,0.703423023223877,-0.14777086675167084,0.398723304271698,0.5695434212684631,0.7296415567398071,-0.1601184457540512,0.4102705121040344,0.5231285691261292,0.7130141258239746,-0.19860011339187622,0.4038017690181732,0.5376924276351929,0.7185592651367188,-0.20767730474472046,0.3939174711704254,0.5342020988464355,0.731184184551239,-0.2183707058429718,0.4014710783958435,0.5064632296562195,0.7254332304000854,-0.24333198368549347,0.4048307538032532,0.5006478428840637,0.724421501159668,-0.2595103979110718,0.4070671796798706,0.49210190773010254,0.720202624797821,-0.27387556433677673,0.40627118945121765,0.4911661446094513,0.7096368670463562,-0.2742919325828552,0.4281027317047119,0.48775771260261536,0.6362408399581909,-0.2622024416923523,0.5395570397377014,0.4851037263870239,0.5827250480651855,-0.27805331349372864,0.603488564491272,0.4678882360458374,0.571145236492157,-0.28265199065208435,0.6393999457359314,0.4301961660385132,0.5836359858512878,-0.27511560916900635,0.6567244529724121,0.39037609100341797,0.5994796752929688,-0.2452041208744049,0.6619088649749756,0.3773270845413208,0.6296259760856628,-0.20650629699230194,0.6577286124229431,0.3582170903682709,0.6641780138015747,-0.17993517220020294,0.6374635100364685,0.3465995192527771,0.6887075901031494,-0.1673150658607483,0.6149470806121826,0.3457277715206146,0.7139522433280945,-0.1593681424856186,0.5950000882148743,0.3329397737979889,0.7413343787193298,-0.1528071165084839,0.5777430534362793,0.30542802810668945,0.7631775736808777,-0.14777995645999908,0.5624188780784607,0.2817910313606262,0.7765035629272461,-0.14758752286434174,0.5541999936103821,0.26100295782089233,0.7793484926223755,-0.1486721783876419,0.5551472902297974,0.2496478408575058,0.7796472311019897,-0.15282955765724182,0.5518106818199158,0.25357112288475037,0.7967759370803833,-0.1637403517961502,0.5129771828651428,0.27421092987060547,0.8137522339820862,-0.17736992239952087,0.457027792930603,0.3122064471244812,0.8277938365936279,-0.19567406177520752,0.3833901584148407,0.359834760427475,0.8290220499038696,-0.22242110967636108,0.29020920395851135,0.42311936616897583,0.8109002113342285,-0.24934685230255127,0.1834903359413147,0.4965866804122925,0.7770814299583435,-0.26061633229255676,0.07909657806158066,0.5674216151237488,0.7440469264984131,-0.2486446499824524,-0.00012356770457699895,0.620137095451355,0.7347614765167236,-0.22515755891799927,-0.031971074640750885,0.6390677094459534,0.7396206855773926,-0.1716165542602539,-0.028651459142565727,0.6501446962356567,0.7530882954597473,-0.11525021493434906,-0.006710400804877281,0.6477116942405701,0.7648419141769409,-0.08777256309986115,0.017040371894836426,0.6379830837249756,0.7645649313926697,-0.09954807162284851,0.026121040806174278,0.6362769603729248,0.7546735405921936,-0.09130769222974777,0.013593935407698154,0.6495736837387085,0.7503941059112549,-0.08333918452262878,-0.01907164976000786,0.6554384231567383,0.7557642459869385,-0.09894031286239624,-0.0452813059091568,0.6457405686378479,0.765102744102478,-0.11992692202329636,-0.055883992463350296,0.6301684975624084,0.7766522169113159,-0.13085360825061798,-0.06627313792705536,0.6126145124435425,0.7881523370742798,-0.14213691651821136,-0.07027056068181992,0.5947058200836182,0.7936703562736511,-0.15827684104442596,-0.06874752789735794,0.5833606123924255,0.7975103259086609,-0.16585864126682281,-0.06252847611904144,0.5766787528991699,0.8018917441368103,-0.16679058969020844,-0.04776255041360855,0.5717248320579529,0.8090422749519348,-0.16721965372562408,-0.025196580216288567,0.5628973245620728,0.8150953054428101,-0.16795027256011963,0.003679292742162943,0.5544356107711792,0.8194032907485962,-0.16763834655284882,0.03611382097005844,0.5469655990600586,0.8219674825668335,-0.16834698617458344,0.06772622466087341,0.5398536920547485,0.826718807220459,-0.1695779412984848,0.09197939932346344,0.5285065174102783,0.8303495645523071,-0.17111119627952576,0.11335009336471558,0.5180659890174866,0.8316829204559326,-0.17188778519630432,0.13129250705242157,0.5113906860351562,0.8297425508499146,-0.17369288206100464,0.1465909332036972,0.5097736716270447,0.8253424763679504,-0.17356085777282715,0.15878619253635406,0.5132965445518494,0.8220754861831665,-0.16962873935699463,0.1695370227098465,0.5164060592651367,0.8201828598976135,-0.16557921469211578,0.18087846040725708,0.5168815851211548,0.8178566098213196,-0.16427606344223022,0.1900157481431961,0.5177044868469238,0.814576268196106,-0.16437307000160217,0.19792480766773224,0.5198777318000793,0.810778021812439,-0.16488520801067352,0.206033393740654,0.5224959850311279,0.8069088459014893,-0.16470083594322205,0.21537624299526215,0.5247712135314941,0.8031290173530579,-0.16319768130779266,0.22420258820056915,0.5273362994194031,0.7994888424873352,-0.1605205237865448,0.23177097737789154,0.5304083228111267,0.7962179780006409,-0.15777212381362915,0.23776228725910187,0.5334922671318054,0.7945879697799683,-0.15565364062786102,0.23975390195846558,0.5356491208076477],"type":"quaternion"},{"name":"RightUpLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1,1.0000001192092896,1.000000238418579,1,1.000000238418579],"interpolation":2300,"type":"vector"},{"name":"RightLeg.position","times":[0.03333333507180214,3.299999952316284],"values":[0.0000012686648460658034,25.5271053314209,-0.0000014698089216835797,0.000003111048272330663,25.52710723876953,0.0000019298095139674842],"type":"vector"},{"name":"RightLeg.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.4096694588661194,-0.23398460447788239,-0.034918393939733505,0.8810237646102905,0.4138280153274536,-0.23414090275764465,-0.036396559327840805,0.8789765238761902,0.41353315114974976,-0.23461978137493134,-0.041190311312675476,0.8787760138511658,0.4105037748813629,-0.23574748635292053,-0.04867757484316826,0.8795114159584045,0.40494877099990845,-0.23881083726882935,-0.058852750808000565,0.8806374669075012,0.3938863277435303,-0.24410484731197357,-0.07120966166257858,0.8832868337631226,0.37577858567237854,-0.24958132207393646,-0.08325988799333572,0.8885760307312012,0.35160768032073975,-0.2532375752925873,-0.0928267389535904,0.8964518904685974,0.31967654824256897,-0.2539076805114746,-0.09764138609170914,0.9076365232467651,0.28940051794052124,-0.2507016658782959,-0.09544730931520462,0.918850302696228,0.2670009434223175,-0.2428446114063263,-0.08958633244037628,0.9282840490341187,0.25815799832344055,-0.2289416491985321,-0.0817672610282898,0.9350156784057617,0.2519979178905487,-0.20966173708438873,-0.07216429710388184,0.9419826865196228,0.2225894331932068,-0.1726228892803192,-0.05581207573413849,0.9578832387924194,0.18534117937088013,-0.12816771864891052,-0.04196644946932793,0.9733758568763733,0.16553132236003876,-0.09915288537740707,-0.035255905240774155,0.9805738925933838,0.19259822368621826,-0.0491512268781662,-0.015835154801607132,0.9799180030822754,0.22673305869102478,0.0066242222674191,0.010048845782876015,0.9738826155662537,0.21877261996269226,0.042774733155965805,0.020397093147039413,0.9746243953704834,0.18804636597633362,0.053630515933036804,0.020660484209656715,0.9804772138595581,0.18704304099082947,0.06857915222644806,0.026278439909219742,0.979602575302124,0.21359862387180328,0.05990627408027649,0.03424578905105591,0.9744814038276672,0.22821977734565735,0.04608427360653877,0.036670051515102386,0.9718268513679504,0.22977446019649506,0.028753675520420074,0.03450005501508713,0.9722071290016174,0.22861792147159576,-0.012374541722238064,0.022212589159607887,0.9731841087341309,0.21801131963729858,-0.06426247209310532,0.00432290555909276,0.9738186597824097,0.19918034970760345,-0.1193590983748436,-0.01433523464947939,0.9725611209869385,0.23482583463191986,-0.15877465903759003,-0.017838407307863235,0.9588165879249573,0.2610985338687897,-0.19467328488826752,-0.022243868559598923,0.945216953754425,0.27566415071487427,-0.2189829796552658,-0.024144262075424194,0.935667097568512,0.265352725982666,-0.23106396198272705,-0.023142045363783836,0.9357680678367615,0.2503806948661804,-0.22888465225696564,-0.017054002732038498,0.9405480027198792,0.22558049857616425,-0.2196117341518402,-0.009066597558557987,0.9491059184074402,0.20199589431285858,-0.24336020648479462,-0.009308896958827972,0.9486236572265625,0.19332320988178253,-0.2689126431941986,-0.008290212601423264,0.943527102470398,0.16812872886657715,-0.27653905749320984,-0.00687422463670373,0.9461562633514404,0.11299537867307663,-0.285631388425827,-0.0038717547431588173,0.9516468644142151,0.0011728924000635743,-0.29279664158821106,0.00906140636652708,0.956131100654602,0.09340910613536835,-0.2457319051027298,0.005045854952186346,0.9648135304450989,0.18874947726726532,-0.18214161694049835,0.019263064488768578,0.9647938013076782,0.19705133140087128,-0.13355253636837006,0.012697595171630383,0.9711710810661316,0.2234124094247818,-0.10635385662317276,0.0032057210337370634,0.968899130821228,0.2524747848510742,-0.08635278791189194,-0.004792628809809685,0.9637306332588196,0.28680530190467834,-0.06538955122232437,-0.0074294935911893845,0.9557257890701294,0.33344942331314087,0.027237361297011375,0.02394843101501465,0.9420701265335083,0.3920702338218689,0.11087284237146378,0.06416820734739304,0.9109723567962646,0.4486273527145386,0.1141161248087883,0.05619059503078461,0.8846206665039062,0.46207213401794434,0.09151677042245865,0.016742829233407974,0.8819488286972046,0.46007904410362244,0.08029025048017502,-0.006072136107832193,0.8842194080352783,0.4643916189670563,0.07545391470193863,-0.013255927711725235,0.8823103308677673,0.45182469487190247,0.0648285299539566,-0.020795725286006927,0.8895050287246704,0.4377061128616333,0.05409598350524902,-0.025840269401669502,0.8971172571182251,0.4341623783111572,0.04345955699682236,-0.025021713227033615,0.8994377255439758,0.4261626601219177,0.028063734993338585,-0.02427304908633232,0.9038853049278259,0.3970385491847992,0.00717628188431263,-0.02661912888288498,0.9173877835273743,0.36259081959724426,-0.017046719789505005,-0.02609666809439659,0.9314270615577698,0.3284042477607727,-0.04397812485694885,-0.02274392731487751,0.9432387351989746,0.30867800116539,-0.08357632160186768,-0.018575316295027733,0.9473055601119995,0.30473124980926514,-0.13162951171398163,-0.010929703712463379,0.943235456943512,0.33214065432548523,-0.1469775140285492,0.01783134415745735,0.9315375685691833,0.3850206732749939,-0.16099365055561066,0.05875715613365173,0.9068560004234314,0.4414496123790741,-0.18211326003074646,0.09715874493122101,0.873222291469574,0.4968309700489044,-0.2182791829109192,0.11870379745960236,0.8315182328224182,0.5455375909805298,-0.26461389660835266,0.11822735518217087,0.7863780856132507,0.5793569087982178,-0.3144250214099884,0.09724049270153046,0.7456719875335693,0.5922074317932129,-0.3379666805267334,0.08638106286525726,0.7263657450675964,0.5753030180931091,-0.3280884325504303,0.09424035251140594,0.7433056235313416,0.5555965900421143,-0.3188644051551819,0.09763913601636887,0.7616459131240845,0.5506225824356079,-0.2744770050048828,0.13245616853237152,0.7771309614181519,0.5383791327476501,-0.2256544679403305,0.16921232640743256,0.7941002249717712,0.5321924686431885,-0.21818962693214417,0.1682228446006775,0.8005407452583313,0.5211119651794434,-0.2434586137533188,0.13699202239513397,0.8064759373664856,0.49546748399734497,-0.26059409976005554,0.10948505252599716,0.8213499784469604,0.46481767296791077,-0.26322323083877563,0.09356196224689484,0.8401810526847839,0.433088481426239,-0.26450273394584656,0.0779329389333725,0.8581370711326599,0.40461766719818115,-0.2650815546512604,0.06350184231996536,0.8729168772697449,0.38580816984176636,-0.2655397653579712,0.05171384662389755,0.8820240497589111,0.37257346510887146,-0.27359721064567566,0.03804268315434456,0.8859381079673767,0.3649304211139679,-0.2789842486381531,0.027123529464006424,0.8878389000892639,0.3614620268344879,-0.28052854537963867,0.018934352323412895,0.8889828324317932,0.3588773310184479,-0.27885594964027405,0.012548442929983139,0.890667736530304,0.35505542159080505,-0.27465561032295227,0.007213139906525612,0.8935590982437134,0.34368252754211426,-0.2683255970478058,0.0013588282745331526,0.8999343514442444,0.3277645707130432,-0.2596941590309143,-0.004549746867269278,0.9083549380302429,0.30946484208106995,-0.24549232423305511,-0.009113832376897335,0.9186304807662964,0.2934178113937378,-0.2342206984758377,-0.014676983468234539,0.926731526851654,0.284512996673584,-0.22845055162906647,-0.020349353551864624,0.9308322072029114,0.2903112769126892,-0.22497521340847015,-0.023180849850177765,0.9298216104507446,0.31072354316711426,-0.221248596906662,-0.02215934358537197,0.9241260290145874,0.3297467827796936,-0.2170688658952713,-0.02062850259244442,0.9185437560081482,0.34570348262786865,-0.21503682434558868,-0.020266801118850708,0.9131470322608948,0.360927015542984,-0.21426570415496826,-0.020297320559620857,0.9074193835258484,0.37431174516677856,-0.21415650844573975,-0.02075890079140663,0.9019960165023804,0.38523808121681213,-0.2158772200345993,-0.02237752638757229,0.8969325423240662,0.39353257417678833,-0.21944692730903625,-0.02513982728123665,0.8923805952072144,0.3997032642364502,-0.22375601530075073,-0.028292372822761536,0.8884650468826294,0.40437641739845276,-0.22812074422836304,-0.03130055218935013,0.8851332664489746,0.4078478217124939,-0.23233374953269958,-0.03392145782709122,0.8823437690734863,0.4096679985523224,-0.23398244380950928,-0.0349411778151989,0.8810241222381592],"type":"quaternion"},{"name":"RightLeg.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,0.9999998807907104,0.9999998211860657,0.9999999403953552,0.9999997019767761,0.9999999403953552],"type":"vector"},{"name":"RightFoot.position","times":[0.03333333507180214,3.299999952316284],"values":[6.810330432927003e-8,23.10919952392578,-7.104912924660312e-8,-0.000011113085747638252,23.10919761657715,0.0000014820886917732423],"type":"vector"},{"name":"RightFoot.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[-0.6448000073432922,0.023697732016444206,0.027224285528063774,0.7634986639022827,-0.646422266960144,0.02374185249209404,0.026072854176163673,0.7621645331382751,-0.645522952079773,0.024406690150499344,0.02522134780883789,0.7629340887069702,-0.642866849899292,0.025223383679986,0.02423723042011261,0.7651787996292114,-0.638513445854187,0.026199644431471825,0.023080751299858093,0.7688182592391968,-0.6312182545661926,0.027477998286485672,0.02151372656226158,0.7748197913169861,-0.6207640171051025,0.029286116361618042,0.01895730197429657,0.7832209467887878,-0.6088584661483765,0.03297092393040657,0.013358861207962036,0.7924808263778687,-0.5959182381629944,0.03892342001199722,0.004020525608211756,0.8020911812782288,-0.5860850811004639,0.046561501920223236,-0.008161505684256554,0.8088694214820862,-0.5822674036026001,0.05464402586221695,-0.021668847650289536,0.8108693957328796,-0.5872372388839722,0.0638963133096695,-0.03761689364910126,0.8060116171836853,-0.5959841012954712,0.07473566383123398,-0.05638815462589264,0.797519862651825,-0.5981584787368774,0.0895024985074997,-0.08145685493946075,0.7921871542930603,-0.6031227111816406,0.09396738559007645,-0.08454948663711548,0.7875688076019287,-0.5877174139022827,0.09746548533439636,-0.08873900026082993,0.7982568740844727,-0.566956102848053,0.08727964758872986,-0.06860671937465668,0.8162329196929932,-0.5238141417503357,0.06610281765460968,-0.029263339936733246,0.8487595915794373,-0.45072951912879944,0.04119759798049927,0.023343877866864204,0.8914037942886353,-0.34222474694252014,-0.0021864373702555895,0.09899374097585678,0.9343863129615784,-0.2648822069168091,-0.029428796842694283,0.14504845440387726,0.9528548121452332,-0.22424469888210297,-0.03655640408396721,0.1553851068019867,0.961370587348938,-0.22335104644298553,-0.03975127637386322,0.16010688245296478,0.9606768488883972,-0.27205362915992737,-0.024673353880643845,0.13626301288604736,0.9522659778594971,-0.34957176446914673,-0.006883363239467144,0.1028527319431305,0.9312214851379395,-0.4310850501060486,0.007183481007814407,0.06963662803173065,0.8995915055274963,-0.49528247117996216,0.015475031919777393,0.049137357622385025,0.8672031760215759,-0.5429609417915344,0.0017497125081717968,0.0693620815873146,0.8368866443634033,-0.570616602897644,-0.009683080017566681,0.08672706037759781,0.8165668249130249,-0.5816878080368042,-0.009622232057154179,0.08607536554336548,0.8087878227233887,-0.5748071670532227,-0.000326144159771502,0.07118453085422516,0.8151867389678955,-0.5690866708755493,0.013417193666100502,0.049181122332811356,0.8206957578659058,-0.5718938112258911,0.03461406007409096,0.013911468908190727,0.8194790482521057,-0.5812802910804749,0.04523090273141861,-0.005748148076236248,0.8124250173568726,-0.5891209244728088,0.05475662276148796,-0.02375527285039425,0.8058373928070068,-0.5863138437271118,0.060446515679359436,-0.03348690643906593,0.807131290435791,-0.48089325428009033,0.06866426020860672,-0.050322275608778,0.872636616230011,-0.284233421087265,0.0985722541809082,-0.11099830269813538,0.9471928477287292,-0.2813924551010132,0.1121472418308258,-0.12364257872104645,0.9449623227119446,-0.34836435317993164,0.11552674323320389,-0.11702101677656174,0.9228228330612183,-0.3674297034740448,0.11215567588806152,-0.11085726320743561,0.9165845513343811,-0.39160650968551636,0.10685832798480988,-0.10242483019828796,0.9081491231918335,-0.41614633798599243,0.1024300828576088,-0.09603278338909149,0.8983919024467468,-0.44276171922683716,0.09421786665916443,-0.08488905429840088,0.8876254558563232,-0.4889267086982727,0.09355886280536652,-0.08459510654211044,0.8631576299667358,-0.5152977705001831,0.06361374258995056,-0.03719596937298775,0.8538371920585632,-0.5326524972915649,0.01240635197609663,0.05765330791473389,0.8442769050598145,-0.531301736831665,0.015621241182088852,0.04927040636539459,0.845604419708252,-0.550456166267395,0.06260720640420914,-0.04213789477944374,0.8314462304115295,-0.5771128535270691,0.06815849244594574,-0.04945098236203194,0.8123113512992859,-0.5977869033813477,0.06992761790752411,-0.049953021109104156,0.7970355749130249,-0.6136112809181213,0.06747036427259445,-0.045428838580846786,0.7854076623916626,-0.630078911781311,0.06176996976137161,-0.04006021097302437,0.7730331420898438,-0.6417711973190308,0.05912933498620987,-0.03716399148106575,0.763709545135498,-0.6423178315162659,0.061385784298181534,-0.03932889178395271,0.7629631757736206,-0.641151487827301,0.06626126170158386,-0.04626350849866867,0.7631474137306213,-0.6398099660873413,0.06950819492340088,-0.050502851605415344,0.7637154459953308,-0.6433378458023071,0.06605244427919388,-0.043473176658153534,0.7614877820014954,-0.6505609750747681,0.06170796602964401,-0.03621624782681465,0.7560760974884033,-0.6700002551078796,0.061043400317430496,-0.033694565296173096,0.7390791773796082,-0.6873767375946045,0.06913799792528152,-0.048074327409267426,0.7214027643203735,-0.7041199803352356,0.07581938803195953,-0.0600222647190094,0.7034655809402466,-0.7126683592796326,0.07676330953836441,-0.06197906658053398,0.6945285201072693,-0.7113291621208191,0.07088803499937057,-0.052048005163669586,0.6973354816436768,-0.7032782435417175,0.05402808636426926,-0.023970622569322586,0.7084532976150513,-0.6939498782157898,0.042839303612709045,-0.006277840584516525,0.7187203764915466,-0.6831684112548828,0.047129154205322266,-0.014246555045247078,0.7285992503166199,-0.6600649952888489,0.04579336196184158,-0.009231917560100555,0.7497546076774597,-0.6461184024810791,0.05324730649590492,-0.021505361422896385,0.7610738277435303,-0.6357979774475098,0.06909618526697159,-0.045263905078172684,0.7674228549003601,-0.6332192420959473,0.07319263368844986,-0.051185257732868195,0.7688018679618835,-0.6174706220626831,0.0622650645673275,-0.0320139154791832,0.783471941947937,-0.6031489372253418,0.05660269036889076,-0.02250196784734726,0.7952994108200073,-0.6009206175804138,0.06124996766448021,-0.030768508091568947,0.7963643670082092,-0.6046938896179199,0.06571934372186661,-0.03918154165148735,0.7927742600440979,-0.6073042154312134,0.06790424138307571,-0.04472317546606064,0.7902976870536804,-0.6144563555717468,0.06776830554008484,-0.047051895409822464,0.7846254110336304,-0.6112257242202759,0.0747104063630104,-0.06460444629192352,0.7852692604064941,-0.6096065640449524,0.07642442733049393,-0.06968826055526733,0.785927951335907,-0.610846996307373,0.07362645864486694,-0.06496939063072205,0.7856361269950867,-0.6134613156318665,0.0704280287027359,-0.05928831547498703,0.784340500831604,-0.6156280636787415,0.06716211140155792,-0.053021859377622604,0.7833772897720337,-0.6141272783279419,0.0638793334364891,-0.046041958034038544,0.7852689623832703,-0.6099584102630615,0.06094023585319519,-0.039357565343379974,0.7891058325767517,-0.6044393181800842,0.059019848704338074,-0.03422911465167999,0.7937242388725281,-0.5980629324913025,0.05688071623444557,-0.02873656339943409,0.798911452293396,-0.5928856730461121,0.05413047596812248,-0.022479381412267685,0.8031507730484009,-0.5940665602684021,0.05122580751776695,-0.01665271818637848,0.802610456943512,-0.6024829149246216,0.048031099140644073,-0.010934880934655666,0.7966101765632629,-0.6112561821937561,0.04459068179130554,-0.005268983077257872,0.7901580929756165,-0.6192156076431274,0.04102122411131859,0.000028965287128812633,0.7841487526893616,-0.6264355182647705,0.03803679719567299,0.00446736766025424,0.7785318493843079,-0.6328477263450623,0.035711441189050674,0.008033844642341137,0.7734105587005615,-0.6381815671920776,0.0335131511092186,0.011365203186869621,0.7690721750259399,-0.6421009302139282,0.031276509165763855,0.014825324527919292,0.7658383250236511,-0.6444714069366455,0.029008716344833374,0.018370559439063072,0.7638571262359619,-0.6454939246177673,0.02686411514878273,0.021981514990329742,0.7629762291908264,-0.6454792022705078,0.02483261190354824,0.02534717135131359,0.7629531621932983,-0.6447962522506714,0.02369064837694168,0.02723548747599125,0.7635016441345215],"type":"quaternion"},{"name":"RightFoot.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,1.000000238418579,1.0000001192092896,0.9999998807907104,1,0.9999998807907104],"type":"vector"},{"name":"RightToeBase.position","times":[0.03333333507180214,3.299999952316284],"values":[-8.431593414570671e-7,13.547396659851074,0.0000012455053592930199,-0.000003919287792086834,13.547396659851074,0.000004012775661976775],"interpolation":2300,"type":"vector"},{"name":"RightToeBase.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.30788561701774597,0.014501922763884068,0.004691822919994593,0.9513012766838074,-0.30788561701774597,0.014501898549497128,0.004691829439252615,0.9513012766838074],"interpolation":2300,"type":"quaternion"},{"name":"RightToeBase.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998807907104,0.9999998807907104,1.0000001192092896,0.9999998807907104,0.9999998807907104,1],"interpolation":2300,"type":"vector"},{"name":"Spine02.position","times":[0.03333333507180214,3.299999952316284],"values":[2.834001064300537,7.754298686981201,-6.13960075378418,2.833998203277588,7.754297733306885,-6.139596939086914],"type":"vector"},{"name":"Spine02.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.33200064301490784,-0.09571805596351624,-0.11373622715473175,0.9314922094345093,-0.3320006728172302,-0.09571804106235504,-0.11373621970415115,0.9314922094345093],"type":"quaternion"},{"name":"Spine02.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1,0.9999998211860657,1,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine01.position","times":[0.03333333507180214,3.299999952316284],"values":[-7.81379412728711e-7,10.28860092163086,1.823066497763648e-7,0.000011346302926540375,10.288602828979492,9.832444902713178e-7],"type":"vector"},{"name":"Spine01.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-1.433363561176293e-8,-7.275956726005006e-9,1.3722456060349941e-8,1,-1.5032128430902958e-8,-1.2107192581822801e-8,1.3780663721263409e-8,1],"type":"quaternion"},{"name":"Spine01.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999998807907104,0.9999999403953552,1,0.9999999403953552,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"Spine.position","times":[0.03333333507180214,3.299999952316284],"values":[5.238695166553953e-7,10.28860855102539,-2.0749989460000506e-7,0.00000811448353488231,10.288602828979492,-0.000002465678790031234],"type":"vector"},{"name":"Spine.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.013350076042115688,-0.015474152751266956,-0.005283687263727188,0.9997771978378296,0.013399491086602211,-0.016187507659196854,-0.005753212608397007,0.9997626543045044,0.013567321002483368,-0.019180618226528168,-0.00789947621524334,0.9996927976608276,0.013787796720862389,-0.022906901314854622,-0.010847726836800575,0.9995837211608887,0.01402139849960804,-0.026855602860450745,-0.013891755603253841,0.9994444251060486,0.014238068833947182,-0.030905727297067642,-0.016488075256347656,0.9992848634719849,0.014419282786548138,-0.0349428653717041,-0.018067525699734688,0.9991219639778137,0.014532404020428658,-0.03885812684893608,-0.01888762228190899,0.9989604949951172,0.014555282890796661,-0.04177653416991234,-0.019231995567679405,0.9988358616828918,0.01440340280532837,-0.0449291355907917,-0.018389998003840446,0.9987170100212097,0.014066523872315884,-0.04826049134135246,-0.0166009534150362,0.9985978007316589,0.01366021204739809,-0.05106758698821068,-0.013236786238849163,0.9985140562057495,0.013270714320242405,-0.051199592649936676,-0.008405216038227081,0.9985648989677429,0.012917635031044483,-0.048825703561306,-0.0025282418355345726,0.9987205862998962,0.010864051058888435,-0.04295055940747261,0.0022716692183166742,0.9990155696868896,0.00755024841055274,-0.03539011627435684,0.003909130115061998,0.9993374347686768,0.004594918340444565,-0.021499916911125183,0.0019768187776207924,0.999756395816803,0.002087177010253072,-0.005090721882879734,-0.003596099792048335,0.9999784231185913,-0.0016171439783647656,0.009233392775058746,-0.008933397941291332,0.999916136264801,-0.005159804131835699,0.02650778368115425,-0.013574683107435703,0.9995431303977966,-0.008392649702727795,0.03165493160486221,-0.01897447369992733,0.9992835521697998,-0.012992572970688343,0.029499556869268417,-0.02311350777745247,0.9992130994796753,-0.016355441883206367,0.025275317952036858,-0.02494102157652378,0.9992355108261108,-0.018229102715849876,0.02094901353120804,-0.023396559059619904,0.9993405342102051,-0.019136235117912292,0.015204522758722305,-0.02204544097185135,0.9994581937789917,-0.019934983924031258,0.007702709641307592,-0.01945365220308304,0.9995823502540588,-0.019467074424028397,-0.00014076693332754076,-0.01647617295384407,0.9996747374534607,-0.018425816670060158,-0.006300405133515596,-0.011760856956243515,0.9997411966323853,-0.017198167741298676,-0.012665197253227234,-0.006947173736989498,0.9997477531433105,-0.015901602804660797,-0.01837341859936714,-0.0034719896502792835,0.9996986985206604,-0.014142844825983047,-0.023891348391771317,-0.0006890121148899198,0.9996142983436584,-0.012562968768179417,-0.02689317613840103,0.0027516118716448545,0.9995555877685547,-0.011786747723817825,-0.030182339251041412,0.006041410844773054,0.9994567036628723,-0.011123184114694595,-0.03395310416817665,0.009244522079825401,0.9993187785148621,-0.009389366954565048,-0.0364859476685524,0.011539938859641552,0.9992234110832214,-0.007078294642269611,-0.038588229566812515,0.012616805732250214,0.9991504549980164,-0.004088069777935743,-0.03856327012181282,0.012597582302987576,0.9991683959960938,-0.000757789530325681,-0.036966241896152496,0.008519337512552738,0.9992799162864685,0.004630750976502895,-0.03530120104551315,0.004110177978873253,0.9993575811386108,0.01125674694776535,-0.03378390148282051,0.015852972865104675,0.9992400407791138,0.018141617998480797,-0.031890831887722015,0.01362049113959074,0.9992339015007019,0.02142404392361641,-0.026602035388350487,0.008051946759223938,0.9993841052055359,0.0234361682087183,-0.01634317636489868,0.005670040845870972,0.999575674533844,0.023139791563153267,0.008856682106852531,0.008826014585793018,0.9996540546417236,0.021250862628221512,0.03496013209223747,0.01297827810049057,0.9990784525871277,0.018905676901340485,0.043653398752212524,0.01414534356445074,0.9987676739692688,0.013627621345221996,0.052493978291749954,0.011704428121447563,0.9984596371650696,0.007249524816870689,0.05689987540245056,0.006899089086800814,0.998329758644104,0.0018856944516301155,0.055192988365888596,-0.004143710248172283,0.9984652996063232,-0.0030178031884133816,0.05151524022221565,-0.009396571666002274,0.9986234307289124,-0.008390514180064201,0.048151273280382156,-0.013070410117506981,0.9987192749977112,-0.014036505483090878,0.04444357752799988,-0.014781123958528042,0.9988039135932922,-0.01976902410387993,0.04082803800702095,-0.015984194353222847,0.998842716217041,-0.022748814895749092,0.03855947032570839,-0.011974909342825413,0.9989255666732788,-0.02348916418850422,0.03871234878897667,-0.0071410443633794785,0.9989487528800964,-0.024912593886256218,0.03830878064036369,-0.004761740565299988,0.9989440441131592,-0.02568800188601017,0.03758830949664116,-0.0031192167662084103,0.9989582300186157,-0.027117766439914703,0.03631240129470825,-0.001009263563901186,0.9989719986915588,-0.028821397572755814,0.03549392893910408,0.004548145458102226,0.9989438652992249,-0.02820499613881111,0.03418046608567238,0.012249167077243328,0.998942494392395,-0.02548891305923462,0.03228843957185745,0.021171564236283302,0.9989292025566101,-0.019734077155590057,0.030610661953687668,0.0304980780929327,0.9988710880279541,-0.010360565967857838,0.029059436172246933,0.035164736211299896,0.9989052414894104,-0.0002982175210490823,0.028278376907110214,0.032516445964574814,0.9990710616111755,0.010110422037541866,0.02685547061264515,0.025129634886980057,0.9992722868919373,0.01509830728173256,0.022895488888025284,0.015980446711182594,0.9994961023330688,0.01656980998814106,0.015127225778996944,0.010816437192261219,0.9996897578239441,0.019558273255825043,0.0008528938633389771,0.004446968901902437,0.9997984766960144,0.02497800998389721,-0.01218659058213234,0.005385716445744038,0.9995992183685303,0.024164710193872452,-0.02598157525062561,0.005867569241672754,0.9993530511856079,0.02133774943649769,-0.04879177361726761,-0.0013352630194276571,0.9985801577568054,0.01727430708706379,-0.061001915484666824,-0.0023863245733082294,0.997985303401947,0.01253496017307043,-0.06391192227602005,-0.004662469495087862,0.9978659152984619,0.00863676518201828,-0.06410585343837738,-0.0033112289384007454,0.9979003071784973,0.005119399167597294,-0.06399275362491608,-0.0018271079752594233,0.9979355931282043,0.002152608009055257,-0.06376709043979645,-0.0020372027065604925,0.9979603886604309,-0.00020835590839851648,-0.06338204443454742,-0.002437794115394354,0.9979863166809082,-0.0010888249380514026,-0.06280327588319778,-0.0025749870110303164,0.9980220198631287,-0.001649749232456088,-0.06211161985993385,-0.0024985321797430515,0.9980646967887878,-0.001228221459314227,-0.06135503947734833,-0.002441860968247056,0.9981122612953186,-0.00008761106437304989,-0.05999799072742462,-0.0024239569902420044,0.9981955885887146,0.00037252873880788684,-0.05824742093682289,-0.002508320612832904,0.9982989430427551,-0.0002249154931632802,-0.055598095059394836,-0.0026989630423486233,0.9984495639801025,-0.0013825974892824888,-0.05210386589169502,-0.0028040038887411356,0.998636782169342,-0.00023202691227197647,-0.0481325164437294,-0.002934242831543088,0.9988366961479187,0.0031412746757268906,-0.04210294410586357,-0.004154654685407877,0.9990997314453125,0.0072604017332196236,-0.03426215052604675,-0.005554223898798227,0.9993711113929749,0.010389759205281734,-0.02785121649503708,-0.006951932795345783,0.9995338916778564,0.012709359638392925,-0.02332303114235401,-0.008141142316162586,0.9996140599250793,0.014123812317848206,-0.020630354061722755,-0.007613359019160271,0.999658465385437,0.015244334936141968,-0.0190525371581316,-0.007098513655364513,0.9996770620346069,0.015493995510041714,-0.018195366486907005,-0.006625069305300713,0.999692440032959,0.015305416658520699,-0.01737930439412594,-0.006231674924492836,0.9997124075889587,0.015022031031548977,-0.016617337241768837,-0.005953371524810791,0.9997313618659973,0.014718803577125072,-0.016061220318078995,-0.005827005486935377,0.9997456669807434,0.014371485449373722,-0.015749378129839897,-0.005808917805552483,0.9997557997703552,0.013916184194386005,-0.015592420473694801,-0.005827466957271099,0.999764621257782,0.013507269322872162,-0.015549437142908573,-0.005838323850184679,0.9997708201408386,0.013329842127859592,-0.015541335567831993,-0.00583177013322711,0.999773383140564],"type":"quaternion"},{"name":"Spine.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,0.9999997019767761,0.9999999403953552,0.9999998211860657,0.9999999403953552,0.9999998211860657],"type":"vector"},{"name":"LeftShoulder.position","times":[0.03333333507180214,3.299999952316284],"values":[4.366400718688965,7.5629119873046875,-0.17679919302463531,4.366411209106445,7.562912464141846,-0.17679664492607117],"type":"vector"},{"name":"LeftShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.6860190033912659,0.4875812232494354,-0.2904376685619354,0.45528945326805115,0.6907804012298584,0.4799993336200714,-0.2855343520641327,0.4592311680316925,0.6991729736328125,0.4665282964706421,-0.27858319878578186,0.46465036273002625,0.7084404230117798,0.45106154680252075,-0.2722839117050171,0.46959254145622253,0.7178505659103394,0.4342384934425354,-0.2661672532558441,0.4746393859386444,0.7263965606689453,0.41770967841148376,-0.259263277053833,0.48025962710380554,0.7333661913871765,0.40292271971702576,-0.2511581778526306,0.486566424369812,0.738635778427124,0.39026159048080444,-0.2406376600265503,0.49417269229888916,0.7403960227966309,0.3835943639278412,-0.22839562594890594,0.5024982690811157,0.7404391765594482,0.3798728883266449,-0.21508502960205078,0.5110625624656677,0.737694501876831,0.3794732987880707,-0.19993241131305695,0.5213769674301147,0.7288436889648438,0.38890644907951355,-0.187504380941391,0.5313951969146729,0.7193005681037903,0.39866065979003906,-0.17953643202781677,0.5398547053337097,0.706730306148529,0.41424936056137085,-0.1756381243467331,0.5459679961204529,0.6874532103538513,0.43905094265937805,-0.1865730881690979,0.5475700497627258,0.6545259356498718,0.48147052526474,-0.22475102543830872,0.5378372669219971,0.5888395309448242,0.5631775856018066,-0.2990836799144745,0.4966367781162262,0.5063549280166626,0.6363890767097473,-0.37286585569381714,0.44674915075302124,0.4499587416648865,0.6660500764846802,-0.41001951694488525,0.4310436248779297,0.429488867521286,0.6659482717514038,-0.4446452856063843,0.41754385828971863,0.4251992404460907,0.646648645401001,-0.47060438990592957,0.42377203702926636,0.4208916425704956,0.6178442239761353,-0.482576847076416,0.4563314914703369,0.40848827362060547,0.5936498045921326,-0.48449909687042236,0.49596160650253296,0.3893750309944153,0.5776533484458923,-0.4800134301185608,0.5331892967224121,0.3726098835468292,0.5609815120697021,-0.4696613550186157,0.5708588361740112,0.35506805777549744,0.5399650931358337,-0.4508187472820282,0.615732729434967,0.338893860578537,0.5184375047683716,-0.4295486807823181,0.6571616530418396,0.32016390562057495,0.5008122324943542,-0.40784525871276855,0.6930688619613647,0.3047243356704712,0.4759827256202698,-0.3847605288028717,0.7297553420066833,0.2974042296409607,0.4452895522117615,-0.3639763295650482,0.7620952725410461,0.2993168830871582,0.41008278727531433,-0.33935099840164185,0.7918853759765625,0.3275587856769562,0.3615608811378479,-0.29731953144073486,0.8207192420959473,0.36733633279800415,0.3041040301322937,-0.2488178163766861,0.8430150747299194,0.4147236943244934,0.24288704991340637,-0.19449631869792938,0.855091392993927,0.4682391583919525,0.18615217506885529,-0.14312629401683807,0.851830005645752,0.5091755390167236,0.14963039755821228,-0.10678610950708389,0.8408018946647644,0.5402897000312805,0.13426296412944794,-0.08311031013727188,0.8265308737754822,0.5749662518501282,0.1173182800412178,-0.07751719653606415,0.8060033321380615,0.6117388010025024,0.1079152375459671,-0.07983515411615372,0.7795873284339905,0.6318693161010742,0.14321085810661316,-0.03886542096734047,0.760737419128418,0.6507588624954224,0.17499586939811707,-0.058690015226602554,0.7365085482597351,0.6482532620429993,0.22911761701107025,-0.11088724434375763,0.7176189422607422,0.6267988681793213,0.3198292553424835,-0.17267745733261108,0.6892132759094238,0.5678568482398987,0.46521297097206116,-0.2503505051136017,0.6312211751937866,0.4903695285320282,0.5984144806861877,-0.319588303565979,0.5470842719078064,0.4507856070995331,0.6587087512016296,-0.360066294670105,0.4829569160938263,0.42648470401763916,0.6896787881851196,-0.39860230684280396,0.42845097184181213,0.4236163794994354,0.6907527446746826,-0.4241137206554413,0.4043976366519928,0.43933427333831787,0.6687066555023193,-0.45531997084617615,0.3905133306980133,0.4440954327583313,0.6530687808990479,-0.4740310609340668,0.38932642340660095,0.4353314936161041,0.6448956727981567,-0.4822467863559723,0.4025348126888275,0.42056459188461304,0.6421871781349182,-0.47994664311408997,0.4247027039527893,0.40607768297195435,0.6380605101585388,-0.47886762022972107,0.44571927189826965,0.38583236932754517,0.6459618806838989,-0.4643292725086212,0.4671884775161743,0.3644406497478485,0.6589540243148804,-0.44780850410461426,0.4821101129055023,0.3475387692451477,0.6585250496864319,-0.4375366270542145,0.5041065216064453,0.34004414081573486,0.6384045481681824,-0.4098212718963623,0.5557483434677124,0.3418382704257965,0.6003598570823669,-0.3677714169025421,0.6224618554115295,0.3459731638431549,0.5604417324066162,-0.31154537200927734,0.6849431395530701,0.36207258701324463,0.5269309282302856,-0.2526107132434845,0.726247251033783,0.38243457674980164,0.5070434808731079,-0.2065640389919281,0.7442997097969055,0.4188222289085388,0.4949592351913452,-0.17104797065258026,0.7418529987335205,0.4722696840763092,0.48901450634002686,-0.14513173699378967,0.7188622951507568,0.529734194278717,0.4856243133544922,-0.1394137144088745,0.6812596321105957,0.5869205594062805,0.4818750321865082,-0.14626957476139069,0.6339763402938843,0.5991387367248535,0.5163204669952393,-0.19044852256774902,0.5815284252166748,0.608072817325592,0.544812023639679,-0.21654453873634338,0.5352903604507446,0.6670433282852173,0.4973007142543793,-0.19849568605422974,0.5180200338363647,0.712076723575592,0.45930251479148865,-0.16291026771068573,0.5054188370704651,0.7357615232467651,0.41008371114730835,-0.14235317707061768,0.519828736782074,0.7672485709190369,0.3087086081504822,-0.1041557714343071,0.5524312257766724,0.7770073413848877,0.22537286579608917,-0.05265152081847191,0.5854011178016663,0.7784450650215149,0.16109898686408997,-0.028187157586216927,0.6060329675674438,0.7757788896560669,0.13584525883197784,-0.021248793229460716,0.6158422827720642,0.7723062038421631,0.11562266200780869,-0.0211902167648077,0.6242799758911133,0.7695118188858032,0.10140340775251389,-0.02915142849087715,0.6298564672470093,0.7679299712181091,0.094298355281353,-0.041632719337940216,0.6321852803230286,0.7684964537620544,0.09336315095424652,-0.053123295307159424,0.6307729482650757,0.767926037311554,0.09852052479982376,-0.06407168507575989,0.6296651363372803,0.7681226134300232,0.10845954716205597,-0.07361090928316116,0.6267421245574951,0.7687849998474121,0.12432748079299927,-0.08450327068567276,0.6215879321098328,0.7672314047813416,0.1445675641298294,-0.09495323896408081,0.6176084280014038,0.7622120976448059,0.1690344363451004,-0.1080942302942276,0.6154476404190063,0.7548394799232483,0.19732175767421722,-0.12262631207704544,0.6133875846862793,0.7518017292022705,0.2272886037826538,-0.13655783236026764,0.603726863861084,0.7526155114173889,0.2605278193950653,-0.1576952189207077,0.5838043093681335,0.7526853680610657,0.2963728904724121,-0.18269512057304382,0.5587936639785767,0.751308798789978,0.3254361152648926,-0.20483586192131042,0.5363475680351257,0.7493383288383484,0.3478255867958069,-0.2230307012796402,0.517461895942688,0.7442140579223633,0.3687150180339813,-0.23345057666301727,0.5056635141372681,0.7391860485076904,0.38638466596603394,-0.24019141495227814,0.4966074526309967,0.7335508465766907,0.400301069021225,-0.24563930928707123,0.49124693870544434,0.7262867093086243,0.41629311442375183,-0.252738893032074,0.48510903120040894,0.717826247215271,0.43355342745780945,-0.26100894808769226,0.47815409302711487,0.7090359330177307,0.4503474235534668,-0.26934462785720825,0.47107186913490295,0.7007666230201721,0.4653184115886688,-0.27692049741744995,0.464456707239151,0.6934839487075806,0.4772067666053772,-0.28354212641716003,0.459301233291626,0.6880921721458435,0.4848964512348175,-0.28849002718925476,0.4562654495239258,0.6860334277153015,0.4876028001308441,-0.29037103056907654,0.4552871882915497],"type":"quaternion"},{"name":"LeftShoulder.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999997615814209,0.9999997615814209,1,0.9999997019767761,0.9999997615814209,0.9999998211860657],"type":"vector"},{"name":"LeftArm.position","times":[0.03333333507180214,3.299999952316284],"values":[-1.4249371815822087e-7,17.630794525146484,-0.00000809675930213416,-0.000003796684723056387,17.63080406188965,-0.000014442402061831672],"type":"vector"},{"name":"LeftArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.4475308656692505,-0.25221654772758484,0.5482354760169983,0.6599552631378174,0.4347110092639923,-0.24580277502536774,0.5352914333343506,0.6812272071838379,0.41353240609169006,-0.22675034403800964,0.5205218195915222,0.7117810845375061,0.3848084509372711,-0.2027919739484787,0.5166448950767517,0.7374794483184814,0.3486991226673126,-0.18079432845115662,0.5228717923164368,0.7565233111381531,0.31044748425483704,-0.16698643565177917,0.5404191613197327,0.7639928460121155,0.279372900724411,-0.16512052714824677,0.5641428828239441,0.7592291235923767,0.2673332095146179,-0.17464618384838104,0.5870926380157471,0.7438775897026062,0.26539942622184753,-0.18734633922576904,0.616105854511261,0.7175500988960266,0.27694159746170044,-0.19206303358078003,0.6239093542098999,0.7050902843475342,0.29333314299583435,-0.18350785970687866,0.6339547634124756,0.6916515827178955,0.3000107705593109,-0.16463255882263184,0.652597963809967,0.676021933555603,0.28785598278045654,-0.13624519109725952,0.6692966818809509,0.6712809801101685,0.26856571435928345,-0.11026351898908615,0.6858025193214417,0.6673749685287476,0.26164865493774414,-0.08785092830657959,0.6877917051315308,0.6713902354240417,0.24977296590805054,-0.06678558886051178,0.6851748824119568,0.6809468269348145,0.25611642003059387,-0.03416847437620163,0.7265695333480835,0.6366581916809082,0.18747596442699432,0.0030474497471004725,0.7642453908920288,0.6170676946640015,0.11373858153820038,0.057260312139987946,0.6651796698570251,0.7357450723648071,0.14227081835269928,0.0702158734202385,0.5532321929931641,0.8177792429924011,0.24226608872413635,0.05806460231542587,0.4866788983345032,0.8373048305511475,0.2737603187561035,0.013390187174081802,0.4996581971645355,0.82171630859375,0.18559283018112183,-0.08272917568683624,0.5368903279304504,0.8188161849975586,0.09655258804559708,-0.17195801436901093,0.5470478534698486,0.8135396242141724,0.02097127214074135,-0.23484770953655243,0.5388532876968384,0.8087297677993774,-0.10995429754257202,-0.24759356677532196,0.46978479623794556,0.8401843309402466,-0.2021889090538025,-0.25838157534599304,0.4019518792629242,0.8548645377159119,-0.26381736993789673,-0.2784411311149597,0.38434627652168274,0.8397314548492432,-0.27169349789619446,-0.2613431215286255,0.36247313022613525,0.8523471355438232,-0.26161983609199524,-0.224973663687706,0.330780953168869,0.8783654570579529,-0.2516420781612396,-0.18342608213424683,0.2851833403110504,0.9064776301383972,-0.25601866841316223,-0.15788786113262177,0.2517206370830536,0.9198709726333618,-0.2607560157775879,-0.1437837928533554,0.22730928659439087,0.9271802306175232,-0.2553786039352417,-0.14236943423748016,0.220256969332695,0.9305909872055054,-0.21880963444709778,-0.16310593485832214,0.257953941822052,0.9268109798431396,-0.12844309210777283,-0.237962543964386,0.33789151906967163,0.9015018343925476,-0.0061219013296067715,-0.3370133638381958,0.4159592092037201,0.8446078896522522,0.16988126933574677,-0.40154245495796204,0.4270244538784027,0.7921831607818604,0.40287014842033386,-0.3925548791885376,0.3440383970737457,0.75182044506073,0.5376906991004944,-0.34853437542915344,0.25441935658454895,0.7243502736091614,0.622616708278656,-0.29962071776390076,0.1764853149652481,0.7010198831558228,0.6394094228744507,-0.23927618563175201,0.041908081620931625,0.7294835448265076,0.5973385572433472,-0.19481182098388672,-0.08687731623649597,0.7731024026870728,0.6010156273841858,-0.17266049981117249,-0.07922214269638062,0.7763327360153198,0.603956401348114,-0.1633521467447281,-0.042118266224861145,0.778960108757019,0.6073966026306152,-0.16116416454315186,0.008904403075575829,0.7778279185295105,0.6332110166549683,-0.20615403354167938,0.18123160302639008,0.7236708402633667,0.579537034034729,-0.2857722043991089,0.36761564016342163,0.6688271760940552,0.502377450466156,-0.3389645516872406,0.374683678150177,0.7016637921333313,0.41072162985801697,-0.390186607837677,0.4257350265979767,0.7055578827857971,0.2870055139064789,-0.41886910796165466,0.49097368121147156,0.707899272441864,0.16692981123924255,-0.43408817052841187,0.5236371755599976,0.7137969136238098,0.044117871671915054,-0.44247907400131226,0.5332426428794861,0.7196653485298157,-0.06300369650125504,-0.43680810928344727,0.509667158126831,0.7385584712028503,-0.1559484302997589,-0.4229661822319031,0.4793066680431366,0.7530237436294556,-0.2143295556306839,-0.4054758548736572,0.47173112630844116,0.7530750036239624,-0.19610026478767395,-0.3742627501487732,0.46184656023979187,0.7798524498939514,-0.1439637839794159,-0.3288341462612152,0.42630356550216675,0.8303058743476868,-0.08816136419773102,-0.2680760324001312,0.34239256381988525,0.8961753249168396,-0.033107683062553406,-0.21461650729179382,0.2380111813545227,0.9466754198074341,0.02561311610043049,-0.20045079290866852,0.14003565907478333,0.969305694103241,0.07596883922815323,-0.22705812752246857,0.09157328307628632,0.9665855765342712,0.11825691908597946,-0.2914891541004181,0.09667637199163437,0.9443003535270691,0.16942881047725677,-0.35202154517173767,0.09812867641448975,0.9152843952178955,0.2416713684797287,-0.36478814482688904,0.059855248779058456,0.8971855640411377,0.37141984701156616,-0.361875981092453,0.06362929940223694,0.8526690006256104,0.5642226934432983,-0.27422669529914856,0.07576154172420502,0.7750565409660339,0.5923583507537842,-0.10266396403312683,0.09898030012845993,0.7929531335830688,0.502493143081665,0.039987511932849884,0.2784326672554016,0.8175432085990906,0.37544742226600647,0.15460152924060822,0.4342224597930908,0.8041072487831116,0.3053421378135681,0.18012797832489014,0.5534934997558594,0.7536345720291138,0.25622543692588806,0.15883411467075348,0.6498546004295349,0.6977171897888184,0.2602587640285492,0.1574605107307434,0.6407329440116882,0.7049347162246704,0.2100108414888382,0.09267076849937439,0.6908257603645325,0.6856146454811096,0.18328434228897095,0.02014043554663658,0.7455132007598877,0.6404773592948914,0.17800273001194,-0.026772264391183853,0.751281201839447,0.6349605321884155,0.16533367335796356,-0.06746617704629898,0.7528989315032959,0.6334479451179504,0.1509709358215332,-0.09471990168094635,0.7522999048233032,0.6342561841011047,0.15432189404964447,-0.12048481404781342,0.7504505515098572,0.6312623620033264,0.17105445265769958,-0.1467445194721222,0.7490699291229248,0.6229772567749023,0.2067060023546219,-0.16693635284900665,0.7358679175376892,0.6228188872337341,0.25026461482048035,-0.184952974319458,0.7148644328117371,0.6262019872665405,0.2933846116065979,-0.20281408727169037,0.6887288689613342,0.6312245726585388,0.33229774236679077,-0.22082924842834473,0.6608189940452576,0.6357129812240601,0.3656589090824127,-0.23801642656326294,0.6326860189437866,0.6398047804832458,0.3866001069545746,-0.25601726770401,0.610713005065918,0.6418920159339905,0.3974147140979767,-0.27217772603034973,0.593160092830658,0.6450906991958618,0.401420533657074,-0.28330475091934204,0.57658851146698,0.6527984142303467,0.4004381000995636,-0.28710296750068665,0.561639666557312,0.664667010307312,0.3996621072292328,-0.2855991721153259,0.5509636998176575,0.6746423840522766,0.4036004841327667,-0.28254425525665283,0.5474405288696289,0.6764497756958008,0.4116058647632599,-0.27646055817604065,0.5369141101837158,0.682549238204956,0.42046695947647095,-0.26976248621940613,0.5276971459388733,0.6870018839836121,0.42857784032821655,-0.26404011249542236,0.5251202583312988,0.6861870288848877,0.4365101456642151,-0.2588934302330017,0.5263012647628784,0.6822317242622375,0.4418659508228302,-0.25544363260269165,0.531375527381897,0.6761236190795898,0.44468924403190613,-0.25338613986968994,0.5369813442230225,0.6705952882766724,0.4467500150203705,-0.2522980570793152,0.543440580368042,0.6644038558006287,0.44699475169181824,-0.2523905038833618,0.5480121970176697,0.6604373455047607],"type":"quaternion"},{"name":"LeftArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[1.000000238418579,1.0000001192092896,0.9999998807907104,1.000000238418579,1.0000001192092896,1],"interpolation":2300,"type":"vector"},{"name":"LeftForeArm.position","times":[0.03333333507180214,3.299999952316284],"values":[0.0000057033512348425575,17.82781219482422,0.00000230024011216301,-0.0000014636967762271524,17.827816009521484,0.0000014623919923906215],"type":"vector"},{"name":"LeftForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.13849987089633942,0.23155836760997772,0.2753051221370697,0.9227164387702942,0.13926082849502563,0.2457391619682312,0.3201836049556732,0.9042683243751526,0.1204841211438179,0.27401402592658997,0.39935407042503357,0.8665542602539062,0.09081167727708817,0.30228811502456665,0.4727688729763031,0.8227179050445557,0.06298606842756271,0.3289770483970642,0.5359204411506653,0.7749813795089722,0.043794166296720505,0.3497565984725952,0.5804601311683655,0.7340425252914429,0.03696131706237793,0.36100196838378906,0.6032653450965881,0.7101988196372986,0.042162951081991196,0.3622210621833801,0.6072356700897217,0.7058916687965393,0.06156715750694275,0.3465360105037689,0.5821245312690735,0.7329756617546082,0.057160310447216034,0.3321326971054077,0.568566620349884,0.750434935092926,0.05125625059008598,0.3036629557609558,0.5372158885002136,0.785213828086853,0.0499207079410553,0.264431357383728,0.48670417070388794,0.8310854434967041,0.048925578594207764,0.22327302396297455,0.4278065860271454,0.8744924068450928,0.061817314475774765,0.1767088919878006,0.3365873098373413,0.9228551387786865,0.0771360695362091,0.15447242558002472,0.2670639753341675,0.9480850100517273,0.0965508222579956,0.12258799374103546,0.29204925894737244,0.9435874819755554,0.07643221318721771,-0.011928291991353035,0.23373116552829742,0.9692190885543823,0.07301125675439835,-0.32880574464797974,0.18418797850608826,0.9233801960945129,0.19109761714935303,-0.5745160579681396,0.344364196062088,0.7175139784812927,0.358073353767395,-0.6993343830108643,0.41507384181022644,0.4587250351905823,0.47916969656944275,-0.6725637316703796,0.44979023933410645,0.3402104377746582,0.3506958782672882,-0.6330004334449768,0.5033379197120667,0.4722009301185608,0.19457975029945374,-0.655912458896637,0.4557957351207733,0.5693574547767639,0.04563657566905022,-0.53125,0.4847281575202942,0.6933465003967285,-0.050083260983228683,-0.49271249771118164,0.45809614658355713,0.7381558418273926,-0.07135572284460068,-0.5156547427177429,0.40696337819099426,0.7505927681922913,-0.09715423732995987,-0.5218300223350525,0.36548152565956116,0.7646422386169434,-0.13801687955856323,-0.5116051435470581,0.2960689961910248,0.7947041988372803,-0.1791374683380127,-0.5166265368461609,0.3421782851219177,0.7641471028327942,-0.20165742933750153,-0.5224947929382324,0.43432655930519104,0.7054742574691772,-0.20563191175460815,-0.5105820298194885,0.5270005464553833,0.6475275754928589,-0.202165424823761,-0.4863884449005127,0.5755743384361267,0.6255154609680176,-0.19466383755207062,-0.4555142819881439,0.5871800780296326,0.6401814818382263,-0.19117358326911926,-0.4211212694644928,0.5708857774734497,0.6783797144889832,-0.17253589630126953,-0.37683922052383423,0.5345141291618347,0.7365583181381226,-0.12349510192871094,-0.3168755769729614,0.4707854092121124,0.8140638470649719,-0.06590679287910461,-0.2552403509616852,0.35519328713417053,0.8968536257743835,-0.023952296003699303,-0.20095330476760864,0.29983413219451904,0.9322786927223206,0.036780402064323425,-0.11987731605768204,0.33418065309524536,0.9341306090354919,0.10635711252689362,-0.06557668745517731,0.3160434663295746,0.9404809474945068,0.16432011127471924,0.003485288703814149,0.3401089608669281,0.9259117841720581,0.22477059066295624,0.09489458054304123,0.36711326241493225,0.8976085186004639,0.29011866450309753,0.24574869871139526,0.4539678394794464,0.805823802947998,0.28872066736221313,0.2906814217567444,0.48851659893989563,0.7703871130943298,0.22463466227054596,0.3318520188331604,0.5750486254692078,0.7132549285888672,0.18457694351673126,0.3008585274219513,0.593187689781189,0.7235633730888367,0.16921812295913696,0.074348583817482,0.3842431604862213,0.9045411944389343,0.07300517708063126,-0.08054272085428238,0.21326790750026703,0.970927357673645,0.07009398192167282,-0.03886495158076286,0.41750285029411316,0.9051340818405151,0.014983456581830978,-0.02708755061030388,0.5249778032302856,0.8505527973175049,-0.04386013746261597,-0.07186207920312881,0.5663310289382935,0.8198666572570801,-0.09244102984666824,-0.12994903326034546,0.5784216523170471,0.7999976873397827,-0.13660676777362823,-0.19178815186023712,0.5497643351554871,0.8014456033706665,-0.18487587571144104,-0.24618372321128845,0.5151145458221436,0.7999196648597717,-0.22448405623435974,-0.28363677859306335,0.4660370647907257,0.8074444532394409,-0.2298150360584259,-0.29290708899497986,0.4037574231624603,0.8356856107711792,-0.21696457266807556,-0.2936195433139801,0.396251380443573,0.8424362540245056,-0.21514740586280823,-0.30261707305908203,0.4248419404029846,0.825617253780365,-0.2225363552570343,-0.3183901011943817,0.4734856188297272,0.79051673412323,-0.2423473745584488,-0.3423826992511749,0.5134490132331848,0.7486066818237305,-0.2563212215900421,-0.3685849606990814,0.5614925622940063,0.6951047778129578,-0.23526667058467865,-0.37822675704956055,0.6291258335113525,0.6370203495025635,-0.17143449187278748,-0.3459279537200928,0.6789225339889526,0.6245064735412598,-0.1068384051322937,-0.3053627610206604,0.687382161617279,0.6502652764320374,-0.07582683116197586,-0.2959350049495697,0.688136100769043,0.6581348776817322,0.02998334914445877,-0.24549339711666107,0.6199975609779358,0.744605302810669,0.16329622268676758,-0.1460372656583786,0.6391726136207581,0.7372013330459595,0.164713516831398,-0.10980571806430817,0.7400455474853516,0.6427634358406067,0.09545399993658066,-0.25235921144485474,0.6995534300804138,0.6616860628128052,0.010791308246552944,-0.39069822430610657,0.5837510824203491,0.7116692662239075,0.013164903968572617,-0.44247207045555115,0.4613174498081207,0.7689157128334045,0.030475161969661713,-0.47756657004356384,0.2844160199165344,0.8307279944419861,0.09190668165683746,-0.4430817663669586,0.35892465710639954,0.8163362145423889,0.09086965769529343,-0.3836527466773987,0.35757124423980713,0.8465790748596191,0.08241690695285797,-0.285788357257843,0.352134108543396,0.8874311447143555,0.08707989007234573,-0.19958262145519257,0.39607420563697815,0.8920252919197083,0.0960187092423439,-0.1745043247938156,0.44256940484046936,0.8743346333503723,0.10033664852380753,-0.1793220192193985,0.4817984700202942,0.851848840713501,0.10196109116077423,-0.172075554728508,0.48364588618278503,0.8521036505699158,0.09041072428226471,-0.13506552577018738,0.4781841039657593,0.8630893230438232,0.09033186733722687,-0.09675713628530502,0.49648916721343994,0.8578909039497375,0.08924184739589691,-0.0506305992603302,0.5111820697784424,0.8533260822296143,0.08584079146385193,-0.0015816160012036562,0.5130512714385986,0.8540534377098083,0.08056040853261948,0.046229783445596695,0.5053181648254395,0.8579198122024536,0.07517753541469574,0.09001526236534119,0.49099212884902954,0.8632336854934692,0.0654345378279686,0.12411291152238846,0.4588510990142822,0.8773653507232666,0.055155713111162186,0.15075166523456573,0.4199522137641907,0.8932367563247681,0.0476999394595623,0.17119409143924713,0.38109728693962097,0.9072938561439514,0.04337889701128006,0.18444028496742249,0.3357544243335724,0.9226966500282288,0.043176475912332535,0.1925956904888153,0.2905879616737366,0.9362698793411255,0.0444793626666069,0.1939266175031662,0.23375850915908813,0.9517200589179993,0.050646062940359116,0.198686882853508,0.2088875025510788,0.9562032222747803,0.06468746811151505,0.2081669718027115,0.22259266674518585,0.9502286911010742,0.08328358829021454,0.21655836701393127,0.24276947975158691,0.941928505897522,0.10145866870880127,0.22244836390018463,0.2559845447540283,0.9352511763572693,0.11679282039403915,0.2264404445886612,0.26444143056869507,0.930137038230896,0.1302066594362259,0.230479896068573,0.27479588985443115,0.9243443608283997,0.1374121457338333,0.23168174922466278,0.2771603763103485,0.9222925901412964,0.13891024887561798,0.2316327840089798,0.2766377329826355,0.9222373962402344],"type":"quaternion"},{"name":"LeftForeArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998211860657,1,0.9999999403953552,0.9999999403953552,1,0.9999998807907104],"type":"vector"},{"name":"LeftHand.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000033131198051705724,18.7510986328125,-0.000006367174137267284,0.000013949728781881277,18.751087188720703,-0.000008334956874023192],"interpolation":2300,"type":"vector"},{"name":"LeftHand.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.05503770709037781,0.05196233093738556,-0.013945183716714382,0.9970337748527527,0.05503769963979721,0.051962338387966156,-0.013945179060101509,0.9970337748527527],"interpolation":2300,"type":"quaternion"},{"name":"LeftHand.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999999403953552,1.000000238418579,1,0.9999998807907104,1.000000238418579,1],"interpolation":2300,"type":"vector"},{"name":"RightShoulder.position","times":[0.03333333507180214,3.299999952316284],"values":[-4.513197898864746,8.246405601501465,-0.08130107820034027,-4.513198375701904,8.246402740478516,-0.08129533380270004],"type":"vector"},{"name":"RightShoulder.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.5453154444694519,-0.6417081356048584,0.3175819218158722,0.43587103486061096,0.5388177037239075,-0.6464859843254089,0.3218483328819275,0.4337569773197174,0.529269814491272,-0.6549736261367798,0.32502758502960205,0.4303952753543854,0.5196093916893005,-0.6634904146194458,0.3269982635974884,0.4276197850704193,0.5094688534736633,-0.672370195388794,0.3283225893974304,0.4249284267425537,0.4994851350784302,-0.6814947724342346,0.32942184805870056,0.42137953639030457,0.4918330907821655,-0.6889991760253906,0.33112210035324097,0.4168194830417633,0.4876910150051117,-0.6935275197029114,0.3374602496623993,0.4090203642845154,0.48951420187950134,-0.6933873295783997,0.34492629766464233,0.4007689356803894,0.49282920360565186,-0.6916521787643433,0.3537313938140869,0.3919321596622467,0.49748992919921875,-0.6878244280815125,0.3658187687397003,0.3815467953681946,0.5074582099914551,-0.6785682439804077,0.3784258961677551,0.3725924491882324,0.5290651917457581,-0.6581588387489319,0.38880443572998047,0.3684401214122772,0.5720980763435364,-0.6181172132492065,0.39498037099838257,0.36691343784332275,0.6282647252082825,-0.5568894743919373,0.38885751366615295,0.37940406799316406,0.6710354685783386,-0.5028682351112366,0.3602926433086395,0.40868592262268066,0.696524977684021,-0.4584885239601135,0.3070415258407593,0.4586576223373413,0.7059373259544373,-0.4103270173072815,0.2261427640914917,0.531171977519989,0.6983511447906494,-0.38113918900489807,0.16720551252365112,0.5823065042495728,0.6478886008262634,-0.40271398425102234,0.10785740613937378,0.6375175714492798,0.5705161094665527,-0.4459978938102722,0.08905448019504547,0.6838615536689758,0.5037386417388916,-0.4743248522281647,0.09198121726512909,0.7161025404930115,0.4304780960083008,-0.5159628987312317,0.10633958131074905,0.7329139113426208,0.3612653911113739,-0.5665318369865417,0.1340918242931366,0.7283875942230225,0.31483888626098633,-0.6169476509094238,0.1728028655052185,0.7002794146537781,0.29499951004981995,-0.6547116041183472,0.21614786982536316,0.6615195870399475,0.2902298867702484,-0.685084342956543,0.24742212891578674,0.6206514835357666,0.2964240312576294,-0.7044705152511597,0.2630084753036499,0.5887959599494934,0.31972038745880127,-0.7148802876472473,0.28035858273506165,0.5550894141197205,0.3490408957004547,-0.7184959650039673,0.3016209602355957,0.5205370783805847,0.377588152885437,-0.7198991179466248,0.32228586077690125,0.4850817322731018,0.3917991518974304,-0.7251279354095459,0.33699142932891846,0.4551039934158325,0.38642051815986633,-0.7404125332832336,0.3483729660511017,0.42556408047676086,0.3642673194408417,-0.7654579281806946,0.3485521972179413,0.39986860752105713,0.3615802228450775,-0.7790707945823669,0.34394896030426025,0.3794833719730377,0.37895622849464417,-0.7809472680091858,0.3360673487186432,0.3654755651950836,0.4179823100566864,-0.7673864364624023,0.338482141494751,0.34905412793159485,0.4684046804904938,-0.7401596903800964,0.35535502433776855,0.3263181447982788,0.5270057320594788,-0.7036043405532837,0.3716081976890564,0.2985185384750366,0.6103418469429016,-0.6394242644309998,0.38285309076309204,0.26840823888778687,0.6618229746818542,-0.6001427173614502,0.3540901243686676,0.27647653222084045,0.7012999653816223,-0.5599891543388367,0.31670016050338745,0.3070692718029022,0.7449214458465576,-0.5028282999992371,0.2748599946498871,0.34162524342536926,0.7951165437698364,-0.4168930649757385,0.20364102721214294,0.39053836464881897,0.8248352408409119,-0.3306428790092468,0.12705139815807343,0.4406587481498718,0.8188213109970093,-0.3021637499332428,0.09758668392896652,0.4782317876815796,0.7943684458732605,-0.28730565309524536,0.07391964644193649,0.530066192150116,0.7567862272262573,-0.28747159242630005,0.06150040030479431,0.5838257074356079,0.7029690742492676,-0.31508803367614746,0.04472697526216507,0.6360452175140381,0.6541536450386047,-0.335254430770874,0.04271670803427696,0.6766555905342102,0.6078402996063232,-0.3530198037624359,0.052177928388118744,0.7093551158905029,0.5648350119590759,-0.3675224781036377,0.0711280032992363,0.7354111075401306,0.5187383890151978,-0.38414669036865234,0.0905255675315857,0.7583844661712646,0.490662157535553,-0.3971773684024811,0.111638642847538,0.7674878835678101,0.4999143183231354,-0.4131071865558624,0.1527637243270874,0.7457153797149658,0.5187503695487976,-0.44409000873565674,0.18802084028720856,0.7059252262115479,0.5425986647605896,-0.4734061062335968,0.207637220621109,0.6620877385139465,0.5560141801834106,-0.48765599727630615,0.21506580710411072,0.6377983689308167,0.5571805834770203,-0.4861409366130829,0.2128060907125473,0.6386942863464355,0.5531739592552185,-0.4881736636161804,0.19680528342723846,0.6457188129425049,0.5523793697357178,-0.4915899932384491,0.17515473067760468,0.6500285863876343,0.5674906373023987,-0.4928528368473053,0.1541898399591446,0.641308069229126,0.5977337956428528,-0.48261237144470215,0.1388472318649292,0.6249168515205383,0.6249523162841797,-0.46217620372772217,0.1304364800453186,0.6154788732528687,0.6544703841209412,-0.43458980321884155,0.12368303537368774,0.6062201261520386,0.6725940108299255,-0.40474236011505127,0.1281181424856186,0.6061243414878845,0.6839028596878052,-0.3836262822151184,0.1568114161491394,0.6004315614700317,0.6763113737106323,-0.4248359501361847,0.19471900165081024,0.5693872570991516,0.6648548245429993,-0.4851147532463074,0.2565481662750244,0.506769061088562,0.6282390356063843,-0.5452725887298584,0.32907918095588684,0.4468786120414734,0.5574344396591187,-0.6213180422782898,0.39005813002586365,0.3886971175670624,0.4952259063720703,-0.6655901670455933,0.4494577646255493,0.33125337958335876,0.42927029728889465,-0.7020977735519409,0.4872751235961914,0.2921450734138489,0.37507256865501404,-0.7273841500282288,0.5151926875114441,0.25457701086997986,0.3255132734775543,-0.7450398802757263,0.5378636717796326,0.2228439450263977,0.2980215549468994,-0.7502517104148865,0.5497297644615173,0.21471554040908813,0.2887016832828522,-0.7487585544586182,0.552147388458252,0.2261531949043274,0.2852644920349121,-0.7487328052520752,0.5492715835571289,0.23732693493366241,0.2875095307826996,-0.7475361824035645,0.5444540977478027,0.24919410049915314,0.29456499218940735,-0.7471204400062561,0.5357183814048767,0.2608608901500702,0.3075827658176422,-0.7461404204368591,0.5223615765571594,0.27532851696014404,0.32287347316741943,-0.742093563079834,0.5085071325302124,0.2940584719181061,0.34033721685409546,-0.733448326587677,0.49495363235473633,0.31819024682044983,0.35976895689964294,-0.7211439609527588,0.48081302642822266,0.3454512655735016,0.3848515748977661,-0.7116276025772095,0.4585132598876953,0.3677513301372528,0.4154699444770813,-0.7041431069374084,0.42427825927734375,0.3893009126186371,0.44794565439224243,-0.6948674917221069,0.3847585916519165,0.4104445278644562,0.47339484095573425,-0.6878582239151001,0.34788644313812256,0.42629027366638184,0.4918133616447449,-0.6836034655570984,0.3159829080104828,0.4369907081127167,0.5077828764915466,-0.6783055663108826,0.2952597141265869,0.4414519965648651,0.5197665691375732,-0.673407256603241,0.28214380145072937,0.4435766339302063,0.527289867401123,-0.6692667007446289,0.2781917154788971,0.4434601962566376,0.533037543296814,-0.6644665598869324,0.27984005212783813,0.44276943802833557,0.5375911593437195,-0.6589618921279907,0.2852988839149475,0.44200628995895386,0.5411239862442017,-0.6537644267082214,0.2928062379360199,0.4405013620853424,0.5436533093452454,-0.6492831110954285,0.30101609230041504,0.4384767413139343,0.5450323820114136,-0.6455062627792358,0.30888018012046814,0.43686890602111816,0.5453889966011047,-0.6427404284477234,0.3150724172592163,0.4360790252685547,0.545333743095398,-0.6417019367218018,0.31761401891708374,0.43583375215530396],"type":"quaternion"},{"name":"RightShoulder.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,0.9999999403953552,1.0000001192092896,0.9999999403953552,0.9999997019767761,1],"type":"vector"},{"name":"RightArm.position","times":[0.03333333507180214,3.299999952316284],"values":[0.000003305012796772644,17.921396255493164,-0.000004175730282440782,-6.86224097989907e-7,17.921403884887695,0.000005002219495509053],"type":"vector"},{"name":"RightArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.4480580985546112,0.16090883314609528,-0.3067791759967804,0.8241595029830933,0.44975244998931885,0.14711448550224304,-0.31305956840515137,0.823452353477478,0.44848525524139404,0.10929110646247864,-0.31144601106643677,0.8306128978729248,0.4456866383552551,0.05573563650250435,-0.3000636398792267,0.8415573835372925,0.443720281124115,-0.0037555352319031954,-0.28392496705055237,0.8499910831451416,0.4423496723175049,-0.05918407440185547,-0.26680848002433777,0.854188084602356,0.4396912157535553,-0.1018609032034874,-0.2502916157245636,0.8565338253974915,0.4323369264602661,-0.12469909340143204,-0.2333788126707077,0.862014651298523,0.424355685710907,-0.11475598067045212,-0.2106025367975235,0.8731551766395569,0.43021202087402344,-0.0868607684969902,-0.20350199937820435,0.8751912713050842,0.44359785318374634,-0.05556068196892738,-0.20367933809757233,0.8710044622421265,0.46639055013656616,-0.020261039957404137,-0.20524288713932037,0.8602004051208496,0.49470236897468567,0.017472099512815475,-0.19882142543792725,0.8458334803581238,0.523990273475647,0.06730899959802628,-0.18006306886672974,0.8297475576400757,0.5567278861999512,0.12429744750261307,-0.15645387768745422,0.8063041567802429,0.5701025724411011,0.15214669704437256,-0.18136267364025116,0.7867286801338196,0.5480842590332031,0.14573350548744202,-0.273087739944458,0.7770382761955261,0.4883650541305542,0.20750752091407776,-0.3609180748462677,0.7669278979301453,0.4143091142177582,0.2799251675605774,-0.445413202047348,0.7426957488059998,0.34138843417167664,0.34402182698249817,-0.5208035111427307,0.7027564644813538,0.2951821982860565,0.39779165387153625,-0.5604466199874878,0.6637235283851624,0.25377020239830017,0.44713854789733887,-0.5779041051864624,0.6337938904762268,0.16592136025428772,0.4977441430091858,-0.5803790092468262,0.6228010654449463,0.01709219068288803,0.5323780179023743,-0.5476294159889221,0.6452779173851013,-0.13192619383335114,0.5369538068771362,-0.4827374815940857,0.6791470050811768,-0.2645097076892853,0.5110591650009155,-0.37292417883872986,0.7278603315353394,-0.34070470929145813,0.46772828698158264,-0.22085388004779816,0.7850949764251709,-0.3409167528152466,0.43103867769241333,-0.07777012884616852,0.8318252563476562,-0.3181799650192261,0.42027291655540466,0.012140733189880848,0.8496968746185303,-0.3026716709136963,0.44269365072250366,0.035241883248090744,0.8433091044425964,-0.29237034916877747,0.4860234260559082,0.006230398081243038,0.8235666155815125,-0.26588889956474304,0.524032473564148,-0.03608397766947746,0.8083260655403137,-0.2303200513124466,0.540519654750824,-0.07648978382349014,0.8055684566497803,-0.18507440388202667,0.5340073704719543,-0.1137109026312828,0.8171006441116333,-0.1546143740415573,0.5120875835418701,-0.1688620150089264,0.8278564810752869,-0.12041021883487701,0.4772290289402008,-0.24211323261260986,0.8361429572105408,-0.08545441180467606,0.43898922204971313,-0.34050455689430237,0.8270686864852905,-0.003250003093853593,0.3938842713832855,-0.4542970657348633,0.7990362048149109,0.12276322394609451,0.30881112813949585,-0.45968544483184814,0.8235619068145752,0.18609380722045898,0.22555139660835266,-0.42835164070129395,0.854991614818573,0.22079116106033325,0.15471169352531433,-0.4250722825527191,0.8640770316123962,0.2560301125049591,0.09222559630870819,-0.4632277488708496,0.8434234857559204,0.28364935517311096,0.03192155435681343,-0.4656630754470825,0.8376646041870117,0.245620459318161,0.011430777609348297,-0.5227751731872559,0.8162389993667603,0.20805934071540833,0.014869078062474728,-0.5192345976829529,0.8287856578826904,0.1569231152534485,0.03479978069663048,-0.5518314838409424,0.8183190822601318,0.10528687387704849,0.06681236624717712,-0.566032886505127,0.8148972988128662,0.04936377704143524,0.11642994731664658,-0.5791529417037964,0.8053503036499023,-0.021572085097432137,0.20442190766334534,-0.5959446430206299,0.7762708067893982,-0.03928132355213165,0.3128862977027893,-0.6042828559875488,0.7317113280296326,-0.03766072541475296,0.4239906668663025,-0.6028594374656677,0.6748141646385193,-0.019313007593154907,0.5208797454833984,-0.5842480659484863,0.622065544128418,0.008851010352373123,0.6115936040878296,-0.5500292778015137,0.5686324238777161,0.005650713108479977,0.6688926815986633,-0.5202417373657227,0.5309417843818665,-0.07023252546787262,0.6829666495323181,-0.5058087706565857,0.5222850441932678,-0.1639876663684845,0.6807970404624939,-0.492025762796402,0.5172370672225952,-0.2182319462299347,0.6754717826843262,-0.4860020577907562,0.5098182559013367,-0.22482991218566895,0.6724129915237427,-0.49261438846588135,0.5046219825744629,-0.19393625855445862,0.6705783009529114,-0.5063322186470032,0.5063014626502991,-0.14463864266872406,0.6662612557411194,-0.533210277557373,0.5008618235588074,-0.15280018746852875,0.6325884461402893,-0.5509375929832458,0.5224477648735046,-0.13798344135284424,0.5912728309631348,-0.5632637739181519,0.5604382753372192,-0.07974066585302353,0.5380304455757141,-0.59601229429245,0.5907064080238342,-0.014143005013465881,0.4595300555229187,-0.6342827081680298,0.6215445399284363,0.04192433878779411,0.3733218014240265,-0.676246166229248,0.6336910724639893,0.08702226728200912,0.2706097662448883,-0.6996238231658936,0.6555333733558655,0.14626169204711914,0.17863766849040985,-0.6872456669807434,0.688759446144104,0.23313607275485992,0.10974449664354324,-0.5920153260231018,0.7636240124702454,0.3056873381137848,0.07260601222515106,-0.4713594913482666,0.8240776062011719,0.34029096364974976,0.01625855639576912,-0.4245795011520386,0.8388504385948181,0.3145735263824463,-0.0713498592376709,-0.3567080795764923,0.8767622709274292,0.31146642565727234,-0.10624083131551743,-0.31683626770973206,0.8895596265792847,0.41519463062286377,-0.06813376396894455,-0.2138615846633911,0.8816090226173401,0.5125605463981628,0.0247436985373497,-0.06973227113485336,0.8554571270942688,0.5405619144439697,0.1310899257659912,-0.007439178880304098,0.8309951424598694,0.5375736951828003,0.1979110836982727,0.033611491322517395,0.8189725279808044,0.5109631419181824,0.2862272262573242,0.10551366209983826,0.8036525845527649,0.4671713709831238,0.3519079387187958,0.14972196519374847,0.7971794605255127,0.4209161102771759,0.37265604734420776,0.1422298103570938,0.814694881439209,0.38378506898880005,0.37197861075401306,0.12252521514892578,0.8362586498260498,0.3514508605003357,0.34097006916999817,0.10416984558105469,0.8656617999076843,0.3286800682544708,0.29005271196365356,0.08677826076745987,0.8945995569229126,0.32113587856292725,0.23334339261054993,0.05915595218539238,0.9159274697303772,0.32136717438697815,0.177729070186615,0.02441343292593956,0.9298061728477478,0.3334723711013794,0.13090649247169495,-0.017138035967946053,0.933469831943512,0.3485724627971649,0.09507813304662704,-0.07436598837375641,0.929476797580719,0.36101195216178894,0.06837822496891022,-0.13933824002742767,0.9195540547370911,0.3695400655269623,0.05064086243510246,-0.20845617353916168,0.90411376953125,0.3794901669025421,0.03978584706783295,-0.278344988822937,0.8814354538917542,0.39532360434532166,0.060393430292606354,-0.3257564306259155,0.8567115664482117,0.41106531023979187,0.07980164885520935,-0.36015546321868896,0.8336336612701416,0.42632654309272766,0.097647525370121,-0.38293007016181946,0.8136800527572632,0.4381697475910187,0.1114259585738182,-0.3819893002510071,0.8060246706008911,0.4472755789756775,0.12230176478624344,-0.3660021126270294,0.8068639039993286,0.4542980194091797,0.13230234384536743,-0.348438560962677,0.8091353178024292,0.456227570772171,0.14246641099452972,-0.3329835832118988,0.8128232955932617,0.45440247654914856,0.15132448077201843,-0.31912389397621155,0.8177892565727234,0.4511062800884247,0.15797044336795807,-0.31031885743141174,0.8217363953590393,0.4478782117366791,0.16104036569595337,-0.30691850185394287,0.8241796493530273],"type":"quaternion"},{"name":"RightArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998211860657,0.9999999403953552,0.9999997615814209,1,1,0.9999997615814209],"type":"vector"},{"name":"RightForeArm.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.0000012584181376951165,17.20370864868164,0.000003913517957698787,0.000015532288671238348,17.20370101928711,6.71209434699449e-8],"type":"vector"},{"name":"RightForeArm.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.039519619196653366,-0.0958850234746933,-0.6261545419692993,0.7727708220481873,0.02874811738729477,-0.09080839902162552,-0.6321481466293335,0.768970787525177,-0.0025765234604477882,-0.07565797865390778,-0.653927206993103,0.7527605891227722,-0.04439419135451317,-0.047771479934453964,-0.6824583411216736,0.7280094027519226,-0.08701317012310028,-0.008403022773563862,-0.7090502977371216,0.6997184157371521,-0.12410316616296768,0.03748908266425133,-0.7260750532150269,0.675283670425415,-0.1530507653951645,0.08169310539960861,-0.7328090667724609,0.6579457521438599,-0.1716785430908203,0.11593887954950333,-0.732367753982544,0.6486310362815857,-0.1656373143196106,0.1360456496477127,-0.7263580560684204,0.6530389189720154,-0.13805033266544342,0.153753399848938,-0.7222918272018433,0.659997284412384,-0.10418141633272171,0.17820674180984497,-0.7078347206115723,0.6755432486534119,-0.06446877121925354,0.2113831639289856,-0.6866329908370972,0.6926009654998779,-0.02028392069041729,0.254131555557251,-0.6647975444793701,0.7021751999855042,0.043268632143735886,0.32193464040756226,-0.6368669867515564,0.6992040872573853,0.12838606536388397,0.4290104806423187,-0.5913704633712769,0.670632541179657,0.18273594975471497,0.5618342757225037,-0.5018942952156067,0.631705641746521,0.16107776761054993,0.6773400902748108,-0.36281028389930725,0.6193812489509583,0.08868709951639175,0.6828858256340027,-0.1799784004688263,0.7024310231208801,0.0629473552107811,0.5870356559753418,-0.11930770426988602,0.7982434034347534,0.07502332329750061,0.46235033869743347,-0.13523729145526886,0.8731063008308411,0.10377755016088486,0.322195440530777,-0.24382521212100983,0.9088287353515625,0.049362506717443466,0.17510628700256348,-0.3717913329601288,0.9103144407272339,-0.022823084145784378,0.04776466637849808,-0.4408951699733734,0.8959961533546448,-0.10161807388067245,0.002774512628093362,-0.47700798511505127,0.8730002641677856,-0.1568615883588791,0.044436875730752945,-0.47481146454811096,0.8648548126220703,-0.20450712740421295,0.11907468736171722,-0.4652489721775055,0.8529604077339172,-0.2468581646680832,0.21783696115016937,-0.4712643623352051,0.8182408213615417,-0.26592186093330383,0.3334958553314209,-0.5036658048629761,0.7512568235397339,-0.2738035023212433,0.3997700810432434,-0.556885838508606,0.6746063232421875,-0.2811454236507416,0.4143313467502594,-0.6254188418388367,0.5984463095664978,-0.29026299715042114,0.3780941367149353,-0.6901503801345825,0.5445041060447693,-0.2945467531681061,0.3146631717681885,-0.738377571105957,0.5186790227890015,-0.2905957102775574,0.2541738450527191,-0.765390932559967,0.5149044394493103,-0.29915717244148254,0.20195485651493073,-0.771017849445343,0.5246434211730957,-0.2929742634296417,0.16877490282058716,-0.7640740871429443,0.5494287014007568,-0.2719051241874695,0.16037091612815857,-0.7475792169570923,0.5843578577041626,-0.22643734514713287,0.19005410373210907,-0.7172033190727234,0.631050705909729,-0.16479049623012543,0.23826858401298523,-0.6964683532714844,0.6565090417861938,-0.10955116897821426,0.27454832196235657,-0.6961018443107605,0.6542659401893616,-0.06764054298400879,0.3830989599227905,-0.6164368987083435,0.68459153175354,-0.06188926473259926,0.5079377889633179,-0.4049304723739624,0.7577600479125977,-0.03165027126669884,0.4882892966270447,-0.2496999055147171,0.8355966806411743,0.03619848191738129,0.4257708489894867,-0.27349328994750977,0.8617483973503113,0.07261169701814651,0.394461989402771,-0.12590834498405457,0.9073446989059448,0.12385528534650803,0.32107818126678467,-0.17374606430530548,0.9227030873298645,0.07786692678928375,0.26444438099861145,-0.22474317252635956,0.9346103668212891,0.01409907452762127,0.2296818196773529,-0.2978982925415039,0.9264470338821411,-0.03183082863688469,0.2129441201686859,-0.3825625479221344,0.8984918594360352,-0.0874951109290123,0.14717650413513184,-0.44133251905441284,0.8808571696281433,-0.1758367419242859,0.03476739302277565,-0.4970250725746155,0.8490222692489624,-0.23364222049713135,-0.03729792311787605,-0.5480550527572632,0.8022816777229309,-0.24802684783935547,-0.07968325912952423,-0.6080314517021179,0.7499540448188782,-0.23156724870204926,-0.1228802353143692,-0.6571145057678223,0.7067372798919678,-0.21470537781715393,-0.14556077122688293,-0.6840518116950989,0.6817527413368225,-0.18569518625736237,-0.14390712976455688,-0.6893481612205505,0.6852789521217346,-0.15417985618114471,-0.11615800857543945,-0.6794152855873108,0.7079059481620789,-0.10964838415384293,-0.10025839507579803,-0.6714237928390503,0.7260273098945618,-0.07710231840610504,-0.10596770793199539,-0.6639440655708313,0.7362094521522522,-0.06523323059082031,-0.1255751997232437,-0.6539164185523987,0.7432151436805725,-0.07687515765428543,-0.13784410059452057,-0.6314657926559448,0.7591707110404968,-0.14155341684818268,-0.10298306494951248,-0.58461993932724,0.7921973466873169,-0.24687646329402924,-0.07203217595815659,-0.5603971481323242,0.7872855067253113,-0.33640164136886597,-0.05141282081604004,-0.5506334900856018,0.7622292637825012,-0.36594557762145996,-0.022147277370095253,-0.525648295879364,0.7676504850387573,-0.3309614360332489,-0.019587896764278412,-0.4781332314014435,0.8133077621459961,-0.21967242658138275,-0.0006682267994619906,-0.34807366132736206,0.9113662242889404,-0.10980532318353653,0.00451840553432703,-0.26256266236305237,0.9586361050605774,-0.06743422150611877,-0.017896533012390137,-0.39176326990127563,0.9174169898033142,-0.01675652712583542,-0.0017209062352776527,-0.4326765537261963,0.901391863822937,0.05258610472083092,0.17292632162570953,-0.35799911618232727,0.9160610437393188,0.13760504126548767,0.41424432396888733,-0.3659992218017578,0.8218948245048523,0.1964171826839447,0.6344086527824402,-0.31640592217445374,0.6773723363876343,0.2569445073604584,0.6307422518730164,-0.41047731041908264,0.6063432693481445,0.29737982153892517,0.5263598561286926,-0.5783427953720093,0.5477501153945923,0.28651586174964905,0.44674623012542725,-0.6759189963340759,0.5113316178321838,0.298702210187912,0.37864142656326294,-0.7239463925361633,0.4932638704776764,0.3006775975227356,0.2831423282623291,-0.7796108722686768,0.47077634930610657,0.27623575925827026,0.20779311656951904,-0.8289698362350464,0.43968716263771057,0.23991654813289642,0.17899878323078156,-0.8503444790840149,0.4327976703643799,0.2096354067325592,0.16876351833343506,-0.8578792810440063,0.4377385675907135,0.18802043795585632,0.17271339893341064,-0.8612618446350098,0.43937045335769653,0.16773521900177002,0.17983245849609375,-0.8656642436981201,0.43606263399124146,0.1323232352733612,0.18278737366199493,-0.870811939239502,0.4367675185203552,0.09579765796661377,0.1847013384103775,-0.8733083009719849,0.4405005872249603,0.04872250556945801,0.18091906607151031,-0.8722050786018372,0.45183268189430237,-0.003310766303911805,0.17704422771930695,-0.8607411980628967,0.47725149989128113,-0.04891306161880493,0.175066277384758,-0.8401718735694885,0.5109506249427795,-0.07949742674827576,0.1782684624195099,-0.8086537718772888,0.5549591183662415,-0.10382712632417679,0.17790548503398895,-0.7629812359809875,0.6127228140830994,-0.09460336714982986,0.1586829274892807,-0.7155974507331848,0.6736395955085754,-0.08206099271774292,0.13228096067905426,-0.6628077626228333,0.7324299812316895,-0.0642479732632637,0.10128898173570633,-0.6183534264564514,0.776692807674408,-0.04552526772022247,0.06643699109554291,-0.6029991507530212,0.793665885925293,-0.025982335209846497,0.027998855337500572,-0.6049976348876953,0.7953105568885803,-0.005017536226660013,-0.010532907210290432,-0.6087805032730103,0.7932529449462891,0.013913423754274845,-0.044371578842401505,-0.6129496693611145,0.7887523770332336,0.028040587902069092,-0.07251132279634476,-0.6201868653297424,0.780592143535614,0.03715050593018532,-0.09037547558546066,-0.6243532299995422,0.7750065326690674,0.03927474468946457,-0.09597032517194748,-0.626589298248291,0.7724202871322632],"type":"quaternion"},{"name":"RightForeArm.scale","times":[0.03333333507180214,3.299999952316284],"values":[1.0000001192092896,1,0.9999999403953552,0.9999999403953552,1,0.9999999403953552],"interpolation":2300,"type":"vector"},{"name":"RightHand.position","times":[0.03333333507180214,3.299999952316284],"values":[0.000004387733952171402,19.450502395629883,-7.372896106971893e-7,-0.000013373769434110727,19.450502395629883,-0.000005012591373088071],"interpolation":2300,"type":"vector"},{"name":"RightHand.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.14937162399291992,-0.14748121798038483,-0.019200144335627556,0.9775319695472717,0.14937162399291992,-0.14748120307922363,-0.019200151786208153,0.9775319695472717],"interpolation":2300,"type":"quaternion"},{"name":"RightHand.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.9999998211860657,0.9999999403953552,0.9999998807907104,0.9999997615814209,0.9999999403953552,0.9999998211860657],"interpolation":2300,"type":"vector"},{"name":"neck.position","times":[0.03333333507180214,3.299999952316284],"values":[0.14690189063549042,6.409504413604736,0.25809943675994873,0.14690807461738586,6.409515857696533,0.2581029236316681],"type":"vector"},{"name":"neck.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.020259665325284004,0.012515295296907425,-0.011196219362318516,0.999653697013855,0.020259661599993706,0.012515277601778507,-0.011196223087608814,0.999653697013855],"type":"quaternion"},{"name":"neck.scale","times":[0.03333333507180214,3.299999952316284],"values":[1.0000001192092896,1.0000003576278687,1.0000001192092896,1.0000001192092896,1.0000003576278687,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"Head.position","times":[0.03333333507180214,3.299999952316284],"values":[4.215277442654042e-7,6.8159003257751465,0.0000014611794085794827,0.000011532042663020547,6.815900802612305,0.000005439934284368064],"type":"vector"},{"name":"Head.quaternion","times":[0.03333333507180214,0.06666667014360428,0.10000000149011612,0.13333334028720856,0.1666666716337204,0.20000000298023224,0.23333333432674408,0.2666666805744171,0.30000001192092896,0.3333333432674408,0.36666667461395264,0.4000000059604645,0.4333333373069763,0.46666666865348816,0.5,0.5333333611488342,0.5666666626930237,0.6000000238418579,0.6333333253860474,0.6666666865348816,0.699999988079071,0.7333333492279053,0.7666666507720947,0.800000011920929,0.8333333134651184,0.8666666746139526,0.8999999761581421,0.9333333373069763,0.9666666388511658,1,1.0333333015441895,1.0666667222976685,1.100000023841858,1.1333333253860474,1.1666666269302368,1.2000000476837158,1.2333333492279053,1.2666666507720947,1.2999999523162842,1.3333333730697632,1.3666666746139526,1.399999976158142,1.4333332777023315,1.4666666984558105,1.5,1.5333333015441895,1.5666667222976685,1.600000023841858,1.6333333253860474,1.6666666269302368,1.7000000476837158,1.7333333492279053,1.7666666507720947,1.7999999523162842,1.8333333730697632,1.8666666746139526,1.899999976158142,1.9333332777023315,1.9666666984558105,2,2.0333333015441895,2.066666603088379,2.0999999046325684,2.133333444595337,2.1666667461395264,2.200000047683716,2.2333333492279053,2.2666666507720947,2.299999952316284,2.3333332538604736,2.366666555404663,2.4000000953674316,2.433333396911621,2.4666666984558105,2.5,2.5333333015441895,2.566666603088379,2.5999999046325684,2.633333444595337,2.6666667461395264,2.700000047683716,2.7333333492279053,2.7666666507720947,2.799999952316284,2.8333332538604736,2.866666555404663,2.9000000953674316,2.933333396911621,2.9666666984558105,3,3.0333333015441895,3.066666603088379,3.0999999046325684,3.133333444595337,3.1666667461395264,3.200000047683716,3.2333333492279053,3.2666666507720947,3.299999952316284],"values":[0.2581385374069214,0.4480721652507782,-0.04013020172715187,0.8549768328666687,0.26203787326812744,0.4471874535083771,-0.04461192712187767,0.8540312051773071,0.2628006637096405,0.44822850823402405,-0.04840044677257538,0.8530442118644714,0.2625731825828552,0.44975024461746216,-0.05231998488306999,0.8520813584327698,0.26181721687316895,0.45170530676841736,-0.058095358312129974,0.8509048819541931,0.2589874863624573,0.45495879650115967,-0.06358447670936584,0.8496440649032593,0.25414934754371643,0.4566497802734375,-0.06701666861772537,0.8499340415000916,0.24607586860656738,0.45185986161231995,-0.06716602295637131,0.8548439145088196,0.23400753736495972,0.4424315094947815,-0.06080922111868858,0.8635954856872559,0.21880348026752472,0.4332590103149414,-0.05200660228729248,0.8727582693099976,0.20081226527690887,0.4208903908729553,-0.04047827422618866,0.8836782574653625,0.18016572296619415,0.4049796760082245,-0.028073381632566452,0.8959596157073975,0.15921562910079956,0.38266581296920776,-0.0164483729749918,0.9099158048629761,0.1398533582687378,0.35181236267089844,-0.0022076943423599005,0.9255616068840027,0.12746955454349518,0.3114125430583954,0.008649409748613834,0.941646933555603,0.1305396854877472,0.26010948419570923,0.008374994620680809,0.9566777348518372,0.1366925835609436,0.2025120109319687,0.00006694104376947507,0.9696927666664124,0.13935813307762146,0.14839133620262146,-0.023605432361364365,0.9787757992744446,0.1456366926431656,0.101973757147789,-0.06254211813211441,0.982079267501831,0.16239358484745026,0.062309667468070984,-0.08014049381017685,0.9814903736114502,0.18309476971626282,0.036890607327222824,-0.09453634172677994,0.977843701839447,0.21116197109222412,0.02450423873960972,-0.10352814942598343,0.9716439843177795,0.25178200006484985,0.01694496162235737,-0.10400981456041336,0.9620294570922852,0.28344789147377014,0.01942550763487816,-0.09321615099906921,0.9542487859725952,0.30828577280044556,0.03641736879944801,-0.079949751496315,0.9472284317016602,0.33764857053756714,0.03700651600956917,-0.05567449331283569,0.9388952851295471,0.3573630154132843,0.038658808916807175,-0.020636670291423798,0.93293696641922,0.3682038486003876,0.03867323324084282,0.01921459659934044,0.9287416934967041,0.37627696990966797,0.019449720159173012,0.060781460255384445,0.9243067502975464,0.3797833323478699,-0.0058896224945783615,0.08750806003808975,0.9209083914756775,0.3797425329685211,-0.031533412635326385,0.1021265983581543,0.9188968539237976,0.3763396143913269,-0.05309811979532242,0.10787639766931534,0.9186467528343201,0.37089893221855164,-0.08240386843681335,0.11054788529872894,0.9183804988861084,0.3594852685928345,-0.12028967589139938,0.11715495586395264,0.9179190993309021,0.34758591651916504,-0.15954682230949402,0.1289120614528656,0.914937436580658,0.3324006497859955,-0.19862651824951172,0.14375977218151093,0.9107087850570679,0.31631895899772644,-0.23295985162258148,0.15571628510951996,0.9063246846199036,0.29829156398773193,-0.26824259757995605,0.14702220261096954,0.9041308164596558,0.2866581380367279,-0.300665944814682,0.12392135709524155,0.9011496305465698,0.2639777958393097,-0.3406004309654236,0.1366482675075531,0.8919833898544312,0.24608467519283295,-0.35981136560440063,0.1434628665447235,0.8884799480438232,0.22632606327533722,-0.36494627594947815,0.12953774631023407,0.8937621116638184,0.21836085617542267,-0.3679720461368561,0.11824321746826172,0.8960656523704529,0.2116093635559082,-0.372419148683548,0.1014275923371315,0.897907555103302,0.20295566320419312,-0.3827471137046814,0.0930701345205307,0.8964661955833435,0.19807499647140503,-0.41612595319747925,0.08373915404081345,0.8835119009017944,0.2121099829673767,-0.43501853942871094,0.06939119845628738,0.8723262548446655,0.21356026828289032,-0.42175546288490295,0.03756727650761604,0.8803994059562683,0.19093847274780273,-0.3956115245819092,0.006970600225031376,0.8983237147331238,0.1626870483160019,-0.37102010846138,0.001157929189503193,0.9142623543739319,0.1508573442697525,-0.3481523096561432,0.018470684066414833,0.9250355958938599,0.12662722170352936,-0.317747563123703,0.039572324603796005,0.9388483166694641,0.0889638364315033,-0.2949987053871155,0.06135394796729088,0.949366569519043,0.0601608045399189,-0.28309881687164307,0.08553942292928696,0.9533722996711731,0.04789986461400986,-0.27926063537597656,0.10635162889957428,0.9531046152114868,0.042817696928977966,-0.27713313698768616,0.1201215386390686,0.9523311853408813,0.040079787373542786,-0.2755224406719208,0.12743037939071655,0.951967716217041,0.04358135908842087,-0.2666936218738556,0.13660858571529388,0.9530547261238098,0.0531623549759388,-0.2386816442012787,0.14578083157539368,0.9586202502250671,0.06788618862628937,-0.19584618508815765,0.15077447891235352,0.9665933847427368,0.08899134397506714,-0.14460469782352448,0.15012510120868683,0.9739776849746704,0.11385016143321991,-0.07111562788486481,0.14174525439739227,0.9807594418525696,0.144828662276268,0.03364937752485275,0.12033124268054962,0.9815359115600586,0.1791335940361023,0.1633589118719101,0.08379713445901871,0.9665418267250061,0.21558620035648346,0.2937425971031189,0.03342927247285843,0.9306558966636658,0.24905598163604736,0.4142155051231384,-0.02284739352762699,0.8751426339149475,0.25689950585365295,0.49947646260261536,-0.05752536654472351,0.8253586292266846,0.24671632051467896,0.5630176067352295,-0.05915302038192749,0.7865387797355652,0.23843656480312347,0.5835559964179993,-0.042967114597558975,0.7750898003578186,0.23571015894412994,0.5661962032318115,-0.021301880478858948,0.7895624041557312,0.2496427744626999,0.5512265563011169,-0.010986458510160446,0.7960572242736816,0.2619877755641937,0.5672777891159058,-0.00933802966028452,0.7806863784790039,0.2671985328197479,0.5844721794128418,-0.011012116447091103,0.7660782933235168,0.2798087000846863,0.5766503810882568,-0.01784600503742695,0.7673740386962891,0.29448816180229187,0.5577917695045471,-0.023670056834816933,0.7756190299987793,0.29797032475471497,0.5439680814743042,-0.02364787831902504,0.7840619683265686,0.29826268553733826,0.525897204875946,-0.02288154512643814,0.7962085008621216,0.29786428809165955,0.5044698715209961,-0.02338826097548008,0.8100864291191101,0.2940361797809601,0.4845309555530548,-0.02024724893271923,0.8236277103424072,0.28534793853759766,0.4734072983264923,-0.01270342618227005,0.8332470655441284,0.2776243984699249,0.46522611379623413,-0.003814985044300556,0.8405205607414246,0.26750126481056213,0.45846882462501526,0.0032376789022237062,0.8474897742271423,0.2520988881587982,0.45344582200050354,0.007817587815225124,0.854852020740509,0.23501849174499512,0.4497027099132538,0.004173699300736189,0.8616939187049866,0.22002582252025604,0.4481094181537628,-0.007561400532722473,0.8664464354515076,0.20827646553516388,0.44865164160728455,-0.02436175011098385,0.8687572479248047,0.20135906338691711,0.4530123770236969,-0.04067298024892807,0.8675137162208557,0.20200185477733612,0.45896342396736145,-0.05033115670084953,0.8637213706970215,0.20831181108951569,0.46301114559173584,-0.05528206005692482,0.8597504496574402,0.21481429040431976,0.46492624282836914,-0.05673972889780998,0.8570175170898438,0.22269470989704132,0.46584099531173706,-0.05529071018099785,0.8546006083488464,0.23165416717529297,0.46610063314437866,-0.054213836789131165,0.8521428108215332,0.23879723250865936,0.46426668763160706,-0.053604960441589355,0.8512102365493774,0.2444622963666916,0.4614468216896057,-0.05270225554704666,0.8511918187141418,0.24917533993721008,0.45786404609680176,-0.05055537447333336,0.8518898487091064,0.2526666522026062,0.45426225662231445,-0.04749593883752823,0.8529651165008545,0.25504159927368164,0.4513722062110901,-0.04423860087990761,0.8539671301841736,0.2567451298236847,0.45019662380218506,-0.04188971593976021,0.8541956543922424,0.25808319449424744,0.4481407105922699,-0.04003389924764633,0.854962170124054],"type":"quaternion"},{"name":"Head.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1,1,1,0.9999998211860657,1],"type":"vector"},{"name":"head_end.position","times":[0.03333333507180214,3.299999952316284],"values":[0.5746042728424072,51.81688690185547,-20.52269172668457,0.5746014714241028,51.81689453125,-20.52269172668457],"interpolation":2300,"type":"vector"},{"name":"head_end.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-0.18743081390857697,8.349547897523735e-8,-0.0052477894350886345,0.9822638034820557,-0.18743081390857697,8.468065715305784e-8,-0.005247787572443485,0.9822638034820557],"interpolation":2300,"type":"quaternion"},{"name":"head_end.scale","times":[0.03333333507180214,3.299999952316284],"values":[1.000000238418579,1.000000238418579,1.0000001192092896,1.0000001192092896,1.000000238418579,1.0000001192092896],"interpolation":2300,"type":"vector"},{"name":"headfront.position","times":[0.03333333507180214,3.299999952316284],"values":[-0.5745984315872192,4.330285549163818,20.52271270751953,-0.5746077299118042,4.330268383026123,20.522703170776367],"interpolation":2300,"type":"vector"},{"name":"headfront.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[0.6296830773353577,-8.993627176323571e-8,0.017631003633141518,0.7766520380973816,0.6296830773353577,-7.674562141346541e-8,0.01763097569346428,0.7766520380973816],"interpolation":2300,"type":"quaternion"},{"name":"headfront.scale","times":[0.03333333507180214,3.299999952316284],"values":[1,1.0000003576278687,1.000000238418579,1,1.0000004768371582,1.0000003576278687],"interpolation":2300,"type":"vector"},{"name":"Armature.position","times":[0.03333333507180214,3.299999952316284],"values":[0,0,0,0,0,0],"interpolation":2300,"type":"vector"},{"name":"Armature.quaternion","times":[0.03333333507180214,3.299999952316284],"values":[-2.1855694143368964e-8,0,0,1,-2.1855694143368964e-8,0,0,1],"interpolation":2300,"type":"quaternion"},{"name":"Armature.scale","times":[0.03333333507180214,3.299999952316284],"values":[0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582,0.009999999776482582],"interpolation":2300,"type":"vector"}],"uuid":"3959efd8-2674-4593-9715-dde7180c0978","blendMode":2500,"userData":"{}"} \ No newline at end of file diff --git a/public/anims/raw/11upward-thrust.glb b/public/anims/raw/11upward-thrust.glb new file mode 100644 index 0000000..5dcfda5 Binary files /dev/null and b/public/anims/raw/11upward-thrust.glb differ diff --git a/public/anims/raw/dropto-target.glb b/public/anims/raw/dropto-target.glb new file mode 100644 index 0000000..a5b7169 Binary files /dev/null and b/public/anims/raw/dropto-target.glb differ diff --git a/public/anims/raw/dual-weapon-combo.glb b/public/anims/raw/dual-weapon-combo.glb new file mode 100644 index 0000000..f893112 Binary files /dev/null and b/public/anims/raw/dual-weapon-combo.glb differ diff --git a/public/anims/raw/greataxe.glb b/public/anims/raw/greataxe.glb new file mode 100644 index 0000000..e894760 Binary files /dev/null and b/public/anims/raw/greataxe.glb differ diff --git a/public/anims/raw/insword-and-shield-slash.glb b/public/anims/raw/insword-and-shield-slash.glb new file mode 100644 index 0000000..5546903 Binary files /dev/null and b/public/anims/raw/insword-and-shield-slash.glb differ diff --git a/public/anims/raw/intoout.glb b/public/anims/raw/intoout.glb new file mode 100644 index 0000000..5546903 Binary files /dev/null and b/public/anims/raw/intoout.glb differ diff --git a/public/anims/raw/knocked-up.glb b/public/anims/raw/knocked-up.glb new file mode 100644 index 0000000..6672830 Binary files /dev/null and b/public/anims/raw/knocked-up.glb differ diff --git a/public/anims/raw/one-hand-sword-combo.glb b/public/anims/raw/one-hand-sword-combo.glb new file mode 100644 index 0000000..5e66048 Binary files /dev/null and b/public/anims/raw/one-hand-sword-combo.glb differ diff --git a/public/anims/raw/spear1.glb b/public/anims/raw/spear1.glb new file mode 100644 index 0000000..334f558 Binary files /dev/null and b/public/anims/raw/spear1.glb differ diff --git a/public/anims/raw/st1able-sword-inward-slash.glb b/public/anims/raw/st1able-sword-inward-slash.glb new file mode 100644 index 0000000..76aa018 Binary files /dev/null and b/public/anims/raw/st1able-sword-inward-slash.glb differ diff --git a/public/anims/raw/sword-and-shield-slash.glb b/public/anims/raw/sword-and-shield-slash.glb new file mode 100644 index 0000000..10de651 Binary files /dev/null and b/public/anims/raw/sword-and-shield-slash.glb differ diff --git a/public/anims/raw/two-hand-sword-combo.glb b/public/anims/raw/two-hand-sword-combo.glb new file mode 100644 index 0000000..dfa4a8d Binary files /dev/null and b/public/anims/raw/two-hand-sword-combo.glb differ diff --git a/public/api/v1/casting-lab-contract.json b/public/api/v1/casting-lab-contract.json new file mode 100644 index 0000000..070095f --- /dev/null +++ b/public/api/v1/casting-lab-contract.json @@ -0,0 +1,60 @@ +{ + "id": "casting-lab", + "version": "1.2.0", + "productHost": "casting.grudge.studio", + "vercelHost": "casting-abilities-threejs.vercel.app", + "three": "^0.185", + "rapier": "@dimforge/rapier3d-compat", + "loaders": { + "draco": "https://www.gstatic.com/draco/versioned/decoders/1.5.7/", + "ktx2": "https://cdn.jsdelivr.net/npm/three@0.185.1/examples/jsm/libs/basis/", + "meshopt": true, + "entry": "src/loaders/gltfPipeline.js", + "sharedApi": "sharedGltfLoader | bindKtx2 | AssetLoader.bindRenderer" + }, + "skills": { + "catalog": "master-weaponSkills + t0-weapons", + "production": "src/combat/weaponSkillProduction.js", + "statuses": "src/combat/skillStatusSystem.js", + "linear": "src/combat/elementalLinearCast.js", + "overrides": "public/skills/production/.json", + "staffNormal": "slot 1 + focus LMB" + }, + "terrain": { + "layers": [ + "L0_height", + "L1_surface", + "L2_vegetation", + "L3_detail", + "water" + ], + "sample": "IslandHeightfield.sample", + "doc": "docs/THREE_LAYER_TERRAIN_SSOT.md" + }, + "character": { + "playKit": "toonKitPlay / loadRaceKit", + "mixer": 1, + "siHumanM": 1.8 + }, + "export": { + "module": "src/sdk/castingLabSdk.js", + "packageExport": "./sdk", + "contractJson": "/api/v1/casting-lab-contract.json" + }, + "vfxMeshes": { + "orbs": "public/models/vfx/orbs/orb-*.glb", + "charge": "public/models/vfx/charge/staff-charge.glb", + "rocks": "public/models/vfx/rocks/rock-0..7.glb", + "arrows": "public/models/vfx/arrows/arrow-path.glb|arrow-loft.glb" + }, + "docs": [ + "docs/CASTING_LAB_SSOT.md", + "docs/CASTING_SDK_EXPORT_SSOT.md", + "docs/WEAPON_SKILL_PRODUCTION_SSOT.md", + "docs/LOADER_DRACO_KTX2_AUDIT.md", + "docs/SYSTEMS_HEALTH_AUDIT.md", + "docs/THREE_LAYER_TERRAIN_SSOT.md", + "docs/SSOT_INDEX.md" + ], + "masterMerge": "1.2.0" +} diff --git a/public/api/v1/casting-master-contract.json b/public/api/v1/casting-master-contract.json new file mode 100644 index 0000000..9f0d45b --- /dev/null +++ b/public/api/v1/casting-master-contract.json @@ -0,0 +1,63 @@ +{ + "id": "grudge-casting-master", + "version": "1.2.0", + "updated": "2026-08-11", + "sources": [ + "https://github.com/MolochDaGod/LinearAbiltyCastingThreeJS", + "https://github.com/MolochDaGod/CastingAbilitiesThreeJS" + ], + "terrainRef": "https://simonstorlschulke.github.io/threejs-examples/?scene=0", + "description": "Mastered Linear skillshots + Casting path/VFX + three-layer islands for Grudge Builder / info.grudge-studio.com", + "linearSkillshots": { + "ice": { "name": "Frost Lance", "shape": "line", "hotkey": "Q" }, + "thunder": { "name": "Storm Lance", "shape": "line", "hotkey": "E" }, + "meteor": { "name": "Cinder Fall", "shape": "line", "hotkey": "R" }, + "beam": { "name": "Nova Beam", "shape": "line", "hotkey": "F" }, + "snare": { "name": "Voltaic Snare", "shape": "zone", "hotkey": "V" }, + "glacier": { "name": "Glacier Wall", "shape": "zone", "hotkey": "G" } + }, + "pathElements": ["fire", "water", "earth", "wind"], + "productToLinear": { + "ice": "ice", + "storm": "thunder", + "fire": "meteor", + "holy": "beam", + "arcane": "snare", + "nature": "glacier" + }, + "terrainLayers": ["L0_height", "L1_surface", "L2_vegetation", "L3_detail", "water"], + "glsl": { + "common": "client/src/island3d/casting/shaders/lib/common.glsl.js", + "noise": "client/src/island3d/casting/shaders/lib/noise.glsl.js", + "materials": [ + "IceMaterial", + "LightningMaterial", + "MeteorMaterial", + "BeamMaterial", + "SnareMaterial", + "GlacierMaterial", + "FrostFieldMaterial", + "VolumetricFireMaterial", + "TrailMaterial", + "WindMaterial", + "OceanWaterMaterial" + ] + }, + "runtime": { + "CastingMaster": "client/src/island3d/casting/CastingMaster.ts", + "WorldFxBus": "client/src/island3d/vfx/WorldFxBus.ts", + "SpellFxSystem": "client/src/island3d/vfx/SpellFxSystem.ts", + "catalog": "shared/casting/masterCatalog.ts" + }, + "hosts": { + "castingLab": "https://casting.grudge.studio", + "info": "https://info.grudge-studio.com", + "builder": "https://client.grudge-studio.com", + "weaponSkills": "https://weapon-skills.grudge-studio.com", + "animAi": "https://anim-ai-worker.grudge.workers.dev" + }, + "endpoints": { + "contract": "/api/v1/casting-master-contract.json", + "labContract": "/api/v1/casting-lab-contract.json" + } +} diff --git a/public/api/v1/fishing-catalog.json b/public/api/v1/fishing-catalog.json new file mode 100644 index 0000000..6789126 --- /dev/null +++ b/public/api/v1/fishing-catalog.json @@ -0,0 +1,26 @@ +{ + "source": "casting-lab-fishing", + "version": "1.1.0", + "profession": "fishing", + "controls": { + "aim": "RMB toggle", + "cast": "LMB", + "snag": "S or RMB in bite window", + "reel": "mouse wheel down", + "slack": "mouse wheel up", + "zoneRight": "LMB hold", + "zoneLeft": "RMB hold", + "start": "Shift+F with pole equipped" + }, + "poles": [ + { "id": "t0-fishing-pole", "tier": 0, "mesh": "./models/fish/poles/fishing_rod.glb" }, + { "id": "fishing-pole-t1", "tier": 1, "mesh": "./models/fish/poles/fishing_rod-0yar0lg58p.glb" }, + { "id": "fishing-pole-t2", "tier": 2, "mesh": "./models/fish/poles/fishing_rod-9aohhrphe7.glb" }, + { "id": "fishing-pole-t3", "tier": 3, "mesh": "./models/fish/poles/fishing_rod-aoabqwh68m.glb" }, + { "id": "fishing-pole-t4", "tier": 4, "mesh": "./models/fish/poles/fishing_rod-ldlwqjn9zg.glb" }, + { "id": "fishing-pole-t5", "tier": 5, "mesh": "./models/fish/poles/fishing_rod-ldlwqjn9zg.glb" } + ], + "buildables": ["dock_long", "dock_wide", "dock_stairs", "fishing_boat", "fish_bucket"], + "assetsRoot": "./models/fish/", + "seeAlso": "./fishing-profession.json" +} diff --git a/public/api/v1/fishing-profession.json b/public/api/v1/fishing-profession.json new file mode 100644 index 0000000..a273a89 --- /dev/null +++ b/public/api/v1/fishing-profession.json @@ -0,0 +1,195 @@ +{ + "source": "casting-lab-fishing", + "version": "1.1.0", + "profession": "fishing", + "kind": "gathering", + "fleetAlign": { + "objectStore": "https://info.grudge-studio.com/api/v1/professions.json", + "gatheringKey": "gathering.Fishing", + "feedsInto": ["Chef", "Mystic"], + "chefFoodSystem": "ONE meal each Red / Green / Blue (SWG-style)" + }, + "controls": { + "start": "Shift+F", + "aim": "RMB toggle", + "cast": "LMB", + "snag": "S or RMB in bite window", + "reel": "mouse wheel down", + "slack": "mouse wheel up", + "zoneRight": "LMB hold", + "zoneLeft": "RMB hold", + "panel": "Inventory · Professions · Fishing" + }, + "rods": [ + { + "id": "t0-fishing-pole", + "tier": 0, + "label": "Novice Stick Pole", + "family": "shore", + "mesh": "./models/fish/poles/fishing_rod.glb", + "power": 1.0, + "control": 1.0, + "lineStrength": 0.5, + "castRangeM": 12, + "nauticalSpeedMul": 1.0, + "abilities": ["steady_hand"] + }, + { + "id": "fishing-pole-t1", + "tier": 1, + "label": "Shore Angler T1", + "family": "shore", + "mesh": "./models/fish/poles/fishing_rod-0yar0lg58p.glb", + "power": 1.12, + "control": 1.08, + "lineStrength": 0.58, + "castRangeM": 15, + "nauticalSpeedMul": 1.02, + "abilities": ["steady_hand", "quick_snag"] + }, + { + "id": "fishing-pole-t2", + "tier": 2, + "label": "River Angler T2", + "family": "river", + "mesh": "./models/fish/poles/fishing_rod-9aohhrphe7.glb", + "power": 1.25, + "control": 1.18, + "lineStrength": 0.68, + "castRangeM": 18, + "nauticalSpeedMul": 1.05, + "abilities": ["steady_hand", "quick_snag", "deep_cast"] + }, + { + "id": "fishing-pole-t3", + "tier": 3, + "label": "Sea Angler T3", + "family": "sea", + "mesh": "./models/fish/poles/fishing_rod-aoabqwh68m.glb", + "power": 1.4, + "control": 1.28, + "lineStrength": 0.78, + "castRangeM": 24, + "nauticalSpeedMul": 1.08, + "abilities": ["steady_hand", "quick_snag", "deep_cast", "iron_line"] + }, + { + "id": "fishing-pole-t4", + "tier": 4, + "label": "Deep Sea Rod T4", + "family": "deep", + "mesh": "./models/fish/poles/fishing_rod-ldlwqjn9zg.glb", + "power": 1.55, + "control": 1.35, + "lineStrength": 0.86, + "castRangeM": 30, + "nauticalSpeedMul": 1.12, + "abilities": ["steady_hand", "quick_snag", "deep_cast", "iron_line", "sea_legs"] + }, + { + "id": "fishing-pole-t5", + "tier": 5, + "label": "Abyss Angler T5", + "family": "void", + "mesh": "./models/fish/poles/fishing_rod-ldlwqjn9zg.glb", + "power": 1.7, + "control": 1.45, + "lineStrength": 0.92, + "castRangeM": 36, + "nauticalSpeedMul": 1.15, + "abilities": ["steady_hand", "quick_snag", "deep_cast", "iron_line", "sea_legs", "void_line"] + } + ], + "rodAbilities": [ + "steady_hand", + "quick_snag", + "deep_cast", + "iron_line", + "sea_legs", + "void_line" + ], + "skillTree": [ + { "id": "fish_init", "label": "Fisher's Initiation", "requiresLevel": 1, "cost": 0, "bonuses": { "fishingUnlocked": 1 } }, + { "id": "fish_qty_1", "label": "Double Hook", "requiresLevel": 5, "cost": 1, "bonuses": { "catchQty": 1 } }, + { "id": "fish_bite_1", "label": "Patient Angler", "requiresLevel": 5, "cost": 1, "bonuses": { "biteMul": 1.15 } }, + { "id": "fish_zone_1", "label": "Wider Reel", "requiresLevel": 10, "cost": 1, "bonuses": { "zoneMul": 1.08 } }, + { "id": "nautical_1", "label": "Sea Legs I", "requiresLevel": 10, "cost": 1, "bonuses": { "nauticalSpeedMul": 1.05 } }, + { "id": "fish_line_1", "label": "Braided Line", "requiresLevel": 12, "cost": 1, "bonuses": { "lineMul": 1.1 } }, + { "id": "fish_qty_2", "label": "Net Haul", "requiresLevel": 20, "cost": 2, "bonuses": { "catchQty": 1 } }, + { "id": "nautical_2", "label": "Sea Legs II", "requiresLevel": 20, "cost": 2, "bonuses": { "nauticalSpeedMul": 1.08 } }, + { "id": "fish_rare_1", "label": "Lucky Tide", "requiresLevel": 22, "cost": 2, "bonuses": { "rareBias": 1.2 } }, + { "id": "fish_meal_link", "label": "Fisher's Kitchen", "requiresLevel": 25, "cost": 2, "bonuses": { "mealCraft": 1 } }, + { "id": "nautical_3", "label": "Tide Runner", "requiresLevel": 30, "cost": 2, "bonuses": { "nauticalSpeedMul": 1.1 } }, + { "id": "fish_master", "label": "Grandmaster Angler", "requiresLevel": 50, "cost": 3, "bonuses": { "catchQty": 2, "zoneMul": 1.12, "rareBias": 1.35, "legendaryBias": 1.25 } } + ], + "meals": { + "rule": "One active meal per color (Red · Green · Blue) — SWG / chef.foodSystem", + "recipes": [ + { "id": "meal_fish_stew", "color": "blue", "label": "Fish Stew", "nauticalSpeedMul": 1.03 }, + { "id": "meal_salmon_broth", "color": "blue", "label": "Salmon Broth", "nauticalSpeedMul": 1.05 }, + { "id": "meal_tuna_curry", "color": "blue", "label": "Tuna Curry", "nauticalSpeedMul": 1.08 }, + { "id": "meal_moonfish_soup", "color": "blue", "label": "Moonfish Soup", "nauticalSpeedMul": 1.12 }, + { "id": "meal_grilled_filet", "color": "red", "label": "Grilled Filet" }, + { "id": "meal_piranha_seared", "color": "red", "label": "Seared Piranha" }, + { "id": "meal_shore_salad", "color": "green", "label": "Shore Salad" }, + { "id": "meal_seaweed_wrap", "color": "green", "label": "Seaweed Wrap" } + ] + }, + "nauticalSpeed": { + "appliesTo": ["windsurf freeride", "boat", "swim (future)"], + "sources": ["skillTree nautical_1..3", "rod sea_legs / rod.nauticalSpeedMul", "blue meal buffs"], + "combine": "multiplicative" + }, + "sizeClasses": { + "tiny": { "rank": 0, "lengthM": [0.06, 0.14] }, + "small": { "rank": 1, "lengthM": [0.15, 0.35] }, + "medium": { "rank": 2, "lengthM": [0.4, 0.9] }, + "large": { "rank": 3, "lengthM": [1.0, 2.2] }, + "huge": { "rank": 4, "lengthM": [2.3, 4.5] }, + "titan": { "rank": 5, "lengthM": [5.0, 12] } + }, + "scale": { + "module": "src/fishing/fishScale.js", + "rule": "SI lengthM on longest axis; elongated longer-than-tall/wide" + }, + "gamePack": { + "sourceZip": "Animated Fish Bundle-glb (1).zip", + "path": "./models/fish/species/large/", + "species": [ + { "id": "fish_reef", "lengthM": 0.55, "sizeClass": "medium", "value": 14 }, + { "id": "fish_mid", "lengthM": 0.75, "sizeClass": "medium", "value": 22 }, + { "id": "fish_stream", "lengthM": 0.9, "sizeClass": "medium", "value": 30 }, + { "id": "dolphin", "lengthM": 2.4, "sizeClass": "huge", "value": 140 }, + { "id": "manta_ray", "lengthM": 2.8, "sizeClass": "huge", "value": 155 }, + { "id": "shark_game", "lengthM": 3.2, "sizeClass": "huge", "value": 220 }, + { "id": "whale", "lengthM": 8.5, "sizeClass": "titan", "value": 500 } + ] + }, + "lures": [ + { "id": "worm", "tier": 0, "sizeClass": ["tiny", "small"], "value": 1 }, + { "id": "lure_basic", "tier": 0, "sizeClass": ["tiny", "small", "medium"], "value": 3 }, + { "id": "lure_spinner", "tier": 1, "sizeClass": ["small", "medium"], "value": 8 }, + { "id": "lure_deep", "tier": 2, "sizeClass": ["medium", "large"], "value": 18 }, + { "id": "lure_heavy", "tier": 3, "sizeClass": ["large", "huge"], "value": 40 }, + { "id": "lure_game", "tier": 4, "sizeClass": ["large", "huge"], "value": 55 }, + { "id": "lure_titan", "tier": 5, "sizeClass": ["huge", "titan"], "value": 120 } + ], + "assets": { + "species": "./models/fish/species/", + "speciesLarge": "./models/fish/species/large/", + "poles": "./models/fish/poles/", + "lures": "./models/fish/lures/", + "docks": "./models/fish/docks/", + "uiBar": "./ui/fishing/" + }, + + "code": { + "rodTypes": "src/fishing/fishingRodTypes.js", + "skillTree": "src/fishing/fishingSkillTree.js", + "meals": "src/fishing/mealBuffs.js", + "state": "src/fishing/professionState.js", + "controller": "src/fishing/FishingController.js", + "catalog": "src/fishing/fishingCatalog.js" + }, + "storageKey": "grudge.casting.fishing.profession.v1" +} diff --git a/public/audio/sfx/README.md b/public/audio/sfx/README.md new file mode 100644 index 0000000..a889360 --- /dev/null +++ b/public/audio/sfx/README.md @@ -0,0 +1,18 @@ +# Casting lab skill SFX + +User-provided combat / cast one-shots wired by `src/audio/skillSfx.js`. + +| File | Role | Used for | +|------|------|----------| +| `cast-ramp.wav` | `cast_ramp` | **Default** cast start · path cast · light residual | +| `cast-chant.wav` | `cast_chant` | **Only** explicit blood/shaman/void skill ids (not normal spells) | +| `parry.wav` | `parry` | Melee **parry attempt** (C) — metal | +| `parry-magic.wav` | `parry_magic` | **Magical** parry attempt — staff/wand/ward | +| `impact-magic-a/b/c.wav` | `impact_magic` | Spell land / residual hit (random) | +| `burn.wav` | `burn` | Soft **loop** while player has burn status (not fire impact) | +| `heal-a.wav` / `heal-b.wav` | `heal` | Heal tonic · holy (random variant) | + +**Do not** wire `cast_chant` on every long cast / arcane / storm — it reads as a death moan. +Default spell path = `cast_ramp` → impact = `impact_magic`. + +Runtime: `SkillSfx.play(role)` · auto on weapon skill cast / path cast / impact. diff --git a/public/audio/sfx/burn.wav b/public/audio/sfx/burn.wav new file mode 100644 index 0000000..335b0a7 Binary files /dev/null and b/public/audio/sfx/burn.wav differ diff --git a/public/audio/sfx/cast-chant.wav b/public/audio/sfx/cast-chant.wav new file mode 100644 index 0000000..1f2b1a8 Binary files /dev/null and b/public/audio/sfx/cast-chant.wav differ diff --git a/public/audio/sfx/cast-ramp.wav b/public/audio/sfx/cast-ramp.wav new file mode 100644 index 0000000..c9e375c Binary files /dev/null and b/public/audio/sfx/cast-ramp.wav differ diff --git a/public/audio/sfx/heal-a.wav b/public/audio/sfx/heal-a.wav new file mode 100644 index 0000000..8515408 Binary files /dev/null and b/public/audio/sfx/heal-a.wav differ diff --git a/public/audio/sfx/heal-b.wav b/public/audio/sfx/heal-b.wav new file mode 100644 index 0000000..138479a Binary files /dev/null and b/public/audio/sfx/heal-b.wav differ diff --git a/public/audio/sfx/impact-magic-a.wav b/public/audio/sfx/impact-magic-a.wav new file mode 100644 index 0000000..d413208 Binary files /dev/null and b/public/audio/sfx/impact-magic-a.wav differ diff --git a/public/audio/sfx/impact-magic-b.wav b/public/audio/sfx/impact-magic-b.wav new file mode 100644 index 0000000..a30b814 Binary files /dev/null and b/public/audio/sfx/impact-magic-b.wav differ diff --git a/public/audio/sfx/impact-magic-c.wav b/public/audio/sfx/impact-magic-c.wav new file mode 100644 index 0000000..ef112b4 Binary files /dev/null and b/public/audio/sfx/impact-magic-c.wav differ diff --git a/public/audio/sfx/parry-magic.wav b/public/audio/sfx/parry-magic.wav new file mode 100644 index 0000000..47c6989 Binary files /dev/null and b/public/audio/sfx/parry-magic.wav differ diff --git a/public/audio/sfx/parry.wav b/public/audio/sfx/parry.wav new file mode 100644 index 0000000..b7917f6 Binary files /dev/null and b/public/audio/sfx/parry.wav differ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..a70db4f Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..2a7262e --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/hud-tight-bar.png b/public/hud-tight-bar.png new file mode 100644 index 0000000..7bba9c7 Binary files /dev/null and b/public/hud-tight-bar.png differ diff --git a/public/hud/bars/README.md b/public/hud/bars/README.md new file mode 100644 index 0000000..2a24210 --- /dev/null +++ b/public/hud/bars/README.md @@ -0,0 +1,12 @@ +# Bars HUD (Casting product picks) + +Source: D:\Games\Models\bars-hud-pack + +| Role | File | +|------|------| +| Player unit frame | unit-frames/unit_frame_009.png | +| Enemy overhead HP | overhead/overhead_health_003.png | +| Ally overhead HP | overhead/overhead_health_001.png | +| Fills | fillers/health_fill_* · mana_fill_* · stamina_fill_* | + +Code SSOT: src/ui/barsHudUi.js diff --git a/public/hud/bars/fillers/health_fill_001.png b/public/hud/bars/fillers/health_fill_001.png new file mode 100644 index 0000000..4b10c67 Binary files /dev/null and b/public/hud/bars/fillers/health_fill_001.png differ diff --git a/public/hud/bars/fillers/health_fill_010.png b/public/hud/bars/fillers/health_fill_010.png new file mode 100644 index 0000000..a093014 Binary files /dev/null and b/public/hud/bars/fillers/health_fill_010.png differ diff --git a/public/hud/bars/fillers/health_fill_015.png b/public/hud/bars/fillers/health_fill_015.png new file mode 100644 index 0000000..b80e139 Binary files /dev/null and b/public/hud/bars/fillers/health_fill_015.png differ diff --git a/public/hud/bars/fillers/mana_fill_001.png b/public/hud/bars/fillers/mana_fill_001.png new file mode 100644 index 0000000..a1b30f0 Binary files /dev/null and b/public/hud/bars/fillers/mana_fill_001.png differ diff --git a/public/hud/bars/fillers/mana_fill_008.png b/public/hud/bars/fillers/mana_fill_008.png new file mode 100644 index 0000000..c630d41 Binary files /dev/null and b/public/hud/bars/fillers/mana_fill_008.png differ diff --git a/public/hud/bars/fillers/stamina_fill_001.png b/public/hud/bars/fillers/stamina_fill_001.png new file mode 100644 index 0000000..310d365 Binary files /dev/null and b/public/hud/bars/fillers/stamina_fill_001.png differ diff --git a/public/hud/bars/fillers/stamina_fill_007.png b/public/hud/bars/fillers/stamina_fill_007.png new file mode 100644 index 0000000..ce35d17 Binary files /dev/null and b/public/hud/bars/fillers/stamina_fill_007.png differ diff --git a/public/hud/bars/overhead/overhead_health_001.png b/public/hud/bars/overhead/overhead_health_001.png new file mode 100644 index 0000000..01c2492 Binary files /dev/null and b/public/hud/bars/overhead/overhead_health_001.png differ diff --git a/public/hud/bars/overhead/overhead_health_003.png b/public/hud/bars/overhead/overhead_health_003.png new file mode 100644 index 0000000..3c11175 Binary files /dev/null and b/public/hud/bars/overhead/overhead_health_003.png differ diff --git a/public/hud/bars/unit-frames/unit_frame_009.png b/public/hud/bars/unit-frames/unit_frame_009.png new file mode 100644 index 0000000..068b40e Binary files /dev/null and b/public/hud/bars/unit-frames/unit_frame_009.png differ diff --git a/public/hud/health_globe.png b/public/hud/health_globe.png new file mode 100644 index 0000000..2889cee Binary files /dev/null and b/public/hud/health_globe.png differ diff --git a/public/hud/mana_globe.png b/public/hud/mana_globe.png new file mode 100644 index 0000000..f87e75c Binary files /dev/null and b/public/hud/mana_globe.png differ diff --git a/public/icons/dev-island/CREDIT.txt b/public/icons/dev-island/CREDIT.txt new file mode 100644 index 0000000..7cccdb3 --- /dev/null +++ b/public/icons/dev-island/CREDIT.txt @@ -0,0 +1,4 @@ +Dev-island icon pack +- Dreamland Minerals (see source License.txt) +- Retro Inventory (Retr3o) see pack Read Me +Rocks mesh: Rocks.zip FBX+atlas ? models/dev-island/ diff --git a/public/icons/dev-island/inventory/Health_01.png b/public/icons/dev-island/inventory/Health_01.png new file mode 100644 index 0000000..709447a Binary files /dev/null and b/public/icons/dev-island/inventory/Health_01.png differ diff --git a/public/icons/dev-island/inventory/Health_01_Bar01.png b/public/icons/dev-island/inventory/Health_01_Bar01.png new file mode 100644 index 0000000..b0478b2 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_01_Bar01.png differ diff --git a/public/icons/dev-island/inventory/Health_01_Bar02.png b/public/icons/dev-island/inventory/Health_01_Bar02.png new file mode 100644 index 0000000..7b06a7a Binary files /dev/null and b/public/icons/dev-island/inventory/Health_01_Bar02.png differ diff --git a/public/icons/dev-island/inventory/Health_01_Bar03.png b/public/icons/dev-island/inventory/Health_01_Bar03.png new file mode 100644 index 0000000..1816161 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_01_Bar03.png differ diff --git a/public/icons/dev-island/inventory/Health_02.png b/public/icons/dev-island/inventory/Health_02.png new file mode 100644 index 0000000..a9b1683 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_02.png differ diff --git a/public/icons/dev-island/inventory/Health_02_Bar01.png b/public/icons/dev-island/inventory/Health_02_Bar01.png new file mode 100644 index 0000000..d3e7394 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_02_Bar01.png differ diff --git a/public/icons/dev-island/inventory/Health_02_Bar02.png b/public/icons/dev-island/inventory/Health_02_Bar02.png new file mode 100644 index 0000000..0cf6a5c Binary files /dev/null and b/public/icons/dev-island/inventory/Health_02_Bar02.png differ diff --git a/public/icons/dev-island/inventory/Health_02_Bar03.png b/public/icons/dev-island/inventory/Health_02_Bar03.png new file mode 100644 index 0000000..ecf367f Binary files /dev/null and b/public/icons/dev-island/inventory/Health_02_Bar03.png differ diff --git a/public/icons/dev-island/inventory/Health_03.png b/public/icons/dev-island/inventory/Health_03.png new file mode 100644 index 0000000..7bef017 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_03.png differ diff --git a/public/icons/dev-island/inventory/Health_03_Bar01.png b/public/icons/dev-island/inventory/Health_03_Bar01.png new file mode 100644 index 0000000..7826cc4 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_03_Bar01.png differ diff --git a/public/icons/dev-island/inventory/Health_03_Bar02.png b/public/icons/dev-island/inventory/Health_03_Bar02.png new file mode 100644 index 0000000..b7e3915 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_03_Bar02.png differ diff --git a/public/icons/dev-island/inventory/Health_03_Bar03.png b/public/icons/dev-island/inventory/Health_03_Bar03.png new file mode 100644 index 0000000..d5bab14 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_03_Bar03.png differ diff --git a/public/icons/dev-island/inventory/Health_04.png b/public/icons/dev-island/inventory/Health_04.png new file mode 100644 index 0000000..147afb3 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Bar01.png b/public/icons/dev-island/inventory/Health_04_Bar01.png new file mode 100644 index 0000000..f8e3996 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Bar01.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Bar02.png b/public/icons/dev-island/inventory/Health_04_Bar02.png new file mode 100644 index 0000000..80edbab Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Bar02.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Bar03.png b/public/icons/dev-island/inventory/Health_04_Bar03.png new file mode 100644 index 0000000..b8a8255 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Bar03.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Bar04.png b/public/icons/dev-island/inventory/Health_04_Bar04.png new file mode 100644 index 0000000..934daee Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Bar04.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Bar05.png b/public/icons/dev-island/inventory/Health_04_Bar05.png new file mode 100644 index 0000000..bdb1871 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Bar05.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Bar06.png b/public/icons/dev-island/inventory/Health_04_Bar06.png new file mode 100644 index 0000000..7d6becd Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Bar06.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Heart_Blue.png b/public/icons/dev-island/inventory/Health_04_Heart_Blue.png new file mode 100644 index 0000000..6b77340 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Heart_Blue.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Heart_Blue_Clear.png b/public/icons/dev-island/inventory/Health_04_Heart_Blue_Clear.png new file mode 100644 index 0000000..ba7bd10 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Heart_Blue_Clear.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Heart_Red.png b/public/icons/dev-island/inventory/Health_04_Heart_Red.png new file mode 100644 index 0000000..c1c6a0e Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Heart_Red.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Heart_Red_Clear.png b/public/icons/dev-island/inventory/Health_04_Heart_Red_Clear.png new file mode 100644 index 0000000..2487ce3 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Heart_Red_Clear.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Heart_Yellow.png b/public/icons/dev-island/inventory/Health_04_Heart_Yellow.png new file mode 100644 index 0000000..9efe57d Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Heart_Yellow.png differ diff --git a/public/icons/dev-island/inventory/Health_04_Heart_Yellow_Clear.png b/public/icons/dev-island/inventory/Health_04_Heart_Yellow_Clear.png new file mode 100644 index 0000000..f0a23b5 Binary files /dev/null and b/public/icons/dev-island/inventory/Health_04_Heart_Yellow_Clear.png differ diff --git a/public/icons/dev-island/inventory/Health_05_01.png b/public/icons/dev-island/inventory/Health_05_01.png new file mode 100644 index 0000000..8ec0dcb Binary files /dev/null and b/public/icons/dev-island/inventory/Health_05_01.png differ diff --git a/public/icons/dev-island/inventory/Health_05_02.png b/public/icons/dev-island/inventory/Health_05_02.png new file mode 100644 index 0000000..1ff5c3e Binary files /dev/null and b/public/icons/dev-island/inventory/Health_05_02.png differ diff --git a/public/icons/dev-island/inventory/Health_05_03.png b/public/icons/dev-island/inventory/Health_05_03.png new file mode 100644 index 0000000..efa503e Binary files /dev/null and b/public/icons/dev-island/inventory/Health_05_03.png differ diff --git a/public/icons/dev-island/inventory/Heart_Blue.png b/public/icons/dev-island/inventory/Heart_Blue.png new file mode 100644 index 0000000..c723a0d Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Blue.png differ diff --git a/public/icons/dev-island/inventory/Heart_Blue_1.png b/public/icons/dev-island/inventory/Heart_Blue_1.png new file mode 100644 index 0000000..0559a5d Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Blue_1.png differ diff --git a/public/icons/dev-island/inventory/Heart_Blue_2.png b/public/icons/dev-island/inventory/Heart_Blue_2.png new file mode 100644 index 0000000..23601c7 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Blue_2.png differ diff --git a/public/icons/dev-island/inventory/Heart_Blue_3.png b/public/icons/dev-island/inventory/Heart_Blue_3.png new file mode 100644 index 0000000..bfe8a38 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Blue_3.png differ diff --git a/public/icons/dev-island/inventory/Heart_Blue_4.png b/public/icons/dev-island/inventory/Heart_Blue_4.png new file mode 100644 index 0000000..3e3afc6 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Blue_4.png differ diff --git a/public/icons/dev-island/inventory/Heart_Orange.png b/public/icons/dev-island/inventory/Heart_Orange.png new file mode 100644 index 0000000..3bf7513 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Orange.png differ diff --git a/public/icons/dev-island/inventory/Heart_Orange_1.png b/public/icons/dev-island/inventory/Heart_Orange_1.png new file mode 100644 index 0000000..7a960fe Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Orange_1.png differ diff --git a/public/icons/dev-island/inventory/Heart_Orange_2.png b/public/icons/dev-island/inventory/Heart_Orange_2.png new file mode 100644 index 0000000..f493f45 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Orange_2.png differ diff --git a/public/icons/dev-island/inventory/Heart_Orange_3.png b/public/icons/dev-island/inventory/Heart_Orange_3.png new file mode 100644 index 0000000..39d0c64 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Orange_3.png differ diff --git a/public/icons/dev-island/inventory/Heart_Orange_4.png b/public/icons/dev-island/inventory/Heart_Orange_4.png new file mode 100644 index 0000000..c11a477 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Orange_4.png differ diff --git a/public/icons/dev-island/inventory/Heart_Red.png b/public/icons/dev-island/inventory/Heart_Red.png new file mode 100644 index 0000000..b218732 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Red.png differ diff --git a/public/icons/dev-island/inventory/Heart_Red_1.png b/public/icons/dev-island/inventory/Heart_Red_1.png new file mode 100644 index 0000000..874859c Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Red_1.png differ diff --git a/public/icons/dev-island/inventory/Heart_Red_2.png b/public/icons/dev-island/inventory/Heart_Red_2.png new file mode 100644 index 0000000..a07d400 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Red_2.png differ diff --git a/public/icons/dev-island/inventory/Heart_Red_3.png b/public/icons/dev-island/inventory/Heart_Red_3.png new file mode 100644 index 0000000..ec626ce Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Red_3.png differ diff --git a/public/icons/dev-island/inventory/Heart_Red_4.png b/public/icons/dev-island/inventory/Heart_Red_4.png new file mode 100644 index 0000000..957c8d0 Binary files /dev/null and b/public/icons/dev-island/inventory/Heart_Red_4.png differ diff --git a/public/icons/dev-island/inventory/Hearts.png b/public/icons/dev-island/inventory/Hearts.png new file mode 100644 index 0000000..40fdddc Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Blue_1.png b/public/icons/dev-island/inventory/Hearts_Blue_1.png new file mode 100644 index 0000000..d65ec2d Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Blue_1.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Blue_2.png b/public/icons/dev-island/inventory/Hearts_Blue_2.png new file mode 100644 index 0000000..984f09e Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Blue_2.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Blue_3.png b/public/icons/dev-island/inventory/Hearts_Blue_3.png new file mode 100644 index 0000000..4dbc216 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Blue_3.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Blue_4.png b/public/icons/dev-island/inventory/Hearts_Blue_4.png new file mode 100644 index 0000000..9099284 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Blue_4.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Blue_5.png b/public/icons/dev-island/inventory/Hearts_Blue_5.png new file mode 100644 index 0000000..03987ff Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Blue_5.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Red_1.png b/public/icons/dev-island/inventory/Hearts_Red_1.png new file mode 100644 index 0000000..9d96a42 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Red_1.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Red_2.png b/public/icons/dev-island/inventory/Hearts_Red_2.png new file mode 100644 index 0000000..7147409 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Red_2.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Red_3.png b/public/icons/dev-island/inventory/Hearts_Red_3.png new file mode 100644 index 0000000..eebcf6d Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Red_3.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Red_4.png b/public/icons/dev-island/inventory/Hearts_Red_4.png new file mode 100644 index 0000000..0203554 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Red_4.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Red_5.png b/public/icons/dev-island/inventory/Hearts_Red_5.png new file mode 100644 index 0000000..03987ff Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Red_5.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Yellow_1.png b/public/icons/dev-island/inventory/Hearts_Yellow_1.png new file mode 100644 index 0000000..75e283f Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Yellow_1.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Yellow_2.png b/public/icons/dev-island/inventory/Hearts_Yellow_2.png new file mode 100644 index 0000000..3893ab6 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Yellow_2.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Yellow_3.png b/public/icons/dev-island/inventory/Hearts_Yellow_3.png new file mode 100644 index 0000000..5c18573 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Yellow_3.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Yellow_4.png b/public/icons/dev-island/inventory/Hearts_Yellow_4.png new file mode 100644 index 0000000..cebd850 Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Yellow_4.png differ diff --git a/public/icons/dev-island/inventory/Hearts_Yellow_5.png b/public/icons/dev-island/inventory/Hearts_Yellow_5.png new file mode 100644 index 0000000..03987ff Binary files /dev/null and b/public/icons/dev-island/inventory/Hearts_Yellow_5.png differ diff --git a/public/icons/dev-island/inventory/Inventory.png b/public/icons/dev-island/inventory/Inventory.png new file mode 100644 index 0000000..61be73c Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory.png differ diff --git a/public/icons/dev-island/inventory/Inventory_9Slices.png b/public/icons/dev-island/inventory/Inventory_9Slices.png new file mode 100644 index 0000000..60b10b7 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_9Slices.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Example_01.png b/public/icons/dev-island/inventory/Inventory_Example_01.png new file mode 100644 index 0000000..f2f6b4a Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Example_01.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Example_02.png b/public/icons/dev-island/inventory/Inventory_Example_02.png new file mode 100644 index 0000000..b3de71c Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Example_02.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Example_03.png b/public/icons/dev-island/inventory/Inventory_Example_03.png new file mode 100644 index 0000000..2293e91 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Example_03.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Example_04.png b/public/icons/dev-island/inventory/Inventory_Example_04.png new file mode 100644 index 0000000..8fd2830 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Example_04.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_1.png b/public/icons/dev-island/inventory/Inventory_Slot_1.png new file mode 100644 index 0000000..5e9b605 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_1.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_10.png b/public/icons/dev-island/inventory/Inventory_Slot_10.png new file mode 100644 index 0000000..0426bf3 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_10.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_2.png b/public/icons/dev-island/inventory/Inventory_Slot_2.png new file mode 100644 index 0000000..a6f9d72 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_2.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_3.png b/public/icons/dev-island/inventory/Inventory_Slot_3.png new file mode 100644 index 0000000..5d127da Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_3.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_4.png b/public/icons/dev-island/inventory/Inventory_Slot_4.png new file mode 100644 index 0000000..d8de4c6 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_4.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_5.png b/public/icons/dev-island/inventory/Inventory_Slot_5.png new file mode 100644 index 0000000..33ad839 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_5.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_6.png b/public/icons/dev-island/inventory/Inventory_Slot_6.png new file mode 100644 index 0000000..f276615 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_6.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_7.png b/public/icons/dev-island/inventory/Inventory_Slot_7.png new file mode 100644 index 0000000..e0d0c59 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_7.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_8.png b/public/icons/dev-island/inventory/Inventory_Slot_8.png new file mode 100644 index 0000000..b1bbb59 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_8.png differ diff --git a/public/icons/dev-island/inventory/Inventory_Slot_9.png b/public/icons/dev-island/inventory/Inventory_Slot_9.png new file mode 100644 index 0000000..dd5b4d7 Binary files /dev/null and b/public/icons/dev-island/inventory/Inventory_Slot_9.png differ diff --git a/public/icons/dev-island/inventory/Settings.png b/public/icons/dev-island/inventory/Settings.png new file mode 100644 index 0000000..d7638c1 Binary files /dev/null and b/public/icons/dev-island/inventory/Settings.png differ diff --git a/public/icons/dev-island/inventory/Settings_Bar01.png b/public/icons/dev-island/inventory/Settings_Bar01.png new file mode 100644 index 0000000..9c8e5e6 Binary files /dev/null and b/public/icons/dev-island/inventory/Settings_Bar01.png differ diff --git a/public/icons/dev-island/inventory/Settings_Bar02.png b/public/icons/dev-island/inventory/Settings_Bar02.png new file mode 100644 index 0000000..01ed45c Binary files /dev/null and b/public/icons/dev-island/inventory/Settings_Bar02.png differ diff --git a/public/icons/dev-island/inventory/Settings_Bar03.png b/public/icons/dev-island/inventory/Settings_Bar03.png new file mode 100644 index 0000000..3c25e18 Binary files /dev/null and b/public/icons/dev-island/inventory/Settings_Bar03.png differ diff --git a/public/icons/dev-island/inventory/Settings_Cross01.png b/public/icons/dev-island/inventory/Settings_Cross01.png new file mode 100644 index 0000000..28cdbdc Binary files /dev/null and b/public/icons/dev-island/inventory/Settings_Cross01.png differ diff --git a/public/icons/dev-island/inventory/Settings_Cross02.png b/public/icons/dev-island/inventory/Settings_Cross02.png new file mode 100644 index 0000000..5a16f97 Binary files /dev/null and b/public/icons/dev-island/inventory/Settings_Cross02.png differ diff --git a/public/icons/dev-island/inventory/Settings_Cross03.png b/public/icons/dev-island/inventory/Settings_Cross03.png new file mode 100644 index 0000000..3499599 Binary files /dev/null and b/public/icons/dev-island/inventory/Settings_Cross03.png differ diff --git a/public/icons/dev-island/minerals/FD_Minerals_Stones.png b/public/icons/dev-island/minerals/FD_Minerals_Stones.png new file mode 100644 index 0000000..c304c4e Binary files /dev/null and b/public/icons/dev-island/minerals/FD_Minerals_Stones.png differ diff --git a/public/maps/training_room/layout.default.json b/public/maps/training_room/layout.default.json new file mode 100644 index 0000000..9b8c82f --- /dev/null +++ b/public/maps/training_room/layout.default.json @@ -0,0 +1,360 @@ +{ + "version": 1, + "source": "casting-training-room-default", + "biomeId": "temperate_meadow", + "terrain": { + "seed": 17, + "amp": 0.85, + "flatCore": 8, + "segments": 96, + "grid": 65 + }, + "nodes": [ + { + "id": "tr_h_0", + "paletteId": "node.rock_pebbles", + "x": 4.697411069414715, + "z": 1.9860335457741178, + "yaw": 0.7, + "scale": 0.85 + }, + { + "id": "tr_h_1", + "paletteId": "node.herb_patch", + "x": 1.6632220930479318, + "z": 4.278059404589569, + "yaw": 1.5, + "scale": 1 + }, + { + "id": "tr_h_2", + "paletteId": "node.rock_pebbles", + "x": -2.832186646805201, + "z": 4.842604546900182, + "yaw": 2.4, + "scale": 0.85 + }, + { + "id": "tr_h_3", + "paletteId": "node.herb_patch", + "x": -4.930670782155252, + "z": -1.3032596203368394, + "yaw": 3.6999999999999997, + "scale": 1 + }, + { + "id": "tr_h_4", + "paletteId": "node.rock_boulder", + "x": 1.7360125750349447, + "z": -5.868616560938407, + "yaw": 5.3, + "scale": 1 + }, + { + "id": "tr_h_5", + "paletteId": "node.rock_pebbles", + "x": 4.29034205958069, + "z": -2.2509975592596545, + "yaw": 6.1, + "scale": 0.85 + }, + { + "id": "tr_h_6", + "paletteId": "node.rock_pebbles", + "x": 10.996347003378732, + "z": 2.229069891520587, + "yaw": 0.5, + "scale": 0.85 + }, + { + "id": "tr_h_7", + "paletteId": "node.herb_patch", + "x": 4.626680438540889, + "z": 9.090315072626643, + "yaw": 1.4000000000000001, + "scale": 1 + }, + { + "id": "tr_h_8", + "paletteId": "node.rock_pebbles", + "x": -5.093637279337023, + "z": 11.129800504346344, + "yaw": 2.3, + "scale": 0.85 + }, + { + "id": "tr_h_9", + "paletteId": "node.herb_patch", + "x": -10.60281963859077, + "z": 1.5113952863211777, + "yaw": 3.3, + "scale": 1 + }, + { + "id": "tr_h_10", + "paletteId": "node.rock_pebbles", + "x": -5.750759434326405, + "z": -10.22358381041139, + "yaw": 4.5, + "scale": 0.85 + }, + { + "id": "tr_h_11", + "paletteId": "node.herb_patch", + "x": 4.778870047263847, + "z": -9.011237488345563, + "yaw": 5.5, + "scale": 1 + }, + { + "id": "tr_h_12", + "paletteId": "node.rock_boulder", + "x": 17.007550049435423, + "z": 9.291266938149453, + "yaw": 0.8, + "scale": 1 + }, + { + "id": "tr_h_13", + "paletteId": "node.rock_boulder", + "x": -0.5956702549462919, + "z": 20.391301502046705, + "yaw": 1.9000000000000001, + "scale": 1 + }, + { + "id": "tr_h_14", + "paletteId": "node.rock_boulder", + "x": -17.059841319861945, + "z": 8.064658340012368, + "yaw": 3, + "scale": 1 + }, + { + "id": "tr_h_15", + "paletteId": "node.rock_boulder", + "x": -16.539134856130456, + "z": -12.793948499612252, + "yaw": 4.1, + "scale": 1 + }, + { + "id": "tr_h_16", + "paletteId": "node.rock_boulder", + "x": 3.709731027815032, + "z": -19.540982465097972, + "yaw": 5.2, + "scale": 1 + }, + { + "id": "tr_h_17", + "paletteId": "node.rock_boulder", + "x": 18.064598145602908, + "z": -9.47788446004065, + "yaw": 6.1, + "scale": 1 + }, + { + "id": "tr_h_18", + "paletteId": "node.rock_ore", + "x": 18.38722286144625, + "z": 23.17080998678096, + "yaw": 1.2, + "scale": 0.95 + }, + { + "id": "tr_h_19", + "paletteId": "node.rock_ore", + "x": -20.388046251162613, + "z": 22.81857949260764, + "yaw": 2.5999999999999996, + "scale": 0.95 + }, + { + "id": "tr_h_20", + "paletteId": "node.rock_ore", + "x": -26.745202989025145, + "z": -10.018368982815543, + "yaw": 3.8, + "scale": 0.95 + }, + { + "id": "tr_h_21", + "paletteId": "node.rock_ore", + "x": -0.37277488359837696, + "z": -30.087690820103795, + "yaw": 5, + "scale": 0.95 + }, + { + "id": "tr_h_22", + "paletteId": "node.rock_ore", + "x": 26.96179798172912, + "z": -10.86859464661496, + "yaw": 6.2, + "scale": 0.95 + }, + { + "id": "tr_h_23", + "paletteId": "node.rock_boulder", + "x": 9.822556987094847, + "z": 35.381856568519325, + "yaw": 1.6, + "scale": 1 + }, + { + "id": "tr_h_24", + "paletteId": "node.rock_boulder", + "x": -23.335077264830943, + "z": -27.017849082493033, + "yaw": 4.3, + "scale": 1 + }, + { + "id": "tr_h_25", + "paletteId": "node.rock_ore", + "x": -35.55947113683516, + "z": 12.64245278688386, + "yaw": 3.0999999999999996, + "scale": 0.95 + }, + { + "id": "tr_d_26", + "paletteId": "node.cliff_arch", + "x": 39.952171975232844, + "z": 12.35865504257722, + "yaw": 0.4, + "scale": 1.4 + }, + { + "id": "tr_d_27", + "paletteId": "node.cliff_column", + "x": 23.42210495938386, + "z": 36.477767191422316, + "yaw": 1.2, + "scale": 1.2 + }, + { + "id": "tr_d_28", + "paletteId": "node.cliff_wall", + "x": -9.269845463477955, + "z": 39.73298333983037, + "yaw": 0.1, + "scale": 1.5 + }, + { + "id": "tr_d_29", + "paletteId": "node.cliff_wall", + "x": -37.58314072276203, + "z": 22.609890168089418, + "yaw": 2, + "scale": 1.3 + }, + { + "id": "tr_d_30", + "paletteId": "node.cliff_wall", + "x": -40.92456749188859, + "z": -10.817054848795765, + "yaw": 0.7, + "scale": 1.6 + }, + { + "id": "tr_d_31", + "paletteId": "node.cliff_wall", + "x": -21.002773586235563, + "z": -37.33830609019812, + "yaw": 1.5, + "scale": 1.25 + }, + { + "id": "tr_d_32", + "paletteId": "node.cliff_wall", + "x": 11.718084881485877, + "z": -39.61316178633425, + "yaw": 0.2, + "scale": 1.1 + }, + { + "id": "tr_d_33", + "paletteId": "node.cliff_wall", + "x": 37.03620626331352, + "z": -24.43391752505718, + "yaw": 2.4, + "scale": 1.35 + }, + { + "id": "tr_d_34", + "paletteId": "node.cliff_arch", + "x": 27.71499289783024, + "z": 28.53642529598302, + "yaw": 1, + "scale": 1.15 + }, + { + "id": "tr_d_35", + "paletteId": "node.cliff_wall", + "x": -29.24781253622365, + "z": -27.710098553522304, + "yaw": 0.5, + "scale": 1.45 + }, + { + "id": "tr_p_36", + "paletteId": "node.pve_dummy", + "x": 9.18, + "z": 0, + "yaw": 3.141592653589793, + "scale": 1, + "label": "Dummy · North" + }, + { + "id": "tr_p_37", + "paletteId": "node.pve_dummy", + "x": 5.621128808086351e-16, + "z": 9.18, + "yaw": 4.71238898038469, + "scale": 1, + "label": "Dummy · East" + }, + { + "id": "tr_p_38", + "paletteId": "node.pve_dummy", + "x": -9.18, + "z": 1.1242257616172703e-15, + "yaw": 6.283185307179586, + "scale": 1, + "label": "Dummy · South" + }, + { + "id": "tr_p_39", + "paletteId": "node.pve_dummy", + "x": -1.6863386424259052e-15, + "z": -9.18, + "yaw": 7.853981633974483, + "scale": 1, + "label": "Dummy · West" + }, + { + "id": "tr_p_40", + "paletteId": "node.pve_dummy", + "x": 10.0974848353439, + "z": 10.097484835343899, + "yaw": 3.9269908169872414, + "scale": 1, + "label": "Dummy · NE" + }, + { + "id": "tr_p_41", + "paletteId": "node.pve_dummy", + "x": -10.097484835343902, + "z": -10.097484835343899, + "yaw": 7.0685834705770345, + "scale": 1, + "label": "Dummy · SW" + } + ], + "createdAt": "2026-08-11T02:17:51.285Z", + "mapId": "training_room", + "mapLabel": "Training Room · DevIsland", + "published": true +} \ No newline at end of file diff --git a/public/models/Standing Idle.fbx b/public/models/Standing Idle.fbx deleted file mode 100644 index 359a94c..0000000 Binary files a/public/models/Standing Idle.fbx and /dev/null differ diff --git a/public/models/dev-island/color_atlas.png b/public/models/dev-island/color_atlas.png new file mode 100644 index 0000000..c7f24aa Binary files /dev/null and b/public/models/dev-island/color_atlas.png differ diff --git a/public/models/dev-island/rock__debris1.glb b/public/models/dev-island/rock__debris1.glb new file mode 100644 index 0000000..a91fa1e Binary files /dev/null and b/public/models/dev-island/rock__debris1.glb differ diff --git a/public/models/dev-island/rock__debris1_dark.glb b/public/models/dev-island/rock__debris1_dark.glb new file mode 100644 index 0000000..a91fa1e Binary files /dev/null and b/public/models/dev-island/rock__debris1_dark.glb differ diff --git a/public/models/dev-island/rock__debris1_light.glb b/public/models/dev-island/rock__debris1_light.glb new file mode 100644 index 0000000..4e5fc9d Binary files /dev/null and b/public/models/dev-island/rock__debris1_light.glb differ diff --git a/public/models/dev-island/rock__debris1_medium.glb b/public/models/dev-island/rock__debris1_medium.glb new file mode 100644 index 0000000..fd7b67a Binary files /dev/null and b/public/models/dev-island/rock__debris1_medium.glb differ diff --git a/public/models/dev-island/rock__debris2.glb b/public/models/dev-island/rock__debris2.glb new file mode 100644 index 0000000..08e2074 Binary files /dev/null and b/public/models/dev-island/rock__debris2.glb differ diff --git a/public/models/dev-island/rock__debris2_dark.glb b/public/models/dev-island/rock__debris2_dark.glb new file mode 100644 index 0000000..08e2074 Binary files /dev/null and b/public/models/dev-island/rock__debris2_dark.glb differ diff --git a/public/models/dev-island/rock__debris2_light.glb b/public/models/dev-island/rock__debris2_light.glb new file mode 100644 index 0000000..703f0f9 Binary files /dev/null and b/public/models/dev-island/rock__debris2_light.glb differ diff --git a/public/models/dev-island/rock__debris2_medium.glb b/public/models/dev-island/rock__debris2_medium.glb new file mode 100644 index 0000000..12d8266 Binary files /dev/null and b/public/models/dev-island/rock__debris2_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles1.glb b/public/models/dev-island/rock__pebbles1.glb new file mode 100644 index 0000000..c03088b Binary files /dev/null and b/public/models/dev-island/rock__pebbles1.glb differ diff --git a/public/models/dev-island/rock__pebbles1_caves_dark.glb b/public/models/dev-island/rock__pebbles1_caves_dark.glb new file mode 100644 index 0000000..ee432b3 Binary files /dev/null and b/public/models/dev-island/rock__pebbles1_caves_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles1_caves_light.glb b/public/models/dev-island/rock__pebbles1_caves_light.glb new file mode 100644 index 0000000..0b58ef6 Binary files /dev/null and b/public/models/dev-island/rock__pebbles1_caves_light.glb differ diff --git a/public/models/dev-island/rock__pebbles1_caves_medium.glb b/public/models/dev-island/rock__pebbles1_caves_medium.glb new file mode 100644 index 0000000..c03088b Binary files /dev/null and b/public/models/dev-island/rock__pebbles1_caves_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles1_dirt_dark.glb b/public/models/dev-island/rock__pebbles1_dirt_dark.glb new file mode 100644 index 0000000..a2e453e Binary files /dev/null and b/public/models/dev-island/rock__pebbles1_dirt_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles1_dirt_light.glb b/public/models/dev-island/rock__pebbles1_dirt_light.glb new file mode 100644 index 0000000..3b59241 Binary files /dev/null and b/public/models/dev-island/rock__pebbles1_dirt_light.glb differ diff --git a/public/models/dev-island/rock__pebbles1_dirt_medium.glb b/public/models/dev-island/rock__pebbles1_dirt_medium.glb new file mode 100644 index 0000000..cca0544 Binary files /dev/null and b/public/models/dev-island/rock__pebbles1_dirt_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles2.glb b/public/models/dev-island/rock__pebbles2.glb new file mode 100644 index 0000000..c8ecf8a Binary files /dev/null and b/public/models/dev-island/rock__pebbles2.glb differ diff --git a/public/models/dev-island/rock__pebbles2_caves_dark.glb b/public/models/dev-island/rock__pebbles2_caves_dark.glb new file mode 100644 index 0000000..7e9f924 Binary files /dev/null and b/public/models/dev-island/rock__pebbles2_caves_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles2_caves_light.glb b/public/models/dev-island/rock__pebbles2_caves_light.glb new file mode 100644 index 0000000..bbb6b2d Binary files /dev/null and b/public/models/dev-island/rock__pebbles2_caves_light.glb differ diff --git a/public/models/dev-island/rock__pebbles2_caves_medium.glb b/public/models/dev-island/rock__pebbles2_caves_medium.glb new file mode 100644 index 0000000..c8ecf8a Binary files /dev/null and b/public/models/dev-island/rock__pebbles2_caves_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles2_dirt_dark.glb b/public/models/dev-island/rock__pebbles2_dirt_dark.glb new file mode 100644 index 0000000..4d609a5 Binary files /dev/null and b/public/models/dev-island/rock__pebbles2_dirt_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles2_dirt_light.glb b/public/models/dev-island/rock__pebbles2_dirt_light.glb new file mode 100644 index 0000000..6da8a66 Binary files /dev/null and b/public/models/dev-island/rock__pebbles2_dirt_light.glb differ diff --git a/public/models/dev-island/rock__pebbles2_dirt_medium.glb b/public/models/dev-island/rock__pebbles2_dirt_medium.glb new file mode 100644 index 0000000..c9fb0c3 Binary files /dev/null and b/public/models/dev-island/rock__pebbles2_dirt_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles3.glb b/public/models/dev-island/rock__pebbles3.glb new file mode 100644 index 0000000..cd3c74f Binary files /dev/null and b/public/models/dev-island/rock__pebbles3.glb differ diff --git a/public/models/dev-island/rock__pebbles3_caves_dark.glb b/public/models/dev-island/rock__pebbles3_caves_dark.glb new file mode 100644 index 0000000..49a0d84 Binary files /dev/null and b/public/models/dev-island/rock__pebbles3_caves_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles3_caves_light.glb b/public/models/dev-island/rock__pebbles3_caves_light.glb new file mode 100644 index 0000000..66ec3d8 Binary files /dev/null and b/public/models/dev-island/rock__pebbles3_caves_light.glb differ diff --git a/public/models/dev-island/rock__pebbles3_caves_medium.glb b/public/models/dev-island/rock__pebbles3_caves_medium.glb new file mode 100644 index 0000000..cd3c74f Binary files /dev/null and b/public/models/dev-island/rock__pebbles3_caves_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles3_dirt_dark.glb b/public/models/dev-island/rock__pebbles3_dirt_dark.glb new file mode 100644 index 0000000..71bd4ea Binary files /dev/null and b/public/models/dev-island/rock__pebbles3_dirt_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles3_dirt_light.glb b/public/models/dev-island/rock__pebbles3_dirt_light.glb new file mode 100644 index 0000000..e0977a9 Binary files /dev/null and b/public/models/dev-island/rock__pebbles3_dirt_light.glb differ diff --git a/public/models/dev-island/rock__pebbles3_dirt_medium.glb b/public/models/dev-island/rock__pebbles3_dirt_medium.glb new file mode 100644 index 0000000..6b1188a Binary files /dev/null and b/public/models/dev-island/rock__pebbles3_dirt_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles4.glb b/public/models/dev-island/rock__pebbles4.glb new file mode 100644 index 0000000..45d0f18 Binary files /dev/null and b/public/models/dev-island/rock__pebbles4.glb differ diff --git a/public/models/dev-island/rock__pebbles4_caves_dark.glb b/public/models/dev-island/rock__pebbles4_caves_dark.glb new file mode 100644 index 0000000..f071a15 Binary files /dev/null and b/public/models/dev-island/rock__pebbles4_caves_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles4_caves_light.glb b/public/models/dev-island/rock__pebbles4_caves_light.glb new file mode 100644 index 0000000..a552e60 Binary files /dev/null and b/public/models/dev-island/rock__pebbles4_caves_light.glb differ diff --git a/public/models/dev-island/rock__pebbles4_caves_medium.glb b/public/models/dev-island/rock__pebbles4_caves_medium.glb new file mode 100644 index 0000000..45d0f18 Binary files /dev/null and b/public/models/dev-island/rock__pebbles4_caves_medium.glb differ diff --git a/public/models/dev-island/rock__pebbles4_dirt_dark.glb b/public/models/dev-island/rock__pebbles4_dirt_dark.glb new file mode 100644 index 0000000..023ff4d Binary files /dev/null and b/public/models/dev-island/rock__pebbles4_dirt_dark.glb differ diff --git a/public/models/dev-island/rock__pebbles4_dirt_light.glb b/public/models/dev-island/rock__pebbles4_dirt_light.glb new file mode 100644 index 0000000..3534157 Binary files /dev/null and b/public/models/dev-island/rock__pebbles4_dirt_light.glb differ diff --git a/public/models/dev-island/rock__pebbles4_dirt_medium.glb b/public/models/dev-island/rock__pebbles4_dirt_medium.glb new file mode 100644 index 0000000..5882675 Binary files /dev/null and b/public/models/dev-island/rock__pebbles4_dirt_medium.glb differ diff --git a/public/models/dev-island/rock__rock1.glb b/public/models/dev-island/rock__rock1.glb new file mode 100644 index 0000000..79ce08a Binary files /dev/null and b/public/models/dev-island/rock__rock1.glb differ diff --git a/public/models/dev-island/rock__rock10.glb b/public/models/dev-island/rock__rock10.glb new file mode 100644 index 0000000..fb8d450 Binary files /dev/null and b/public/models/dev-island/rock__rock10.glb differ diff --git a/public/models/dev-island/rock__rock10_cave_dark.glb b/public/models/dev-island/rock__rock10_cave_dark.glb new file mode 100644 index 0000000..fb4764e Binary files /dev/null and b/public/models/dev-island/rock__rock10_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock10_cave_light.glb b/public/models/dev-island/rock__rock10_cave_light.glb new file mode 100644 index 0000000..a0452f9 Binary files /dev/null and b/public/models/dev-island/rock__rock10_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock10_cave_medium.glb b/public/models/dev-island/rock__rock10_cave_medium.glb new file mode 100644 index 0000000..fb8d450 Binary files /dev/null and b/public/models/dev-island/rock__rock10_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock10_natural_dark.glb b/public/models/dev-island/rock__rock10_natural_dark.glb new file mode 100644 index 0000000..3590ffc Binary files /dev/null and b/public/models/dev-island/rock__rock10_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock10_natural_light.glb b/public/models/dev-island/rock__rock10_natural_light.glb new file mode 100644 index 0000000..09ce49d Binary files /dev/null and b/public/models/dev-island/rock__rock10_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock10_natural_medium.glb b/public/models/dev-island/rock__rock10_natural_medium.glb new file mode 100644 index 0000000..84caa9d Binary files /dev/null and b/public/models/dev-island/rock__rock10_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock1_cave_dark.glb b/public/models/dev-island/rock__rock1_cave_dark.glb new file mode 100644 index 0000000..0311a68 Binary files /dev/null and b/public/models/dev-island/rock__rock1_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock1_cave_light.glb b/public/models/dev-island/rock__rock1_cave_light.glb new file mode 100644 index 0000000..e07418c Binary files /dev/null and b/public/models/dev-island/rock__rock1_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock1_cave_medium.glb b/public/models/dev-island/rock__rock1_cave_medium.glb new file mode 100644 index 0000000..79ce08a Binary files /dev/null and b/public/models/dev-island/rock__rock1_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock1_natural_dark.glb b/public/models/dev-island/rock__rock1_natural_dark.glb new file mode 100644 index 0000000..ae94302 Binary files /dev/null and b/public/models/dev-island/rock__rock1_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock1_natural_light.glb b/public/models/dev-island/rock__rock1_natural_light.glb new file mode 100644 index 0000000..67addaf Binary files /dev/null and b/public/models/dev-island/rock__rock1_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock1_natural_medium.glb b/public/models/dev-island/rock__rock1_natural_medium.glb new file mode 100644 index 0000000..ee539af Binary files /dev/null and b/public/models/dev-island/rock__rock1_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock2.glb b/public/models/dev-island/rock__rock2.glb new file mode 100644 index 0000000..e665ca4 Binary files /dev/null and b/public/models/dev-island/rock__rock2.glb differ diff --git a/public/models/dev-island/rock__rock2_cave_dark.glb b/public/models/dev-island/rock__rock2_cave_dark.glb new file mode 100644 index 0000000..ef79e71 Binary files /dev/null and b/public/models/dev-island/rock__rock2_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock2_cave_light.glb b/public/models/dev-island/rock__rock2_cave_light.glb new file mode 100644 index 0000000..b712fef Binary files /dev/null and b/public/models/dev-island/rock__rock2_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock2_cave_medium.glb b/public/models/dev-island/rock__rock2_cave_medium.glb new file mode 100644 index 0000000..e665ca4 Binary files /dev/null and b/public/models/dev-island/rock__rock2_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock2_natural_dark.glb b/public/models/dev-island/rock__rock2_natural_dark.glb new file mode 100644 index 0000000..fa3f079 Binary files /dev/null and b/public/models/dev-island/rock__rock2_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock2_natural_light.glb b/public/models/dev-island/rock__rock2_natural_light.glb new file mode 100644 index 0000000..0001b9d Binary files /dev/null and b/public/models/dev-island/rock__rock2_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock2_natural_medium.glb b/public/models/dev-island/rock__rock2_natural_medium.glb new file mode 100644 index 0000000..9456809 Binary files /dev/null and b/public/models/dev-island/rock__rock2_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock3.glb b/public/models/dev-island/rock__rock3.glb new file mode 100644 index 0000000..85de07b Binary files /dev/null and b/public/models/dev-island/rock__rock3.glb differ diff --git a/public/models/dev-island/rock__rock3_cave_dark.glb b/public/models/dev-island/rock__rock3_cave_dark.glb new file mode 100644 index 0000000..874b057 Binary files /dev/null and b/public/models/dev-island/rock__rock3_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock3_cave_light.glb b/public/models/dev-island/rock__rock3_cave_light.glb new file mode 100644 index 0000000..9d97605 Binary files /dev/null and b/public/models/dev-island/rock__rock3_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock3_cave_medium.glb b/public/models/dev-island/rock__rock3_cave_medium.glb new file mode 100644 index 0000000..85de07b Binary files /dev/null and b/public/models/dev-island/rock__rock3_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock3_natural_dark.glb b/public/models/dev-island/rock__rock3_natural_dark.glb new file mode 100644 index 0000000..1ca1adf Binary files /dev/null and b/public/models/dev-island/rock__rock3_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock3_natural_light.glb b/public/models/dev-island/rock__rock3_natural_light.glb new file mode 100644 index 0000000..74d233b Binary files /dev/null and b/public/models/dev-island/rock__rock3_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock3_natural_medium.glb b/public/models/dev-island/rock__rock3_natural_medium.glb new file mode 100644 index 0000000..e1a100b Binary files /dev/null and b/public/models/dev-island/rock__rock3_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock4.glb b/public/models/dev-island/rock__rock4.glb new file mode 100644 index 0000000..8ebf7b2 Binary files /dev/null and b/public/models/dev-island/rock__rock4.glb differ diff --git a/public/models/dev-island/rock__rock4_cave_dark.glb b/public/models/dev-island/rock__rock4_cave_dark.glb new file mode 100644 index 0000000..aea3cb0 Binary files /dev/null and b/public/models/dev-island/rock__rock4_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock4_cave_light.glb b/public/models/dev-island/rock__rock4_cave_light.glb new file mode 100644 index 0000000..18e36c4 Binary files /dev/null and b/public/models/dev-island/rock__rock4_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock4_cave_medium.glb b/public/models/dev-island/rock__rock4_cave_medium.glb new file mode 100644 index 0000000..8ebf7b2 Binary files /dev/null and b/public/models/dev-island/rock__rock4_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock4_natural_dark.glb b/public/models/dev-island/rock__rock4_natural_dark.glb new file mode 100644 index 0000000..daa926a Binary files /dev/null and b/public/models/dev-island/rock__rock4_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock4_natural_light.glb b/public/models/dev-island/rock__rock4_natural_light.glb new file mode 100644 index 0000000..f9ce422 Binary files /dev/null and b/public/models/dev-island/rock__rock4_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock4_natural_medium.glb b/public/models/dev-island/rock__rock4_natural_medium.glb new file mode 100644 index 0000000..323cb6a Binary files /dev/null and b/public/models/dev-island/rock__rock4_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock5.glb b/public/models/dev-island/rock__rock5.glb new file mode 100644 index 0000000..09fe701 Binary files /dev/null and b/public/models/dev-island/rock__rock5.glb differ diff --git a/public/models/dev-island/rock__rock5_cave_dark.glb b/public/models/dev-island/rock__rock5_cave_dark.glb new file mode 100644 index 0000000..50e38fc Binary files /dev/null and b/public/models/dev-island/rock__rock5_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock5_cave_light.glb b/public/models/dev-island/rock__rock5_cave_light.glb new file mode 100644 index 0000000..b66447e Binary files /dev/null and b/public/models/dev-island/rock__rock5_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock5_cave_medium.glb b/public/models/dev-island/rock__rock5_cave_medium.glb new file mode 100644 index 0000000..09fe701 Binary files /dev/null and b/public/models/dev-island/rock__rock5_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock5_natural_dark.glb b/public/models/dev-island/rock__rock5_natural_dark.glb new file mode 100644 index 0000000..850634f Binary files /dev/null and b/public/models/dev-island/rock__rock5_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock5_natural_light.glb b/public/models/dev-island/rock__rock5_natural_light.glb new file mode 100644 index 0000000..08ff59d Binary files /dev/null and b/public/models/dev-island/rock__rock5_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock5_natural_medium.glb b/public/models/dev-island/rock__rock5_natural_medium.glb new file mode 100644 index 0000000..e287b78 Binary files /dev/null and b/public/models/dev-island/rock__rock5_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock6.glb b/public/models/dev-island/rock__rock6.glb new file mode 100644 index 0000000..7803ee3 Binary files /dev/null and b/public/models/dev-island/rock__rock6.glb differ diff --git a/public/models/dev-island/rock__rock6_cave_dark.glb b/public/models/dev-island/rock__rock6_cave_dark.glb new file mode 100644 index 0000000..6e44e33 Binary files /dev/null and b/public/models/dev-island/rock__rock6_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock6_cave_light.glb b/public/models/dev-island/rock__rock6_cave_light.glb new file mode 100644 index 0000000..7e2bfc7 Binary files /dev/null and b/public/models/dev-island/rock__rock6_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock6_cave_medium.glb b/public/models/dev-island/rock__rock6_cave_medium.glb new file mode 100644 index 0000000..7803ee3 Binary files /dev/null and b/public/models/dev-island/rock__rock6_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock6_natural_dark.glb b/public/models/dev-island/rock__rock6_natural_dark.glb new file mode 100644 index 0000000..7adc4a7 Binary files /dev/null and b/public/models/dev-island/rock__rock6_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock6_natural_light.glb b/public/models/dev-island/rock__rock6_natural_light.glb new file mode 100644 index 0000000..cfd1df0 Binary files /dev/null and b/public/models/dev-island/rock__rock6_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock6_natural_medium.glb b/public/models/dev-island/rock__rock6_natural_medium.glb new file mode 100644 index 0000000..1e3acb8 Binary files /dev/null and b/public/models/dev-island/rock__rock6_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock7.glb b/public/models/dev-island/rock__rock7.glb new file mode 100644 index 0000000..b221b02 Binary files /dev/null and b/public/models/dev-island/rock__rock7.glb differ diff --git a/public/models/dev-island/rock__rock7_cave_dark.glb b/public/models/dev-island/rock__rock7_cave_dark.glb new file mode 100644 index 0000000..5644d5d Binary files /dev/null and b/public/models/dev-island/rock__rock7_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock7_cave_light.glb b/public/models/dev-island/rock__rock7_cave_light.glb new file mode 100644 index 0000000..5319d01 Binary files /dev/null and b/public/models/dev-island/rock__rock7_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock7_cave_medium.glb b/public/models/dev-island/rock__rock7_cave_medium.glb new file mode 100644 index 0000000..b221b02 Binary files /dev/null and b/public/models/dev-island/rock__rock7_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock7_natural_dark.glb b/public/models/dev-island/rock__rock7_natural_dark.glb new file mode 100644 index 0000000..a5203a2 Binary files /dev/null and b/public/models/dev-island/rock__rock7_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock7_natural_light.glb b/public/models/dev-island/rock__rock7_natural_light.glb new file mode 100644 index 0000000..e13f323 Binary files /dev/null and b/public/models/dev-island/rock__rock7_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock7_natural_medium.glb b/public/models/dev-island/rock__rock7_natural_medium.glb new file mode 100644 index 0000000..293fa68 Binary files /dev/null and b/public/models/dev-island/rock__rock7_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock8.glb b/public/models/dev-island/rock__rock8.glb new file mode 100644 index 0000000..db38a0a Binary files /dev/null and b/public/models/dev-island/rock__rock8.glb differ diff --git a/public/models/dev-island/rock__rock8_cave_dark.glb b/public/models/dev-island/rock__rock8_cave_dark.glb new file mode 100644 index 0000000..cd92430 Binary files /dev/null and b/public/models/dev-island/rock__rock8_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock8_cave_light.glb b/public/models/dev-island/rock__rock8_cave_light.glb new file mode 100644 index 0000000..e0337b9 Binary files /dev/null and b/public/models/dev-island/rock__rock8_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock8_cave_medium.glb b/public/models/dev-island/rock__rock8_cave_medium.glb new file mode 100644 index 0000000..db38a0a Binary files /dev/null and b/public/models/dev-island/rock__rock8_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock8_natural_dark.glb b/public/models/dev-island/rock__rock8_natural_dark.glb new file mode 100644 index 0000000..29f33d0 Binary files /dev/null and b/public/models/dev-island/rock__rock8_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock8_natural_light.glb b/public/models/dev-island/rock__rock8_natural_light.glb new file mode 100644 index 0000000..64136d7 Binary files /dev/null and b/public/models/dev-island/rock__rock8_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock8_natural_medium.glb b/public/models/dev-island/rock__rock8_natural_medium.glb new file mode 100644 index 0000000..8ef57e3 Binary files /dev/null and b/public/models/dev-island/rock__rock8_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rock9.glb b/public/models/dev-island/rock__rock9.glb new file mode 100644 index 0000000..1e1a54b Binary files /dev/null and b/public/models/dev-island/rock__rock9.glb differ diff --git a/public/models/dev-island/rock__rock9_cave_dark.glb b/public/models/dev-island/rock__rock9_cave_dark.glb new file mode 100644 index 0000000..5607933 Binary files /dev/null and b/public/models/dev-island/rock__rock9_cave_dark.glb differ diff --git a/public/models/dev-island/rock__rock9_cave_light.glb b/public/models/dev-island/rock__rock9_cave_light.glb new file mode 100644 index 0000000..00fbac5 Binary files /dev/null and b/public/models/dev-island/rock__rock9_cave_light.glb differ diff --git a/public/models/dev-island/rock__rock9_cave_medium.glb b/public/models/dev-island/rock__rock9_cave_medium.glb new file mode 100644 index 0000000..1e1a54b Binary files /dev/null and b/public/models/dev-island/rock__rock9_cave_medium.glb differ diff --git a/public/models/dev-island/rock__rock9_natural_dark.glb b/public/models/dev-island/rock__rock9_natural_dark.glb new file mode 100644 index 0000000..8002168 Binary files /dev/null and b/public/models/dev-island/rock__rock9_natural_dark.glb differ diff --git a/public/models/dev-island/rock__rock9_natural_light.glb b/public/models/dev-island/rock__rock9_natural_light.glb new file mode 100644 index 0000000..7f3ff7e Binary files /dev/null and b/public/models/dev-island/rock__rock9_natural_light.glb differ diff --git a/public/models/dev-island/rock__rock9_natural_medium.glb b/public/models/dev-island/rock__rock9_natural_medium.glb new file mode 100644 index 0000000..8322c97 Binary files /dev/null and b/public/models/dev-island/rock__rock9_natural_medium.glb differ diff --git a/public/models/dev-island/rock__rockform.glb b/public/models/dev-island/rock__rockform.glb new file mode 100644 index 0000000..4aaf846 Binary files /dev/null and b/public/models/dev-island/rock__rockform.glb differ diff --git a/public/models/dev-island/rock__rockform_arch_dark.glb b/public/models/dev-island/rock__rockform_arch_dark.glb new file mode 100644 index 0000000..cb393f4 Binary files /dev/null and b/public/models/dev-island/rock__rockform_arch_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_arch_light.glb b/public/models/dev-island/rock__rockform_arch_light.glb new file mode 100644 index 0000000..1f487c5 Binary files /dev/null and b/public/models/dev-island/rock__rockform_arch_light.glb differ diff --git a/public/models/dev-island/rock__rockform_arch_medium.glb b/public/models/dev-island/rock__rockform_arch_medium.glb new file mode 100644 index 0000000..e216104 Binary files /dev/null and b/public/models/dev-island/rock__rockform_arch_medium.glb differ diff --git a/public/models/dev-island/rock__rockform_column_dark.glb b/public/models/dev-island/rock__rockform_column_dark.glb new file mode 100644 index 0000000..28138f6 Binary files /dev/null and b/public/models/dev-island/rock__rockform_column_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_column_light.glb b/public/models/dev-island/rock__rockform_column_light.glb new file mode 100644 index 0000000..6526731 Binary files /dev/null and b/public/models/dev-island/rock__rockform_column_light.glb differ diff --git a/public/models/dev-island/rock__rockform_column_medium.glb b/public/models/dev-island/rock__rockform_column_medium.glb new file mode 100644 index 0000000..c7911c8 Binary files /dev/null and b/public/models/dev-island/rock__rockform_column_medium.glb differ diff --git a/public/models/dev-island/rock__rockform_corner_hard_dark.glb b/public/models/dev-island/rock__rockform_corner_hard_dark.glb new file mode 100644 index 0000000..b39c80e Binary files /dev/null and b/public/models/dev-island/rock__rockform_corner_hard_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_corner_hard_light.glb b/public/models/dev-island/rock__rockform_corner_hard_light.glb new file mode 100644 index 0000000..cb6d5bf Binary files /dev/null and b/public/models/dev-island/rock__rockform_corner_hard_light.glb differ diff --git a/public/models/dev-island/rock__rockform_corner_hard_medium.glb b/public/models/dev-island/rock__rockform_corner_hard_medium.glb new file mode 100644 index 0000000..823c129 Binary files /dev/null and b/public/models/dev-island/rock__rockform_corner_hard_medium.glb differ diff --git a/public/models/dev-island/rock__rockform_corner_soft_dark.glb b/public/models/dev-island/rock__rockform_corner_soft_dark.glb new file mode 100644 index 0000000..5fd40b9 Binary files /dev/null and b/public/models/dev-island/rock__rockform_corner_soft_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_corner_soft_light.glb b/public/models/dev-island/rock__rockform_corner_soft_light.glb new file mode 100644 index 0000000..dfff699 Binary files /dev/null and b/public/models/dev-island/rock__rockform_corner_soft_light.glb differ diff --git a/public/models/dev-island/rock__rockform_corner_soft_medium.glb b/public/models/dev-island/rock__rockform_corner_soft_medium.glb new file mode 100644 index 0000000..8049719 Binary files /dev/null and b/public/models/dev-island/rock__rockform_corner_soft_medium.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_long_dark.glb b/public/models/dev-island/rock__rockform_wall_long_dark.glb new file mode 100644 index 0000000..72ee3f8 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_long_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_long_light.glb b/public/models/dev-island/rock__rockform_wall_long_light.glb new file mode 100644 index 0000000..4ff804f Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_long_light.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_long_medium.glb b/public/models/dev-island/rock__rockform_wall_long_medium.glb new file mode 100644 index 0000000..e074320 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_long_medium.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_short1_dark.glb b/public/models/dev-island/rock__rockform_wall_short1_dark.glb new file mode 100644 index 0000000..607da15 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_short1_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_short1_light.glb b/public/models/dev-island/rock__rockform_wall_short1_light.glb new file mode 100644 index 0000000..7505746 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_short1_light.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_short1_medium.glb b/public/models/dev-island/rock__rockform_wall_short1_medium.glb new file mode 100644 index 0000000..6187fc5 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_short1_medium.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_short2_dark.glb b/public/models/dev-island/rock__rockform_wall_short2_dark.glb new file mode 100644 index 0000000..4aaf846 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_short2_dark.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_short2_light.glb b/public/models/dev-island/rock__rockform_wall_short2_light.glb new file mode 100644 index 0000000..749c2a0 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_short2_light.glb differ diff --git a/public/models/dev-island/rock__rockform_wall_short2_medium.glb b/public/models/dev-island/rock__rockform_wall_short2_medium.glb new file mode 100644 index 0000000..cee6d73 Binary files /dev/null and b/public/models/dev-island/rock__rockform_wall_short2_medium.glb differ diff --git a/public/models/dev-island/rock__rockwall.glb b/public/models/dev-island/rock__rockwall.glb new file mode 100644 index 0000000..a785b79 Binary files /dev/null and b/public/models/dev-island/rock__rockwall.glb differ diff --git a/public/models/dev-island/rock__rockwall_corner_dark.glb b/public/models/dev-island/rock__rockwall_corner_dark.glb new file mode 100644 index 0000000..15faea9 Binary files /dev/null and b/public/models/dev-island/rock__rockwall_corner_dark.glb differ diff --git a/public/models/dev-island/rock__rockwall_corner_light.glb b/public/models/dev-island/rock__rockwall_corner_light.glb new file mode 100644 index 0000000..7c9f952 Binary files /dev/null and b/public/models/dev-island/rock__rockwall_corner_light.glb differ diff --git a/public/models/dev-island/rock__rockwall_corner_medium.glb b/public/models/dev-island/rock__rockwall_corner_medium.glb new file mode 100644 index 0000000..0f66e55 Binary files /dev/null and b/public/models/dev-island/rock__rockwall_corner_medium.glb differ diff --git a/public/models/dev-island/rock__rockwall_straight_dark.glb b/public/models/dev-island/rock__rockwall_straight_dark.glb new file mode 100644 index 0000000..a785b79 Binary files /dev/null and b/public/models/dev-island/rock__rockwall_straight_dark.glb differ diff --git a/public/models/dev-island/rock__rockwall_straight_light.glb b/public/models/dev-island/rock__rockwall_straight_light.glb new file mode 100644 index 0000000..9343a6b Binary files /dev/null and b/public/models/dev-island/rock__rockwall_straight_light.glb differ diff --git a/public/models/dev-island/rock__rockwall_straight_medium.glb b/public/models/dev-island/rock__rockwall_straight_medium.glb new file mode 100644 index 0000000..4f62d51 Binary files /dev/null and b/public/models/dev-island/rock__rockwall_straight_medium.glb differ diff --git a/public/models/dev-island/rocks_pack.glb b/public/models/dev-island/rocks_pack.glb new file mode 100644 index 0000000..daf0508 Binary files /dev/null and b/public/models/dev-island/rocks_pack.glb differ diff --git a/public/models/fish/docks/boat.glb b/public/models/fish/docks/boat.glb new file mode 100644 index 0000000..08282c9 Binary files /dev/null and b/public/models/fish/docks/boat.glb differ diff --git a/public/models/fish/docks/dock_long.glb b/public/models/fish/docks/dock_long.glb new file mode 100644 index 0000000..a971334 Binary files /dev/null and b/public/models/fish/docks/dock_long.glb differ diff --git a/public/models/fish/docks/dock_long_no_rope.glb b/public/models/fish/docks/dock_long_no_rope.glb new file mode 100644 index 0000000..4ad5d50 Binary files /dev/null and b/public/models/fish/docks/dock_long_no_rope.glb differ diff --git a/public/models/fish/docks/dock_stairs.glb b/public/models/fish/docks/dock_stairs.glb new file mode 100644 index 0000000..1e088ed Binary files /dev/null and b/public/models/fish/docks/dock_stairs.glb differ diff --git a/public/models/fish/docks/dock_t1.glb b/public/models/fish/docks/dock_t1.glb new file mode 100644 index 0000000..2c1ed67 Binary files /dev/null and b/public/models/fish/docks/dock_t1.glb differ diff --git a/public/models/fish/docks/dock_t2.glb b/public/models/fish/docks/dock_t2.glb new file mode 100644 index 0000000..de40329 Binary files /dev/null and b/public/models/fish/docks/dock_t2.glb differ diff --git a/public/models/fish/docks/dock_t3.glb b/public/models/fish/docks/dock_t3.glb new file mode 100644 index 0000000..8b9cafe Binary files /dev/null and b/public/models/fish/docks/dock_t3.glb differ diff --git a/public/models/fish/docks/dock_wide.glb b/public/models/fish/docks/dock_wide.glb new file mode 100644 index 0000000..3ed6b40 Binary files /dev/null and b/public/models/fish/docks/dock_wide.glb differ diff --git a/public/models/fish/lures/lure-2dkfirm95d.glb b/public/models/fish/lures/lure-2dkfirm95d.glb new file mode 100644 index 0000000..7674fe6 Binary files /dev/null and b/public/models/fish/lures/lure-2dkfirm95d.glb differ diff --git a/public/models/fish/lures/lure-3pcaourgtu.glb b/public/models/fish/lures/lure-3pcaourgtu.glb new file mode 100644 index 0000000..6ad7502 Binary files /dev/null and b/public/models/fish/lures/lure-3pcaourgtu.glb differ diff --git a/public/models/fish/lures/lure-h6fxsojx9b.glb b/public/models/fish/lures/lure-h6fxsojx9b.glb new file mode 100644 index 0000000..04b02e4 Binary files /dev/null and b/public/models/fish/lures/lure-h6fxsojx9b.glb differ diff --git a/public/models/fish/lures/lure-jknxyvhxtd.glb b/public/models/fish/lures/lure-jknxyvhxtd.glb new file mode 100644 index 0000000..2c6c8ed Binary files /dev/null and b/public/models/fish/lures/lure-jknxyvhxtd.glb differ diff --git a/public/models/fish/lures/lure-swjbrn0kvz.glb b/public/models/fish/lures/lure-swjbrn0kvz.glb new file mode 100644 index 0000000..bc9eef6 Binary files /dev/null and b/public/models/fish/lures/lure-swjbrn0kvz.glb differ diff --git a/public/models/fish/lures/lure.glb b/public/models/fish/lures/lure.glb new file mode 100644 index 0000000..0d31ddd Binary files /dev/null and b/public/models/fish/lures/lure.glb differ diff --git a/public/models/fish/lures/worm.glb b/public/models/fish/lures/worm.glb new file mode 100644 index 0000000..963a03f Binary files /dev/null and b/public/models/fish/lures/worm.glb differ diff --git a/public/models/fish/poles/fishing_rod-0yar0lg58p.glb b/public/models/fish/poles/fishing_rod-0yar0lg58p.glb new file mode 100644 index 0000000..a65e648 Binary files /dev/null and b/public/models/fish/poles/fishing_rod-0yar0lg58p.glb differ diff --git a/public/models/fish/poles/fishing_rod-9aohhrphe7.glb b/public/models/fish/poles/fishing_rod-9aohhrphe7.glb new file mode 100644 index 0000000..df754ad Binary files /dev/null and b/public/models/fish/poles/fishing_rod-9aohhrphe7.glb differ diff --git a/public/models/fish/poles/fishing_rod-aoabqwh68m.glb b/public/models/fish/poles/fishing_rod-aoabqwh68m.glb new file mode 100644 index 0000000..3bf2692 Binary files /dev/null and b/public/models/fish/poles/fishing_rod-aoabqwh68m.glb differ diff --git a/public/models/fish/poles/fishing_rod-ldlwqjn9zg.glb b/public/models/fish/poles/fishing_rod-ldlwqjn9zg.glb new file mode 100644 index 0000000..2677823 Binary files /dev/null and b/public/models/fish/poles/fishing_rod-ldlwqjn9zg.glb differ diff --git a/public/models/fish/poles/fishing_rod.glb b/public/models/fish/poles/fishing_rod.glb new file mode 100644 index 0000000..8df0eff Binary files /dev/null and b/public/models/fish/poles/fishing_rod.glb differ diff --git a/public/models/fish/species/anglerfish.glb b/public/models/fish/species/anglerfish.glb new file mode 100644 index 0000000..a729785 Binary files /dev/null and b/public/models/fish/species/anglerfish.glb differ diff --git a/public/models/fish/species/armored_catfish.glb b/public/models/fish/species/armored_catfish.glb new file mode 100644 index 0000000..c42dac9 Binary files /dev/null and b/public/models/fish/species/armored_catfish.glb differ diff --git a/public/models/fish/species/betta.glb b/public/models/fish/species/betta.glb new file mode 100644 index 0000000..fc8aa78 Binary files /dev/null and b/public/models/fish/species/betta.glb differ diff --git a/public/models/fish/species/black_lion_fish.glb b/public/models/fish/species/black_lion_fish.glb new file mode 100644 index 0000000..3f7229e Binary files /dev/null and b/public/models/fish/species/black_lion_fish.glb differ diff --git a/public/models/fish/species/blobfish.glb b/public/models/fish/species/blobfish.glb new file mode 100644 index 0000000..2edc5f1 Binary files /dev/null and b/public/models/fish/species/blobfish.glb differ diff --git a/public/models/fish/species/blue_goldfish.glb b/public/models/fish/species/blue_goldfish.glb new file mode 100644 index 0000000..2b5bf8a Binary files /dev/null and b/public/models/fish/species/blue_goldfish.glb differ diff --git a/public/models/fish/species/blue_tang.glb b/public/models/fish/species/blue_tang.glb new file mode 100644 index 0000000..2a29e8d Binary files /dev/null and b/public/models/fish/species/blue_tang.glb differ diff --git a/public/models/fish/species/butterfly_fish.glb b/public/models/fish/species/butterfly_fish.glb new file mode 100644 index 0000000..7f4df0b Binary files /dev/null and b/public/models/fish/species/butterfly_fish.glb differ diff --git a/public/models/fish/species/cardinal_fish.glb b/public/models/fish/species/cardinal_fish.glb new file mode 100644 index 0000000..f931bee Binary files /dev/null and b/public/models/fish/species/cardinal_fish.glb differ diff --git a/public/models/fish/species/clownfish.glb b/public/models/fish/species/clownfish.glb new file mode 100644 index 0000000..7278c9f Binary files /dev/null and b/public/models/fish/species/clownfish.glb differ diff --git a/public/models/fish/species/coral_grouper.glb b/public/models/fish/species/coral_grouper.glb new file mode 100644 index 0000000..54ac741 Binary files /dev/null and b/public/models/fish/species/coral_grouper.glb differ diff --git a/public/models/fish/species/cowfish.glb b/public/models/fish/species/cowfish.glb new file mode 100644 index 0000000..905c5f5 Binary files /dev/null and b/public/models/fish/species/cowfish.glb differ diff --git a/public/models/fish/species/flatfish.glb b/public/models/fish/species/flatfish.glb new file mode 100644 index 0000000..71f490a Binary files /dev/null and b/public/models/fish/species/flatfish.glb differ diff --git a/public/models/fish/species/flower_horn.glb b/public/models/fish/species/flower_horn.glb new file mode 100644 index 0000000..8a6d6c0 Binary files /dev/null and b/public/models/fish/species/flower_horn.glb differ diff --git a/public/models/fish/species/goblin_shark.glb b/public/models/fish/species/goblin_shark.glb new file mode 100644 index 0000000..96af8f1 Binary files /dev/null and b/public/models/fish/species/goblin_shark.glb differ diff --git a/public/models/fish/species/goldfish.glb b/public/models/fish/species/goldfish.glb new file mode 100644 index 0000000..c1da68a Binary files /dev/null and b/public/models/fish/species/goldfish.glb differ diff --git a/public/models/fish/species/humphead.glb b/public/models/fish/species/humphead.glb new file mode 100644 index 0000000..c0441f5 Binary files /dev/null and b/public/models/fish/species/humphead.glb differ diff --git a/public/models/fish/species/koi.glb b/public/models/fish/species/koi.glb new file mode 100644 index 0000000..5b6104d Binary files /dev/null and b/public/models/fish/species/koi.glb differ diff --git a/public/models/fish/species/large/SOURCE.md b/public/models/fish/species/large/SOURCE.md new file mode 100644 index 0000000..b64bff4 --- /dev/null +++ b/public/models/fish/species/large/SOURCE.md @@ -0,0 +1,16 @@ +# Large game fish (Animated Fish Bundle pack 2) + +Source zip: `D:\Games\Models\Animated Fish Bundle-glb (1).zip` +Extracted: species/large/ (clean names only — no root clutter) + +| File | Source name | +|------|-------------| +| dolphin.glb | Dolphin.glb | +| fish_mid.glb | Fish.glb | +| fish_stream.glb | Fish-BEcU9rjiAq.glb | +| fish_reef.glb | Fish-XWl86YFtpF.glb | +| manta_ray.glb | Manta ray.glb | +| shark_game.glb | Shark.glb | +| whale.glb | Whale.glb | + +Runtime SI scale: `src/fishing/fishScale.js` (length-primary, longer than tall/wide). diff --git a/public/models/fish/species/large/dolphin.glb b/public/models/fish/species/large/dolphin.glb new file mode 100644 index 0000000..769fdd0 Binary files /dev/null and b/public/models/fish/species/large/dolphin.glb differ diff --git a/public/models/fish/species/large/fish_mid.glb b/public/models/fish/species/large/fish_mid.glb new file mode 100644 index 0000000..668ef09 Binary files /dev/null and b/public/models/fish/species/large/fish_mid.glb differ diff --git a/public/models/fish/species/large/fish_reef.glb b/public/models/fish/species/large/fish_reef.glb new file mode 100644 index 0000000..e8aa413 Binary files /dev/null and b/public/models/fish/species/large/fish_reef.glb differ diff --git a/public/models/fish/species/large/fish_stream.glb b/public/models/fish/species/large/fish_stream.glb new file mode 100644 index 0000000..347d168 Binary files /dev/null and b/public/models/fish/species/large/fish_stream.glb differ diff --git a/public/models/fish/species/large/manta_ray.glb b/public/models/fish/species/large/manta_ray.glb new file mode 100644 index 0000000..65dc684 Binary files /dev/null and b/public/models/fish/species/large/manta_ray.glb differ diff --git a/public/models/fish/species/large/shark_game.glb b/public/models/fish/species/large/shark_game.glb new file mode 100644 index 0000000..605b16a Binary files /dev/null and b/public/models/fish/species/large/shark_game.glb differ diff --git a/public/models/fish/species/large/whale.glb b/public/models/fish/species/large/whale.glb new file mode 100644 index 0000000..3110841 Binary files /dev/null and b/public/models/fish/species/large/whale.glb differ diff --git a/public/models/fish/species/lionfish.glb b/public/models/fish/species/lionfish.glb new file mode 100644 index 0000000..c1e67b6 Binary files /dev/null and b/public/models/fish/species/lionfish.glb differ diff --git a/public/models/fish/species/mandarin_fish.glb b/public/models/fish/species/mandarin_fish.glb new file mode 100644 index 0000000..a2f4ce0 Binary files /dev/null and b/public/models/fish/species/mandarin_fish.glb differ diff --git a/public/models/fish/species/moorish_idol.glb b/public/models/fish/species/moorish_idol.glb new file mode 100644 index 0000000..645c91d Binary files /dev/null and b/public/models/fish/species/moorish_idol.glb differ diff --git a/public/models/fish/species/parrot_fish.glb b/public/models/fish/species/parrot_fish.glb new file mode 100644 index 0000000..9a68737 Binary files /dev/null and b/public/models/fish/species/parrot_fish.glb differ diff --git a/public/models/fish/species/piranha.glb b/public/models/fish/species/piranha.glb new file mode 100644 index 0000000..1883ab3 Binary files /dev/null and b/public/models/fish/species/piranha.glb differ diff --git a/public/models/fish/species/puffer.glb b/public/models/fish/species/puffer.glb new file mode 100644 index 0000000..1f72b59 Binary files /dev/null and b/public/models/fish/species/puffer.glb differ diff --git a/public/models/fish/species/rare/SOURCE.md b/public/models/fish/species/rare/SOURCE.md new file mode 100644 index 0000000..93c2579 --- /dev/null +++ b/public/models/fish/species/rare/SOURCE.md @@ -0,0 +1,10 @@ +# Rare species meshes + +| File | Source | Notes | +|------|--------|-------| +| aetherwing_turtle.glb | Documents\aetherwing_turtle.glb | Super rare · form/mount | +| glow_whale.glb | Documents\glow_whale.glb | Titan · hard · leviathan prey | +| pulbo_monstruo.glb | Documents\pulbo_monstruo.glb | Deep rare | +| ocean_creature.glb | Documents\ocean_creature.glb | **~219MB** — gitignored; copy local or CDN | + +Docks T1–T3: `docks/dock_t1..t3.glb` from D:\Games\Models\dock1..3.glb diff --git a/public/models/fish/species/rare/aetherwing_turtle.glb b/public/models/fish/species/rare/aetherwing_turtle.glb new file mode 100644 index 0000000..810f159 Binary files /dev/null and b/public/models/fish/species/rare/aetherwing_turtle.glb differ diff --git a/public/models/fish/species/rare/glow_whale.glb b/public/models/fish/species/rare/glow_whale.glb new file mode 100644 index 0000000..87babfb Binary files /dev/null and b/public/models/fish/species/rare/glow_whale.glb differ diff --git a/public/models/fish/species/rare/pulbo_monstruo.glb b/public/models/fish/species/rare/pulbo_monstruo.glb new file mode 100644 index 0000000..0f8ed89 Binary files /dev/null and b/public/models/fish/species/rare/pulbo_monstruo.glb differ diff --git a/public/models/fish/species/red_snapper.glb b/public/models/fish/species/red_snapper.glb new file mode 100644 index 0000000..fe280d6 Binary files /dev/null and b/public/models/fish/species/red_snapper.glb differ diff --git a/public/models/fish/species/royal_gramma.glb b/public/models/fish/species/royal_gramma.glb new file mode 100644 index 0000000..a65618f Binary files /dev/null and b/public/models/fish/species/royal_gramma.glb differ diff --git a/public/models/fish/species/shark.glb b/public/models/fish/species/shark.glb new file mode 100644 index 0000000..a837caf Binary files /dev/null and b/public/models/fish/species/shark.glb differ diff --git a/public/models/fish/species/sunfish.glb b/public/models/fish/species/sunfish.glb new file mode 100644 index 0000000..9fb55ea Binary files /dev/null and b/public/models/fish/species/sunfish.glb differ diff --git a/public/models/fish/species/swordfish.glb b/public/models/fish/species/swordfish.glb new file mode 100644 index 0000000..c125f63 Binary files /dev/null and b/public/models/fish/species/swordfish.glb differ diff --git a/public/models/fish/species/tang.glb b/public/models/fish/species/tang.glb new file mode 100644 index 0000000..71127f7 Binary files /dev/null and b/public/models/fish/species/tang.glb differ diff --git a/public/models/fish/species/tetra.glb b/public/models/fish/species/tetra.glb new file mode 100644 index 0000000..6e73d4c Binary files /dev/null and b/public/models/fish/species/tetra.glb differ diff --git a/public/models/fish/species/tuna.glb b/public/models/fish/species/tuna.glb new file mode 100644 index 0000000..32910fc Binary files /dev/null and b/public/models/fish/species/tuna.glb differ diff --git a/public/models/fish/species/turbot.glb b/public/models/fish/species/turbot.glb new file mode 100644 index 0000000..ef67d6c Binary files /dev/null and b/public/models/fish/species/turbot.glb differ diff --git a/public/models/fish/species/yellow_tang.glb b/public/models/fish/species/yellow_tang.glb new file mode 100644 index 0000000..7f9b757 Binary files /dev/null and b/public/models/fish/species/yellow_tang.glb differ diff --git a/public/models/fish/species/zebra_clown_fish.glb b/public/models/fish/species/zebra_clown_fish.glb new file mode 100644 index 0000000..7035814 Binary files /dev/null and b/public/models/fish/species/zebra_clown_fish.glb differ diff --git a/public/models/generated/catalog.json b/public/models/generated/catalog.json new file mode 100644 index 0000000..084c1c7 --- /dev/null +++ b/public/models/generated/catalog.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "updatedAt": "2026-08-05T07:26:17.211Z", + "source": "three-generator", + "cdnRoot": "https://assets.grudge-studio.com", + "assets": [ + { + "id": "9ed6c726c368c24d76fca7273c52c12c0f3bff21", + "name": "horse", + "meshUrl": "https://assets.grudge-studio.com/models/props/generated/horse_da4c09919ad7.glb", + "colliderUrl": "https://assets.grudge-studio.com/models/props/generated/horse_da4c09919ad7.collider.json", + "manifestUrl": "https://assets.grudge-studio.com/models/props/generated/horse_da4c09919ad7.manifest.json", + "kind": "prop", + "tags": [ + "generated", + "three-generator", + "baked" + ] + }, + { + "id": "2005a225d141481fa7a19debff4ee8a020359bc8", + "name": "house", + "meshUrl": "https://assets.grudge-studio.com/models/props/generated/house_1aa71b9670ad.glb", + "colliderUrl": "https://assets.grudge-studio.com/models/props/generated/house_1aa71b9670ad.collider.json", + "manifestUrl": "https://assets.grudge-studio.com/models/props/generated/house_1aa71b9670ad.manifest.json", + "kind": "prop", + "tags": [ + "generated", + "three-generator", + "baked" + ] + }, + { + "id": "55c60fde35bd0e8bc171f0081a56088c7d63f667", + "name": "rock", + "meshUrl": "https://assets.grudge-studio.com/models/props/generated/rock_93e4af3a22f7.glb", + "colliderUrl": "https://assets.grudge-studio.com/models/props/generated/rock_93e4af3a22f7.collider.json", + "manifestUrl": "https://assets.grudge-studio.com/models/props/generated/rock_93e4af3a22f7.manifest.json", + "kind": "prop", + "tags": [ + "generated", + "three-generator", + "baked" + ] + }, + { + "id": "c0af63a0ba0c482f1c97ca3197ba588c444827b9", + "name": "rock_3d_model", + "meshUrl": "https://assets.grudge-studio.com/models/props/generated/rock_3d_model_b10ad580e69f.glb", + "colliderUrl": "https://assets.grudge-studio.com/models/props/generated/rock_3d_model_b10ad580e69f.collider.json", + "manifestUrl": "https://assets.grudge-studio.com/models/props/generated/rock_3d_model_b10ad580e69f.manifest.json", + "kind": "prop", + "tags": [ + "generated", + "three-generator", + "baked" + ] + } + ] +} \ No newline at end of file diff --git a/public/models/generated/horse.collider.json b/public/models/generated/horse.collider.json new file mode 100644 index 0000000..6bad67e --- /dev/null +++ b/public/models/generated/horse.collider.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert", + "colliders": [ + { + "mesh": "tmpwc_kdvdi.ply", + "type": "capsule", + "center": [ + -0.01699976623058319, + -0.016790688037872314, + -0.01619681715965271 + ], + "size": [ + 0.5522216260433197, + 1.600000023841858, + 1.6839643120765686 + ], + "min": [ + -0.29311057925224304, + -0.8167906999588013, + -0.858178973197937 + ], + "max": [ + 0.25911104679107666, + 0.7832093238830566, + 0.8257853388786316 + ], + "capsule": { + "align": "Y", + "radius": 0.862, + "height": 0.01, + "offsetY": -0.0025 + }, + "box": { + "halfExtents": [ + 0.2961, + 0.82, + 0.862 + ] + } + } + ], + "rootCollider": { + "type": "capsule", + "role": "character_root", + "center": [ + 0, + 0.8, + 0 + ], + "size": [ + 0.7, + 1.6, + 0.7 + ], + "capsule": { + "align": "Y", + "radius": 0.35, + "height": 0.9 + }, + "practice": "Attach Cannon/Rapier body to hips/Y root; do not per-mesh armor RB on grudge6." + } +} \ No newline at end of file diff --git a/public/models/generated/horse.glb b/public/models/generated/horse.glb new file mode 100644 index 0000000..29c5b64 Binary files /dev/null and b/public/models/generated/horse.glb differ diff --git a/public/models/generated/horse.manifest.json b/public/models/generated/horse.manifest.json new file mode 100644 index 0000000..47e3f3c --- /dev/null +++ b/public/models/generated/horse.manifest.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert@1.0.0", + "source": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\horse.glb", + "output": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\horse.glb", + "intermediateBackend": "copy", + "warnings": [], + "stats": { + "meshes": 1, + "materials": 1, + "textures": 1, + "animations": 0, + "nodes": 1, + "skins": 0 + }, + "aabb": { + "min": [ + -0.29311057925224304, + -0.8167906999588013, + -0.858178973197937 + ], + "max": [ + 0.25911104679107666, + 0.7832093238830566, + 0.8257853388786316 + ], + "size": [ + 0.5522216260433197, + 1.600000023841858, + 1.6839643120765686 + ], + "bodyOnly": false + }, + "collider": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\horse.collider.json", + "producedAt": "2026-08-05T07:24:48.194Z", + "production": { + "format": "glb", + "yHipGround": true, + "textures": "webp", + "maxTexture": 1024, + "meshCompression": "quantize", + "bakeMeshes": true, + "bakeAnims": true, + "bakeColliders": true + } +} \ No newline at end of file diff --git a/public/models/generated/house.collider.json b/public/models/generated/house.collider.json new file mode 100644 index 0000000..0193f05 --- /dev/null +++ b/public/models/generated/house.collider.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert", + "colliders": [ + { + "mesh": "tmp35hxises.ply", + "type": "capsule", + "center": [ + -0.048384666442871094, + -0.044503211975097656, + -0.10267305374145508 + ], + "size": [ + 4.732089996337891, + 4.5, + 4.101110458374023 + ], + "min": [ + -2.4144296646118164, + -2.2945032119750977, + -2.153228282928467 + ], + "max": [ + 2.317660331726074, + 2.2054967880249023, + 1.9478821754455566 + ], + "capsule": { + "align": "Y", + "radius": 2.386, + "height": 0.01, + "offsetY": -0.0067 + }, + "box": { + "halfExtents": [ + 2.386, + 2.27, + 2.0706 + ] + } + } + ], + "rootCollider": { + "type": "capsule", + "role": "character_root", + "center": [ + 0, + 2.25, + 0 + ], + "size": [ + 0.7, + 4.5, + 0.7 + ], + "capsule": { + "align": "Y", + "radius": 0.35, + "height": 3.8 + }, + "practice": "Attach Cannon/Rapier body to hips/Y root; do not per-mesh armor RB on grudge6." + } +} \ No newline at end of file diff --git a/public/models/generated/house.glb b/public/models/generated/house.glb new file mode 100644 index 0000000..787a68f Binary files /dev/null and b/public/models/generated/house.glb differ diff --git a/public/models/generated/house.manifest.json b/public/models/generated/house.manifest.json new file mode 100644 index 0000000..f17b846 --- /dev/null +++ b/public/models/generated/house.manifest.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert@1.0.0", + "source": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\house.glb", + "output": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\house.glb", + "intermediateBackend": "copy", + "warnings": [], + "stats": { + "meshes": 1, + "materials": 1, + "textures": 1, + "animations": 0, + "nodes": 1, + "skins": 0 + }, + "aabb": { + "min": [ + -2.4144296646118164, + -2.2945032119750977, + -2.153228282928467 + ], + "max": [ + 2.317660331726074, + 2.2054967880249023, + 1.9478821754455566 + ], + "size": [ + 4.732089996337891, + 4.5, + 4.101110458374023 + ], + "bodyOnly": false + }, + "collider": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\house.collider.json", + "producedAt": "2026-08-05T07:24:52.260Z", + "production": { + "format": "glb", + "yHipGround": true, + "textures": "webp", + "maxTexture": 1024, + "meshCompression": "quantize", + "bakeMeshes": true, + "bakeAnims": true, + "bakeColliders": true + } +} \ No newline at end of file diff --git a/public/models/generated/rock.collider.json b/public/models/generated/rock.collider.json new file mode 100644 index 0000000..ef68c8f --- /dev/null +++ b/public/models/generated/rock.collider.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert", + "colliders": [ + { + "mesh": "geometry_0", + "type": "capsule", + "center": [ + 0.00824214518070221, + 0.027212217450141907, + -0.00862392783164978 + ], + "size": [ + 0.9758162796497345, + 0.9000000059604645, + 0.9610510468482971 + ], + "min": [ + -0.47966599464416504, + -0.42278778553009033, + -0.48914945125579834 + ], + "max": [ + 0.49615028500556946, + 0.47721222043037415, + 0.4719015955924988 + ], + "capsule": { + "align": "Y", + "radius": 0.5079, + "height": 0.01, + "offsetY": 0.0041 + }, + "box": { + "halfExtents": [ + 0.5079, + 0.47, + 0.5005 + ] + } + } + ], + "rootCollider": { + "type": "capsule", + "role": "character_root", + "center": [ + 0, + 0.45, + 0 + ], + "size": [ + 0.7, + 0.9, + 0.7 + ], + "capsule": { + "align": "Y", + "radius": 0.35, + "height": 0.2 + }, + "practice": "Attach Cannon/Rapier body to hips/Y root; do not per-mesh armor RB on grudge6." + } +} \ No newline at end of file diff --git a/public/models/generated/rock.glb b/public/models/generated/rock.glb new file mode 100644 index 0000000..050fd48 Binary files /dev/null and b/public/models/generated/rock.glb differ diff --git a/public/models/generated/rock.manifest.json b/public/models/generated/rock.manifest.json new file mode 100644 index 0000000..b06d2e8 --- /dev/null +++ b/public/models/generated/rock.manifest.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert@1.0.0", + "source": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\rock.glb", + "output": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\rock.glb", + "intermediateBackend": "copy", + "warnings": [], + "stats": { + "meshes": 1, + "materials": 1, + "textures": 1, + "animations": 0, + "nodes": 1, + "skins": 0 + }, + "aabb": { + "min": [ + -0.47966599464416504, + -0.42278778553009033, + -0.48914945125579834 + ], + "max": [ + 0.49615028500556946, + 0.47721222043037415, + 0.4719015955924988 + ], + "size": [ + 0.9758162796497345, + 0.9000000059604645, + 0.9610510468482971 + ], + "bodyOnly": false + }, + "collider": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\rock.collider.json", + "producedAt": "2026-08-05T07:24:42.516Z", + "production": { + "format": "glb", + "yHipGround": true, + "textures": "webp", + "maxTexture": 1024, + "meshCompression": "quantize", + "bakeMeshes": true, + "bakeAnims": true, + "bakeColliders": true + } +} \ No newline at end of file diff --git a/public/models/generated/rock_3d_model.collider.json b/public/models/generated/rock_3d_model.collider.json new file mode 100644 index 0000000..8f10e97 --- /dev/null +++ b/public/models/generated/rock_3d_model.collider.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert", + "colliders": [ + { + "mesh": "geometry_0", + "type": "capsule", + "center": [ + -0.0004053562879562378, + 0.0002449154853820801, + -0.0032607167959213257 + ], + "size": [ + 0.8186366856098175, + 0.800000011920929, + 0.7661904990673065 + ], + "min": [ + -0.409723699092865, + -0.3997550904750824, + -0.3863559663295746 + ], + "max": [ + 0.4089129865169525, + 0.40024492144584656, + 0.37983453273773193 + ], + "capsule": { + "align": "Y", + "radius": 0.4293, + "height": 0.01, + "offsetY": 0 + }, + "box": { + "halfExtents": [ + 0.4293, + 0.42, + 0.4031 + ] + } + } + ], + "rootCollider": { + "type": "capsule", + "role": "character_root", + "center": [ + 0, + 0.4, + 0 + ], + "size": [ + 0.7, + 0.8, + 0.7 + ], + "capsule": { + "align": "Y", + "radius": 0.35, + "height": 0.1 + }, + "practice": "Attach Cannon/Rapier body to hips/Y root; do not per-mesh armor RB on grudge6." + } +} \ No newline at end of file diff --git a/public/models/generated/rock_3d_model.glb b/public/models/generated/rock_3d_model.glb new file mode 100644 index 0000000..29540fc Binary files /dev/null and b/public/models/generated/rock_3d_model.glb differ diff --git a/public/models/generated/rock_3d_model.manifest.json b/public/models/generated/rock_3d_model.manifest.json new file mode 100644 index 0000000..5e8875c --- /dev/null +++ b/public/models/generated/rock_3d_model.manifest.json @@ -0,0 +1,46 @@ +{ + "version": 1, + "generator": "@grudge-studio/asset-convert@1.0.0", + "source": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\rock_3d_model.glb", + "output": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\rock_3d_model.glb", + "intermediateBackend": "copy", + "warnings": [], + "stats": { + "meshes": 1, + "materials": 1, + "textures": 1, + "animations": 0, + "nodes": 1, + "skins": 0 + }, + "aabb": { + "min": [ + -0.409723699092865, + -0.3997550904750824, + -0.3863559663295746 + ], + "max": [ + 0.4089129865169525, + 0.40024492144584656, + 0.37983453273773193 + ], + "size": [ + 0.8186366856098175, + 0.800000011920929, + 0.7661904990673065 + ], + "bodyOnly": false + }, + "collider": "C:\\Users\\nugye\\Documents\\_forks\\three-generator\\uploads\\baked\\rock_3d_model.collider.json", + "producedAt": "2026-08-05T07:24:56.009Z", + "production": { + "format": "glb", + "yHipGround": true, + "textures": "webp", + "maxTexture": 1024, + "meshCompression": "quantize", + "bakeMeshes": true, + "bakeAnims": true, + "bakeColliders": true + } +} \ No newline at end of file diff --git a/public/models/reference/grudgepistolzio/INCOMING_CLIPS.txt b/public/models/reference/grudgepistolzio/INCOMING_CLIPS.txt new file mode 100644 index 0000000..c5dea55 --- /dev/null +++ b/public/models/reference/grudgepistolzio/INCOMING_CLIPS.txt @@ -0,0 +1,21 @@ +Heavy_mixamo.fbx +pistol idle.fbx +pistol jump.fbx +pistol run.fbx +pistol jump (2).fbx +pistol walk.fbx +pistol run backward.fbx +pistol walk backward.fbx +pistol strafe.fbx +pistol strafe (2).fbx +pistol walk arc.fbx +pistol walk arc (2).fbx +pistol run arc.fbx +pistol run arc (2).fbx +pistol run backward arc.fbx +pistol run backward arc (2).fbx +pistol stand to kneel.fbx +pistol kneeling idle.fbx +pistol kneel to stand.fbx +pistol walk backward arc.fbx +pistol walk backward arc (2).fbx diff --git a/public/models/reference/minecraft_tps_pistol.glb b/public/models/reference/minecraft_tps_pistol.glb new file mode 100644 index 0000000..2c676ce Binary files /dev/null and b/public/models/reference/minecraft_tps_pistol.glb differ diff --git a/public/models/ride/back_fly_windsurf.glb b/public/models/ride/back_fly_windsurf.glb new file mode 100644 index 0000000..1b11e71 Binary files /dev/null and b/public/models/ride/back_fly_windsurf.glb differ diff --git a/public/models/ride/hoverboard.glb b/public/models/ride/hoverboard.glb new file mode 100644 index 0000000..42b93af Binary files /dev/null and b/public/models/ride/hoverboard.glb differ diff --git a/public/models/ride/ik-reference.png b/public/models/ride/ik-reference.png new file mode 100644 index 0000000..1671e92 Binary files /dev/null and b/public/models/ride/ik-reference.png differ diff --git a/public/models/ride/ride.manifest.json b/public/models/ride/ride.manifest.json new file mode 100644 index 0000000..13a1bbf --- /dev/null +++ b/public/models/ride/ride.manifest.json @@ -0,0 +1,75 @@ +{ + "units": "m", + "primary": "windsurf", + "sourceImage": "./ik-reference.png", + "notes": "Travel frame: +Y up, +Z nose/forward (SI). Mesh artYawDeg 0 = package +Z is travel nose (180 pointed stern into wind). Feet staggered; hands on boom L/R ~1.05 m.", + "hoverboard": { + "file": "./hoverboard.glb", + "lengthM": 1.15, + "deckY": 0.0, + "sockets": { + "footL": [-0.14, 0.02, 0.05], + "footR": [0.14, 0.02, 0.05], + "deckCenter": [0, 0.02, 0] + }, + "forward": [0, 0, 1] + }, + "windsurf": { + "file": "./windsurf_package.glb", + "size": [2.8, 0.684, 1.815], + "deckY": 0.06, + "artYawDeg": 0, + "domain": "water", + "deployOnLand": false, + "meshNote": "Author GLB is Sketchfab OBJ_Para_Coat / Tube003 (single mesh). Parts are LOGICAL sockets in SI travel frame, not named submeshes.", + "sockets": { + "footL": [-0.2, 0.08, -0.28], + "footR": [0.16, 0.08, 0.12], + "deckCenter": [0.0, 0.07, -0.08], + "mastBase": [0.0, 0.08, 0.05], + "sailBoomL": [-0.42, 1.05, 0.08], + "sailBoomR": [0.38, 1.05, 0.1], + "sailRail": [0.12, 1.08, 0.14], + "sailPeak": [0.0, 1.55, 0.2], + "bowTip": [0.0, 0.08, 1.15], + "engineMount": [0.0, 0.12, -0.95] + }, + "parts": { + "board": { + "label": "Board / deck", + "sockets": ["deckCenter", "footL", "footR", "mastBase"], + "color": 4788441 + }, + "front": { + "label": "Front / bow", + "sockets": ["bowTip"], + "color": 4063130 + }, + "sail": { + "label": "Sail / boom", + "sockets": ["sailPeak", "sailRail", "sailBoomL", "sailBoomR", "mastBase"], + "color": 16761162 + }, + "engine": { + "label": "Engine / stern", + "sockets": ["engineMount"], + "color": 16734938 + } + }, + "ik": { + "feet": ["footR", "footL"], + "hands": ["sailBoomL", "sailBoomR"], + "primaryHand": "sailBoomL", + "secondaryHand": "sailBoomR", + "swapLr": true, + "note": "Toon Bip001 vs author sockets — L foot→footR, R hand→sailBoomL", + "rigidBody": "deckCenter" + }, + "motion": { + "bankMaxDeg": 18, + "swayHz": 0.55, + "swayM": 0.04 + }, + "forward": [0, 0, 1] + } +} diff --git a/public/models/ride/shark_fin.glb b/public/models/ride/shark_fin.glb new file mode 100644 index 0000000..58f10d8 Binary files /dev/null and b/public/models/ride/shark_fin.glb differ diff --git a/public/models/ride/windsurf_package.glb b/public/models/ride/windsurf_package.glb new file mode 100644 index 0000000..24291d8 Binary files /dev/null and b/public/models/ride/windsurf_package.glb differ diff --git a/public/models/ride/wings/README.md b/public/models/ride/wings/README.md new file mode 100644 index 0000000..f7a0645 --- /dev/null +++ b/public/models/ride/wings/README.md @@ -0,0 +1,10 @@ +# Wing mesh drop zone + +Place animated wing GLBs here (not committed until assets exist): + +| Prefab | Stow (back) | Open (deploy) | +|--------|-------------|-----------------| +| holy_wings | holy_wings_stow.glb | holy_wings_open.glb | +| traveler_wings | traveler_wings_stow.glb | traveler_wings_open.glb | + +See docs/BACK_SLOT_MOBILITY_SSOT.md and src/config/backSlotMobilitySsot.js diff --git a/public/models/vfx/arrows/arrow-loft.glb b/public/models/vfx/arrows/arrow-loft.glb new file mode 100644 index 0000000..06d9339 Binary files /dev/null and b/public/models/vfx/arrows/arrow-loft.glb differ diff --git a/public/models/vfx/arrows/arrow-manifest.json b/public/models/vfx/arrows/arrow-manifest.json new file mode 100644 index 0000000..5e253b0 --- /dev/null +++ b/public/models/vfx/arrows/arrow-manifest.json @@ -0,0 +1,33 @@ +{ + "systems": { + "path": { + "id": "arrow-path", + "mesh": "models/vfx/arrows/arrow-path.glb", + "source": "teleport_arrow.glb", + "lengthM": 1.1, + "loft": 0, + "role": "linear attack path — distance sets end event (explode/aoe/blink/return)", + "endEvents": [ + "impact", + "explode", + "aoe", + "blink", + "return" + ] + }, + "loft": { + "id": "arrow-loft", + "mesh": "models/vfx/arrows/arrow-loft.glb", + "source": "arrow_curved.glb", + "lengthM": 0.95, + "loft": 0.42, + "role": "throw / place device / trap / summon arrow", + "endEvents": [ + "throw", + "place_device", + "trap", + "summon" + ] + } + } +} \ No newline at end of file diff --git a/public/models/vfx/arrows/arrow-path.glb b/public/models/vfx/arrows/arrow-path.glb new file mode 100644 index 0000000..c6de501 Binary files /dev/null and b/public/models/vfx/arrows/arrow-path.glb differ diff --git a/public/models/vfx/bubbles/bubble-manifest.json b/public/models/vfx/bubbles/bubble-manifest.json new file mode 100644 index 0000000..8c7c602 --- /dev/null +++ b/public/models/vfx/bubbles/bubble-manifest.json @@ -0,0 +1,6 @@ +{ + "source": "bubbles_2.glb", + "note": "Morph multipack (~37MB, 178 frames). Do NOT load whole file as a projectile. Runtime water/freeze uses procedural SI spheres (createWaterBubbleMaterial). Sample morph extract deferred.", + "runtime": "src/vfx/elementAttackVfx.js WATER_BUBBLE + SkillProjectileSystem.spawnBubbleField", + "diameterM": 0.22 +} diff --git a/public/models/vfx/charge/charge-manifest.json b/public/models/vfx/charge/charge-manifest.json new file mode 100644 index 0000000..bc0eeca --- /dev/null +++ b/public/models/vfx/charge/charge-manifest.json @@ -0,0 +1,7 @@ +{ + "source": "kamehameha_charging.glb", + "diameterM": 0.35, + "path": "models/vfx/charge/staff-charge.glb", + "alias": "models/vfx/charge/kamehameha-charge.glb", + "note": "Small charge shell at staff tip / hand during cast — tint with elemental shaders" +} \ No newline at end of file diff --git a/public/models/vfx/charge/kamehameha-charge.glb b/public/models/vfx/charge/kamehameha-charge.glb new file mode 100644 index 0000000..d399f72 Binary files /dev/null and b/public/models/vfx/charge/kamehameha-charge.glb differ diff --git a/public/models/vfx/charge/staff-charge.glb b/public/models/vfx/charge/staff-charge.glb new file mode 100644 index 0000000..d399f72 Binary files /dev/null and b/public/models/vfx/charge/staff-charge.glb differ diff --git a/public/models/vfx/orbs/orb-arcane.glb b/public/models/vfx/orbs/orb-arcane.glb new file mode 100644 index 0000000..1036f9a Binary files /dev/null and b/public/models/vfx/orbs/orb-arcane.glb differ diff --git a/public/models/vfx/orbs/orb-fire.glb b/public/models/vfx/orbs/orb-fire.glb new file mode 100644 index 0000000..df53006 Binary files /dev/null and b/public/models/vfx/orbs/orb-fire.glb differ diff --git a/public/models/vfx/orbs/orb-holy.glb b/public/models/vfx/orbs/orb-holy.glb new file mode 100644 index 0000000..cfb7488 Binary files /dev/null and b/public/models/vfx/orbs/orb-holy.glb differ diff --git a/public/models/vfx/orbs/orb-ice.glb b/public/models/vfx/orbs/orb-ice.glb new file mode 100644 index 0000000..39149b5 Binary files /dev/null and b/public/models/vfx/orbs/orb-ice.glb differ diff --git a/public/models/vfx/orbs/orb-manifest.json b/public/models/vfx/orbs/orb-manifest.json new file mode 100644 index 0000000..082e1ab --- /dev/null +++ b/public/models/vfx/orbs/orb-manifest.json @@ -0,0 +1,145 @@ +{ + "source": "gd_orbs_pack.glb", + "diameterM": 0.45, + "note": "Individual orbs for staff attacks — never load gd_orbs_pack_src whole as projectile", + "orbs": [ + { + "id": "orb-fire", + "label": "Fire Orb", + "element": "fire", + "path": "models/vfx/orbs/orb-fire.glb", + "diameterM": 0.45, + "color": [ + 1, + 0.35, + 0.08, + 1 + ], + "emissive": [ + 1, + 0.2, + 0.02 + ], + "staffUse": { + "projectileScale": 1, + "tipScale": 0.55, + "chargeScale": 0.8 + } + }, + { + "id": "orb-ice", + "label": "Ice Orb", + "element": "ice", + "path": "models/vfx/orbs/orb-ice.glb", + "diameterM": 0.45, + "color": [ + 0.35, + 0.82, + 1, + 1 + ], + "emissive": [ + 0.1, + 0.45, + 0.85 + ], + "staffUse": { + "projectileScale": 1, + "tipScale": 0.55, + "chargeScale": 0.8 + } + }, + { + "id": "orb-nature", + "label": "Nature Orb", + "element": "nature", + "path": "models/vfx/orbs/orb-nature.glb", + "diameterM": 0.45, + "color": [ + 0.35, + 0.85, + 0.28, + 1 + ], + "emissive": [ + 0.08, + 0.55, + 0.12 + ], + "staffUse": { + "projectileScale": 1, + "tipScale": 0.55, + "chargeScale": 0.8 + } + }, + { + "id": "orb-storm", + "label": "Storm Orb", + "element": "storm", + "path": "models/vfx/orbs/orb-storm.glb", + "diameterM": 0.45, + "color": [ + 0.55, + 0.85, + 1, + 1 + ], + "emissive": [ + 0.2, + 0.55, + 1 + ], + "staffUse": { + "projectileScale": 1, + "tipScale": 0.55, + "chargeScale": 0.8 + } + }, + { + "id": "orb-holy", + "label": "Holy Orb", + "element": "holy", + "path": "models/vfx/orbs/orb-holy.glb", + "diameterM": 0.45, + "color": [ + 1, + 0.9, + 0.45, + 1 + ], + "emissive": [ + 0.95, + 0.75, + 0.2 + ], + "staffUse": { + "projectileScale": 1, + "tipScale": 0.55, + "chargeScale": 0.8 + } + }, + { + "id": "orb-arcane", + "label": "Arcane Orb", + "element": "arcane", + "path": "models/vfx/orbs/orb-arcane.glb", + "diameterM": 0.45, + "color": [ + 0.7, + 0.35, + 1, + 1 + ], + "emissive": [ + 0.45, + 0.12, + 0.9 + ], + "staffUse": { + "projectileScale": 1, + "tipScale": 0.55, + "chargeScale": 0.8 + } + } + ] +} \ No newline at end of file diff --git a/public/models/vfx/orbs/orb-nature.glb b/public/models/vfx/orbs/orb-nature.glb new file mode 100644 index 0000000..6bff071 Binary files /dev/null and b/public/models/vfx/orbs/orb-nature.glb differ diff --git a/public/models/vfx/orbs/orb-storm.glb b/public/models/vfx/orbs/orb-storm.glb new file mode 100644 index 0000000..d2226c9 Binary files /dev/null and b/public/models/vfx/orbs/orb-storm.glb differ diff --git a/public/models/vfx/projectiles/bullet1.glb b/public/models/vfx/projectiles/bullet1.glb new file mode 100644 index 0000000..1677d74 Binary files /dev/null and b/public/models/vfx/projectiles/bullet1.glb differ diff --git a/public/models/vfx/rocks/rock-0.glb b/public/models/vfx/rocks/rock-0.glb new file mode 100644 index 0000000..58cbbe3 Binary files /dev/null and b/public/models/vfx/rocks/rock-0.glb differ diff --git a/public/models/vfx/rocks/rock-1.glb b/public/models/vfx/rocks/rock-1.glb new file mode 100644 index 0000000..5208145 Binary files /dev/null and b/public/models/vfx/rocks/rock-1.glb differ diff --git a/public/models/vfx/rocks/rock-2.glb b/public/models/vfx/rocks/rock-2.glb new file mode 100644 index 0000000..4ad3f1c Binary files /dev/null and b/public/models/vfx/rocks/rock-2.glb differ diff --git a/public/models/vfx/rocks/rock-3.glb b/public/models/vfx/rocks/rock-3.glb new file mode 100644 index 0000000..ca8ede4 Binary files /dev/null and b/public/models/vfx/rocks/rock-3.glb differ diff --git a/public/models/vfx/rocks/rock-4.glb b/public/models/vfx/rocks/rock-4.glb new file mode 100644 index 0000000..f7487fc Binary files /dev/null and b/public/models/vfx/rocks/rock-4.glb differ diff --git a/public/models/vfx/rocks/rock-5.glb b/public/models/vfx/rocks/rock-5.glb new file mode 100644 index 0000000..612a771 Binary files /dev/null and b/public/models/vfx/rocks/rock-5.glb differ diff --git a/public/models/vfx/rocks/rock-6.glb b/public/models/vfx/rocks/rock-6.glb new file mode 100644 index 0000000..fb2ed0b Binary files /dev/null and b/public/models/vfx/rocks/rock-6.glb differ diff --git a/public/models/vfx/rocks/rock-7.glb b/public/models/vfx/rocks/rock-7.glb new file mode 100644 index 0000000..d5bc15d Binary files /dev/null and b/public/models/vfx/rocks/rock-7.glb differ diff --git a/public/models/vfx/rocks/rock-manifest.json b/public/models/vfx/rocks/rock-manifest.json new file mode 100644 index 0000000..c532b9e --- /dev/null +++ b/public/models/vfx/rocks/rock-manifest.json @@ -0,0 +1,62 @@ +{ + "source": "assorted_rock_pack.glb", + "diameterM": 0.55, + "rocks": [ + { + "id": "rock-0", + "path": "models/vfx/rocks/rock-0.glb", + "diameterM": 0.55, + "rawMax": 382.30989837646484, + "scale": 0.001438623489309735 + }, + { + "id": "rock-1", + "path": "models/vfx/rocks/rock-1.glb", + "diameterM": 0.55, + "rawMax": 104.09304809570312, + "scale": 0.005283734217239274 + }, + { + "id": "rock-2", + "path": "models/vfx/rocks/rock-2.glb", + "diameterM": 0.55, + "rawMax": 185.39414978027344, + "scale": 0.0029666524032816157 + }, + { + "id": "rock-3", + "path": "models/vfx/rocks/rock-3.glb", + "diameterM": 0.55, + "rawMax": 370.81678771972656, + "scale": 0.001483212244467489 + }, + { + "id": "rock-4", + "path": "models/vfx/rocks/rock-4.glb", + "diameterM": 0.55, + "rawMax": 226.39739990234375, + "scale": 0.0024293565219266735 + }, + { + "id": "rock-5", + "path": "models/vfx/rocks/rock-5.glb", + "diameterM": 0.55, + "rawMax": 294.4364013671875, + "scale": 0.0018679755541302883 + }, + { + "id": "rock-6", + "path": "models/vfx/rocks/rock-6.glb", + "diameterM": 0.55, + "rawMax": 337.2662048339844, + "scale": 0.0016307592996776287 + }, + { + "id": "rock-7", + "path": "models/vfx/rocks/rock-7.glb", + "diameterM": 0.55, + "rawMax": 405.38067626953125, + "scale": 0.0013567494263942508 + } + ] +} \ No newline at end of file diff --git a/public/models/vfx/summons/summon-fire-fist.glb b/public/models/vfx/summons/summon-fire-fist.glb new file mode 100644 index 0000000..ad6a778 Binary files /dev/null and b/public/models/vfx/summons/summon-fire-fist.glb differ diff --git a/public/models/vfx/summons/summon-ice-shard.glb b/public/models/vfx/summons/summon-ice-shard.glb new file mode 100644 index 0000000..8efa368 Binary files /dev/null and b/public/models/vfx/summons/summon-ice-shard.glb differ diff --git a/public/skills/production/staff_fire_bolt.json b/public/skills/production/staff_fire_bolt.json new file mode 100644 index 0000000..bb72768 --- /dev/null +++ b/public/skills/production/staff_fire_bolt.json @@ -0,0 +1,37 @@ +{ + "$schema": "weapon-skill-production-override", + "id": "staff_fire_bolt", + "weaponTypeId": "STAFF", + "note": "Optional overrides only. Catalog id/dmg/cd/effects stay SSOT from master-weaponSkills / t0-weapons.", + "catalogUrl": "https://info.grudge-studio.com/WEAPON_SKILLS.html", + "animRole": null, + "animClip": null, + "hitFrameDelay": 0.18, + "comboStages": null, + "castEffectId": null, + "travelEffectId": null, + "impactEffectId": null, + "meshId": null, + "projectileMeshUrl": null, + "chargeMeshUrl": null, + "intensity": 1, + "delivery": null, + "element": null, + "force": null, + "knockbackMm": null, + "knockupVy": null, + "aoeM": null, + "projectileSpeed": null, + "contactRadius": null, + "collider": null, + "statuses": null, + "checklist": { + "catalogIdVerified": false, + "animClipVerified": false, + "vfxCastTravelImpact": false, + "physicsSI": false, + "statusesMapped": false, + "readinessGreen": false, + "smokeCastingLab": false + } +} diff --git a/public/skills/production/staff_ice_nova.json b/public/skills/production/staff_ice_nova.json new file mode 100644 index 0000000..5b5c01b --- /dev/null +++ b/public/skills/production/staff_ice_nova.json @@ -0,0 +1,32 @@ +{ + "$schema": "weapon-skill-production-override", + "id": "staff_ice_nova", + "weaponTypeId": "STAFF", + "note": "Freeze AOE production override — catalog id stays SSOT", + "animRole": "cast", + "animClip": "magic/standing 1h cast spell 01", + "hitFrameDelay": 0.4, + "castEffectId": "frost_wave", + "travelEffectId": "moon_beam", + "impactEffectId": "frost_wave", + "element": "ice", + "delivery": "around_caster", + "aoeM": 5.5, + "force": 4, + "knockbackMm": 40, + "knockupVy": 0.3, + "intensity": 1.15, + "statuses": [ + { "id": "freeze", "durationSec": 2.5, "magnitude": 1 }, + { "id": "slow", "durationSec": 1.5, "magnitude": 0.4 } + ], + "checklist": { + "catalogIdVerified": true, + "animClipVerified": true, + "vfxCastTravelImpact": true, + "physicsSI": true, + "statusesMapped": true, + "readinessGreen": false, + "smokeCastingLab": false + } +} diff --git a/public/skills/production/t0_sword_practice_slash.json b/public/skills/production/t0_sword_practice_slash.json new file mode 100644 index 0000000..887c10e --- /dev/null +++ b/public/skills/production/t0_sword_practice_slash.json @@ -0,0 +1,37 @@ +{ + "$schema": "weapon-skill-production-override", + "id": "t0_sword_practice_slash", + "weaponTypeId": "SWORD", + "note": "Optional overrides only. Catalog id/dmg/cd/effects stay SSOT from master-weaponSkills / t0-weapons.", + "catalogUrl": "https://info.grudge-studio.com/WEAPON_SKILLS.html", + "animRole": null, + "animClip": null, + "hitFrameDelay": 0.18, + "comboStages": null, + "castEffectId": null, + "travelEffectId": null, + "impactEffectId": null, + "meshId": null, + "projectileMeshUrl": null, + "chargeMeshUrl": null, + "intensity": 1, + "delivery": null, + "element": null, + "force": null, + "knockbackMm": null, + "knockupVy": null, + "aoeM": null, + "projectileSpeed": null, + "contactRadius": null, + "collider": null, + "statuses": null, + "checklist": { + "catalogIdVerified": false, + "animClipVerified": false, + "vfxCastTravelImpact": false, + "physicsSI": false, + "statusesMapped": false, + "readinessGreen": false, + "smokeCastingLab": false + } +} diff --git a/public/textures/waternormals.jpg b/public/textures/waternormals.jpg new file mode 100644 index 0000000..9dfe03c Binary files /dev/null and b/public/textures/waternormals.jpg differ diff --git a/public/ui/craftpix/cast/bg.png b/public/ui/craftpix/cast/bg.png new file mode 100644 index 0000000..7262177 Binary files /dev/null and b/public/ui/craftpix/cast/bg.png differ diff --git a/public/ui/craftpix/cast/fill.png b/public/ui/craftpix/cast/fill.png new file mode 100644 index 0000000..ef1783b Binary files /dev/null and b/public/ui/craftpix/cast/fill.png differ diff --git a/public/ui/craftpix/cast/icon_frame.png b/public/ui/craftpix/cast/icon_frame.png new file mode 100644 index 0000000..412ca5f Binary files /dev/null and b/public/ui/craftpix/cast/icon_frame.png differ diff --git a/public/ui/craftpix/fill/track_bg.png b/public/ui/craftpix/fill/track_bg.png new file mode 100644 index 0000000..89e9105 Binary files /dev/null and b/public/ui/craftpix/fill/track_bg.png differ diff --git a/public/ui/craftpix/fill/track_fill.png b/public/ui/craftpix/fill/track_fill.png new file mode 100644 index 0000000..af322ae Binary files /dev/null and b/public/ui/craftpix/fill/track_fill.png differ diff --git a/public/ui/craftpix/hotbar/slot_bg.png b/public/ui/craftpix/hotbar/slot_bg.png new file mode 100644 index 0000000..3d58ab9 Binary files /dev/null and b/public/ui/craftpix/hotbar/slot_bg.png differ diff --git a/public/ui/craftpix/hotbar/slot_border.png b/public/ui/craftpix/hotbar/slot_border.png new file mode 100644 index 0000000..0a37396 Binary files /dev/null and b/public/ui/craftpix/hotbar/slot_border.png differ diff --git a/public/ui/craftpix/hotbar/slot_cd.png b/public/ui/craftpix/hotbar/slot_cd.png new file mode 100644 index 0000000..ebdb65e Binary files /dev/null and b/public/ui/craftpix/hotbar/slot_cd.png differ diff --git a/public/ui/craftpix/hotbar/slot_press.png b/public/ui/craftpix/hotbar/slot_press.png new file mode 100644 index 0000000..c49633b Binary files /dev/null and b/public/ui/craftpix/hotbar/slot_press.png differ diff --git a/public/ui/craftpix/panel/bg.png b/public/ui/craftpix/panel/bg.png new file mode 100644 index 0000000..9ec5c77 Binary files /dev/null and b/public/ui/craftpix/panel/bg.png differ diff --git a/public/ui/craftpix/unit/avatar_bg.png b/public/ui/craftpix/unit/avatar_bg.png new file mode 100644 index 0000000..21f493a Binary files /dev/null and b/public/ui/craftpix/unit/avatar_bg.png differ diff --git a/public/ui/craftpix/unit/avatar_border.png b/public/ui/craftpix/unit/avatar_border.png new file mode 100644 index 0000000..861f896 Binary files /dev/null and b/public/ui/craftpix/unit/avatar_border.png differ diff --git a/public/ui/craftpix/unit/avatar_overlay.png b/public/ui/craftpix/unit/avatar_overlay.png new file mode 100644 index 0000000..71654e7 Binary files /dev/null and b/public/ui/craftpix/unit/avatar_overlay.png differ diff --git a/public/ui/craftpix/unit/level_bg.png b/public/ui/craftpix/unit/level_bg.png new file mode 100644 index 0000000..aa9418e Binary files /dev/null and b/public/ui/craftpix/unit/level_bg.png differ diff --git a/public/ui/craftpix/unit/level_border.png b/public/ui/craftpix/unit/level_border.png new file mode 100644 index 0000000..79d0b22 Binary files /dev/null and b/public/ui/craftpix/unit/level_border.png differ diff --git a/public/ui/craftpix/unit/pb_bg.png b/public/ui/craftpix/unit/pb_bg.png new file mode 100644 index 0000000..b378b87 Binary files /dev/null and b/public/ui/craftpix/unit/pb_bg.png differ diff --git a/public/ui/craftpix/unit/pb_fill.png b/public/ui/craftpix/unit/pb_fill.png new file mode 100644 index 0000000..f0cb995 Binary files /dev/null and b/public/ui/craftpix/unit/pb_fill.png differ diff --git a/public/ui/craftpix/unit/sb_bg.png b/public/ui/craftpix/unit/sb_bg.png new file mode 100644 index 0000000..3bb1a03 Binary files /dev/null and b/public/ui/craftpix/unit/sb_bg.png differ diff --git a/public/ui/craftpix/unit/sb_fill.png b/public/ui/craftpix/unit/sb_fill.png new file mode 100644 index 0000000..6f70add Binary files /dev/null and b/public/ui/craftpix/unit/sb_fill.png differ diff --git a/public/ui/craftpix/unit/skull.png b/public/ui/craftpix/unit/skull.png new file mode 100644 index 0000000..4e393df Binary files /dev/null and b/public/ui/craftpix/unit/skull.png differ diff --git a/public/ui/fishing/FishingBar.png b/public/ui/fishing/FishingBar.png new file mode 100644 index 0000000..b5c0a1d Binary files /dev/null and b/public/ui/fishing/FishingBar.png differ diff --git a/public/ui/fishing/FishingPointer.png b/public/ui/fishing/FishingPointer.png new file mode 100644 index 0000000..d6ad72d Binary files /dev/null and b/public/ui/fishing/FishingPointer.png differ diff --git a/public/ui/fishing/FishingTarget.png b/public/ui/fishing/FishingTarget.png new file mode 100644 index 0000000..d8ca8f4 Binary files /dev/null and b/public/ui/fishing/FishingTarget.png differ diff --git a/public/ui/fishing/FishingTemplate.png b/public/ui/fishing/FishingTemplate.png new file mode 100644 index 0000000..f70bc76 Binary files /dev/null and b/public/ui/fishing/FishingTemplate.png differ diff --git a/public/ui/inventory/equipment-reference.png b/public/ui/inventory/equipment-reference.png new file mode 100644 index 0000000..b4fc2db Binary files /dev/null and b/public/ui/inventory/equipment-reference.png differ diff --git a/public/ui/inventory/inventory-slots-set.png b/public/ui/inventory/inventory-slots-set.png new file mode 100644 index 0000000..293d64f Binary files /dev/null and b/public/ui/inventory/inventory-slots-set.png differ diff --git a/public/ui/warlords-dev/buttons/button-ui.png b/public/ui/warlords-dev/buttons/button-ui.png new file mode 100644 index 0000000..0f3f7eb Binary files /dev/null and b/public/ui/warlords-dev/buttons/button-ui.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon1.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon1.png new file mode 100644 index 0000000..d9976fa Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon1.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon10.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon10.png new file mode 100644 index 0000000..cbc7657 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon10.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon2.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon2.png new file mode 100644 index 0000000..d1d12e5 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon2.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon3.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon3.png new file mode 100644 index 0000000..64e7cac Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon3.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon4.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon4.png new file mode 100644 index 0000000..8d0591c Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon4.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon5.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon5.png new file mode 100644 index 0000000..68414d9 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon5.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon6.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon6.png new file mode 100644 index 0000000..0be4663 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon6.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon7.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon7.png new file mode 100644 index 0000000..d158723 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon7.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon8.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon8.png new file mode 100644 index 0000000..92f8d11 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon8.png differ diff --git a/public/ui/warlords-dev/cursors/pirate/MouseIcon9.png b/public/ui/warlords-dev/cursors/pirate/MouseIcon9.png new file mode 100644 index 0000000..ceeb802 Binary files /dev/null and b/public/ui/warlords-dev/cursors/pirate/MouseIcon9.png differ diff --git a/public/ui/warlords-dev/inventory/inventory.png b/public/ui/warlords-dev/inventory/inventory.png new file mode 100644 index 0000000..8f955ae Binary files /dev/null and b/public/ui/warlords-dev/inventory/inventory.png differ diff --git a/public/ui/warlords-dev/inventory/miniinventory.png b/public/ui/warlords-dev/inventory/miniinventory.png new file mode 100644 index 0000000..dfa3e8b Binary files /dev/null and b/public/ui/warlords-dev/inventory/miniinventory.png differ diff --git a/scripts/apply-casting-dns.mjs b/scripts/apply-casting-dns.mjs new file mode 100644 index 0000000..56875a0 --- /dev/null +++ b/scripts/apply-casting-dns.mjs @@ -0,0 +1,183 @@ +/** + * Create/update casting.grudge-studio.com → Vercel A 76.76.21.21 (DNS only). + * Loads CF_DNS_API_TOKEN + CF_ZONE_ID from Desktop secretnow.txt (or env). + */ +import fs from 'fs'; + +function loadSecrets(path) { + let raw = ''; + try { + raw = fs.readFileSync(path, 'utf8'); + } catch { + return {}; + } + raw = raw.replace(/^\uFEFF/, ''); + const out = {}; + for (const line of raw.split(/\r?\n/)) { + if (!line || line.trim().startsWith('#')) continue; + const i = line.indexOf('='); + if (i < 1) continue; + const k = line.slice(0, i).trim(); + let v = line.slice(i + 1).trim(); + if ((v.startsWith('"') && v.endsWith('"')) || (v.startsWith("'") && v.endsWith("'"))) { + v = v.slice(1, -1); + } + out[k] = v; + } + return out; +} + +const secrets = { + ...loadSecrets('C:/Users/nugye/Desktop/secretnow.txt'), + ...loadSecrets('F:/GitHub/ObjectStore/.env'), +}; + +const zoneId = process.env.CF_ZONE_ID || secrets.CF_ZONE_ID; +const tokens = [ + ['CF_DNS_API_TOKEN', process.env.CF_DNS_API_TOKEN || secrets.CF_DNS_API_TOKEN], + ['CLOUDFLARE_USER_API', process.env.CLOUDFLARE_USER_API || secrets.CLOUDFLARE_USER_API], + ['CF_ZERO_TRUST_TOKEN', process.env.CF_ZERO_TRUST_TOKEN || secrets.CF_ZERO_TRUST_TOKEN], + ['CF_AI_WORKERS_API', process.env.CF_AI_WORKERS_API || secrets.CF_AI_WORKERS_API], + ['CF_WORKER_R2_API', process.env.CF_WORKER_R2_API || secrets.CF_WORKER_R2_API], + ['CF_AI_WORKERS_API_alt', secrets.CF_AI_WORKERS_API], +].filter(([, t]) => t && t.length > 10); + +console.log('zone', zoneId?.slice(0, 8), 'token candidates', tokens.length); + +async function verify(token) { + const r = await fetch('https://api.cloudflare.com/client/v4/user/tokens/verify', { + headers: { Authorization: `Bearer ${token}` }, + }); + const j = await r.json(); + return { ok: j.success, status: j.result?.status, err: j.errors?.[0]?.message, http: r.status }; +} + +async function dnsList(token) { + const r = await fetch( + `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records?name=casting.grudge-studio.com`, + { headers: { Authorization: `Bearer ${token}` } }, + ); + const j = await r.json(); + return { ok: j.success, result: j.result || [], err: j.errors?.[0]?.message, http: r.status }; +} + +async function upsertA(token) { + const body = { + type: 'A', + name: 'casting', + content: '76.76.21.21', + ttl: 1, + proxied: false, + comment: 'Vercel casting-abilities-threejs', + }; + const list = await dnsList(token); + if (!list.ok) throw new Error(`list failed: ${list.err || list.http}`); + + let out; + if (list.result.length) { + const rec = list.result[0]; + if (rec.type !== 'A') { + await fetch( + `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${rec.id}`, + { method: 'DELETE', headers: { Authorization: `Bearer ${token}` } }, + ); + out = await ( + await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }) + ).json(); + console.log('replaced', rec.type, '→ A', out.success, out.errors?.[0]?.message || ''); + } else { + out = await ( + await fetch( + `https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records/${rec.id}`, + { + method: 'PUT', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }, + ) + ).json(); + console.log('updated A', out.success, out.result?.content, 'proxied', out.result?.proxied); + } + } else { + out = await ( + await fetch(`https://api.cloudflare.com/client/v4/zones/${zoneId}/dns_records`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(body), + }) + ).json(); + console.log('created A', out.success, out.result?.content, 'proxied', out.result?.proxied); + } + if (!out.success) throw new Error(out.errors?.[0]?.message || 'upsert failed'); + return out; +} + +async function probeDoh() { + for (let i = 0; i < 12; i++) { + await new Promise((r) => setTimeout(r, 2000)); + const j = await ( + await fetch( + 'https://cloudflare-dns.com/dns-query?name=casting.grudge-studio.com&type=A', + { headers: { accept: 'application/dns-json' } }, + ) + ).json(); + const ans = (j.Answer || []).map((a) => a.data).join(','); + console.log('DoH', i, 'status', j.Status, ans || '(empty)'); + if (ans.includes('76.76.21.21')) { + try { + const http = await fetch('https://casting.grudge-studio.com/', { + redirect: 'follow', + }); + console.log('HTTP casting', http.status); + } catch (e) { + console.log('HTTP casting err', e.message); + } + try { + const h = await fetch('https://casting.grudge-studio.com/api/health'); + const t = await h.text(); + console.log('HTTP /api/health', h.status, t.slice(0, 120)); + } catch (e) { + console.log('health err', e.message); + } + return true; + } + } + return false; +} + +let token = null; +for (const [name, t] of tokens) { + const v = await verify(t); + console.log(name, 'verify', v.http, v.ok, v.status || v.err || ''); + if (!v.ok) continue; + const d = await dnsList(t); + console.log(name, 'dns', d.http, d.ok, d.err || `count=${d.result.length}`); + if (d.ok) { + token = t; + console.log('using', name); + break; + } +} + +if (!token) { + console.error('No CF token with Zone DNS Edit. secretnow CF_DNS_API_TOKEN is invalid/revoked.'); + console.error('Mint new token: Cloudflare → API Tokens → Edit zone DNS → grudge-studio.com'); + process.exit(2); +} + +await upsertA(token); +const ok = await probeDoh(); +process.exit(ok ? 0 : 1); diff --git a/scripts/audit-asset-library.mjs b/scripts/audit-asset-library.mjs new file mode 100644 index 0000000..2628c7b --- /dev/null +++ b/scripts/audit-asset-library.mjs @@ -0,0 +1,120 @@ +/** + * Audit half-done / conflicting asset + catalog sources. + * Run: node scripts/audit-asset-library.mjs + */ +async function head(u) { + try { + const r = await fetch(u, { method: 'HEAD' }); + return r.status; + } catch (e) { + return `err:${e.message?.slice(0, 30)}`; + } +} +async function getStatus(u) { + try { + const r = await fetch(u); + return r.status; + } catch (e) { + return `err`; + } +} + +const INFO = 'https://info.grudge-studio.com/api/v1'; +const CDN = 'https://assets.grudge-studio.com'; + +const catalogs = [ + ['USE', 'game-library.json'], + ['USE', 'canonical-items-manifest.json'], + ['USE', 'master-weapon-prefabs.json'], + ['USE', 'master-weaponSkills.json'], + ['USE', 't0-weapons.json'], + ['USE', 'master-armor.json'], + ['USE', 'master-items.json'], + ['USE', 'master-item-prefabs.json'], + ['USE', 'master-relics.json'], + ['USE', 'master-mounts.json'], + ['USE', 'master-classRelics.json'], + ['USE', 'master-consumables.json'], + ['USE', 'master-materials.json'], + ['USE', 'master-recipes.json'], + ['USE', 'master-registry.json'], + ['DESIGN_ONLY', 'weapons.json'], + ['DESIGN_ONLY', 'armor.json'], + ['DEPRECATED', 'master-weapons.json'], + ['DEPRECATED', 'master-t0-items.json'], + ['DEPRECATED', 'weapon-prefabs.json'], + ['DEPRECATED', 'weaponSkills.json'], + ['DEPRECATED', 'items-database.json'], + ['PLANNED', 'master-armor-prefabs.json'] +]; + +console.log('=== Catalog HTTP status (info.grudge-studio.com) ==='); +for (const [role, f] of catalogs) { + const st = await getStatus(`${INFO}/${f}`); + console.log(`${String(st).padEnd(5)} ${role.padEnd(12)} ${f}`); +} + +console.log('\n=== Family GLBs (prod/gltf/weapons) ==='); +for (const fam of ['sword', 'axe', 'staff', 'bow', 'dagger', 'hammer', 'assault_rifle', 'wand', 'shield']) { + const st = await head(`${CDN}/prod/gltf/weapons/${fam}.glb`); + console.log(`${String(st).padEnd(5)} ${fam}.glb`); +} + +const prefabs = await (await fetch(`${INFO}/master-weapon-prefabs.json`)).json(); +console.log('\n=== master-weapon-prefabs meta ==='); +console.log('total', prefabs.total, 'd1Table', prefabs.d1Table, 'd1Database', prefabs.d1Database); +console.log('r2Layout', JSON.stringify(prefabs.r2Layout)); + +let dropOk = 0, + dropBad = 0, + modelOk = 0, + modelBad = 0; +const dropSamples = []; +const modelSamples = []; +for (const s of prefabs.prefabs) { + if (s.assets?.dropPrefabR2Key && dropSamples.length < 6) dropSamples.push(s); + if ((s.modelUrl || s.prodGltfUrl) && modelSamples.length < 6) modelSamples.push(s); +} +console.log('\n=== Sample dropPrefab HEAD ==='); +for (const s of dropSamples) { + const key = String(s.assets.dropPrefabR2Key).replace(/^\/+/, ''); + const st = await head(`${CDN}/${key}`); + if (st === 200) dropOk++; + else dropBad++; + console.log(st, s.id, key.slice(-60)); +} +console.log('\n=== Sample modelUrl HEAD ==='); +for (const s of modelSamples) { + const u = s.modelUrl || s.prodGltfUrl; + const st = await head(u); + if (st === 200) modelOk++; + else modelBad++; + console.log(st, s.id, u.split('/').pop()); +} + +const t0w = prefabs.prefabs.find((x) => x.id === 't0-wand'); +const t0n = prefabs.prefabs.find((x) => x.id === 't0-nature-staff'); +console.log('\n=== T0 in prefabs ==='); +console.log( + 't0-wand', + t0w + ? { model: t0w.modelUrl || t0w.prodGltfUrl, icon: !!t0w.assets?.iconUrl } + : 'NOT IN master-weapon-prefabs' +); +console.log( + 't0-nature-staff', + t0n + ? { model: t0n.modelUrl || t0n.prodGltfUrl, icon: !!t0n.assets?.iconUrl } + : 'NOT IN master-weapon-prefabs' +); + +const t0list = await (await fetch(`${INFO}/t0-weapons.json`)).json(); +console.log( + '\nt0-weapons.json count', + (t0list.weapons || []).length, + 'ids', + (t0list.weapons || []).map((w) => w.id).join(', ') +); + +console.log('\n=== SUMMARY COUNTS (samples only) ==='); +console.log({ dropOk, dropBad, modelOk, modelBad }); diff --git a/scripts/fix-casting-dns.mjs b/scripts/fix-casting-dns.mjs new file mode 100644 index 0000000..e69f7b2 --- /dev/null +++ b/scripts/fix-casting-dns.mjs @@ -0,0 +1,155 @@ +/** + * Fix casting.grudge-studio.com DNS → Vercel A 76.76.21.21 + */ +import fs from 'fs'; + +function loadEnv(p) { + try { + return fs.readFileSync(p, 'utf8'); + } catch { + return ''; + } +} +function get(env, k) { + const m = env.match(new RegExp(`^${k}=(.+)$`, 'm')); + if (!m) return null; + return m[1].trim().replace(/^["']|["']$/g, ''); +} + +const envBlobs = [ + loadEnv('F:/GitHub/ObjectStore/.env'), + loadEnv('C:/Users/nugye/Documents/grudox/.env.local'), + loadEnv('C:/Users/nugye/Documents/Game-Studio-Tool/gst-api-deploy/.env'), + loadEnv('C:/Users/nugye/Documents/grudge-studio-auth/.env.local.production'), + loadEnv('C:/Users/nugye/Documents/1111111/GrudgeBuilder/.env.local'), + loadEnv('C:/Users/nugye/Desktop/grudgeproduction/.env'), + // Optional: export CF_DNS_API_TOKEN in shell after minting Edit zone DNS token + process.env.CF_DNS_API_TOKEN + ? `CF_DNS_API_TOKEN=${process.env.CF_DNS_API_TOKEN}\nCF_ZONE_ID=${process.env.CF_ZONE_ID || ''}` + : '' +]; + +const zoneId = + envBlobs.map((e) => get(e, 'CF_ZONE_ID')).find(Boolean) || null; + +const tokenKeys = [ + 'CF_DNS_API_TOKEN', + 'CF_ZERO_TRUST_TOKEN', + 'CLOUDFLARE_API_TOKEN', + 'CLOUDFLARE_USER_API', + 'CF_D1_API_TOKEN' +]; +const tokens = []; +for (const env of envBlobs) { + for (const k of tokenKeys) { + const t = get(env, k); + if (t && !tokens.includes(t)) tokens.push(t); + } +} + +console.log('zone', zoneId?.slice(0, 8), 'tokens', tokens.length); + +async function authHeaders(token) { + return { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }; +} + +let headers = null; +for (const t of tokens) { + const h = await authHeaders(t); + const url = zoneId + ? `https://api.cloudflare.com/client/v4/zones/${zoneId}` + : 'https://api.cloudflare.com/client/v4/zones?name=grudge-studio.com'; + const r = await fetch(url, { headers: h }); + const j = await r.json(); + console.log('try token…', r.status, j.success, j.errors?.[0]?.message || j.result?.name || j.result?.[0]?.name || ''); + if (j.success) { + headers = h; + if (!zoneId && j.result?.[0]?.id) { + // zone discovered + } + break; + } +} + +if (!headers) { + console.error('\n=== MANUAL DNS FIX REQUIRED ==='); + console.error('Cloudflare API tokens in .env are expired/invalid (403).'); + console.error('In Cloudflare dashboard → grudge-studio.com → DNS → Add record:'); + console.error(' Type: A'); + console.error(' Name: casting'); + console.error(' IPv4: 76.76.21.21'); + console.error(' Proxy: DNS only (grey cloud)'); + console.error(' TTL: Auto'); + console.error('Then wait 1–2 min and open https://casting.grudge-studio.com/'); + console.error('Working fallback NOW: https://casting-abilities-threejs.vercel.app/'); + process.exit(2); +} + +const zid = + zoneId || + ( + await ( + await fetch('https://api.cloudflare.com/client/v4/zones?name=grudge-studio.com', { + headers + }) + ).json() + ).result?.[0]?.id; + +const list = await ( + await fetch( + `https://api.cloudflare.com/client/v4/zones/${zid}/dns_records?name=casting.grudge-studio.com`, + { headers } + ) +).json(); + +const body = { + type: 'A', + name: 'casting', + content: '76.76.21.21', + ttl: 1, + proxied: false, + comment: 'Vercel casting-abilities-threejs' +}; + +let out; +if (list.result?.length) { + const id = list.result[0].id; + out = await ( + await fetch(`https://api.cloudflare.com/client/v4/zones/${zid}/dns_records/${id}`, { + method: 'PUT', + headers, + body: JSON.stringify(body) + }) + ).json(); + console.log('updated', out.success, out.errors?.[0]?.message || ''); +} else { + out = await ( + await fetch(`https://api.cloudflare.com/client/v4/zones/${zid}/dns_records`, { + method: 'POST', + headers, + body: JSON.stringify(body) + }) + ).json(); + console.log('created', out.success, out.errors?.[0]?.message || ''); +} + +if (!out.success) process.exit(1); + +for (let i = 0; i < 8; i++) { + await new Promise((r) => setTimeout(r, 2500)); + const dj = await ( + await fetch('https://cloudflare-dns.com/dns-query?name=casting.grudge-studio.com&type=A', { + headers: { accept: 'application/dns-json' } + }) + ).json(); + console.log('DoH', i, dj.Status, dj.Answer?.map((a) => a.data)); + if (dj.Answer?.length) { + try { + const http = await fetch('https://casting.grudge-studio.com/'); + console.log('HTTP', http.status); + if (http.status === 200) process.exit(0); + } catch (e) { + console.log('HTTP err', e.message); + } + } +} diff --git a/scripts/patch-cast-channel.mjs b/scripts/patch-cast-channel.mjs new file mode 100644 index 0000000..9b07515 --- /dev/null +++ b/scripts/patch-cast-channel.mjs @@ -0,0 +1,388 @@ +/** + * One-shot patch: cast channel helpers + wrap useSkill fire in cast bar. + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const p = path.join(__dirname, '../src/combat/DrcCombatController.js'); +let s = fs.readFileSync(p, 'utf8'); + +if (!s.includes('getCastBarState()')) { + const anchor = ' /**\n * Fire skill slot 0–3.\n * @param {number} slot\n * @returns {boolean}\n */\n useSkill(slot) {'; + const i = s.indexOf(anchor); + if (i < 0) { + console.error('useSkill anchor not found'); + process.exit(1); + } + const block = ` get isCasting() { + return !!this._cast; + } + + getCastBarState() { + if (!this._cast) return null; + const c = this._cast; + const prog = MathUtils.clamp( + (this.elapsed - c.startedAt) / Math.max(0.001, c.duration), + 0, + 1, + ); + return { + active: true, + label: c.label, + progress01: prog, + duration: c.duration, + remaining: Math.max(0, c.endsAt - this.elapsed), + element: c.element || 'arcane', + }; + } + + _beginCast(opts) { + const duration = Math.max(0, Number(opts.duration) || 0); + if (duration < 0.06) { + opts.onComplete?.(); + return true; + } + if (this._cast) this._interruptCast('replaced', false); + this._cast = { + label: opts.label || 'Casting', + element: opts.element || 'arcane', + startedAt: this.elapsed, + duration, + endsAt: this.elapsed + duration, + onComplete: opts.onComplete, + interruptible: opts.interruptible !== false, + aim: opts.aim || null, + }; + this.character.setCasting?.(true, opts.aim || null); + this.character.playWeaponCombat?.('cast') || + this.character.playCastFlourish?.() || + this.character.requestOneShot?.('cast'); + this.onCastBar?.(this.getCastBarState()); + return true; + } + + _tickCast(dt, keys) { + if (!this._cast) return; + if (this._cast.interruptible && settings.drc?.castInterruptOnMove !== false) { + const moving = + keys?.has?.('KeyW') || + keys?.has?.('KeyA') || + keys?.has?.('KeyS') || + keys?.has?.('KeyD') || + keys?.has?.('ArrowUp') || + keys?.has?.('ArrowDown') || + keys?.has?.('ArrowLeft') || + keys?.has?.('ArrowRight'); + if (moving && this.elapsed - this._cast.startedAt > 0.12) { + this._interruptCast('moved'); + return; + } + } + if (this.elapsed >= this._cast.endsAt) { + const done = this._cast.onComplete; + this._clearCast(); + try { + done?.(); + } catch (e) { + console.warn('[DrcCombat] cast complete', e); + } + this.onCastBar?.(null); + return; + } + this.character.setCasting?.(true, this._cast.aim || null); + this.onCastBar?.(this.getCastBarState()); + void dt; + } + + _interruptCast(reason = 'cancel', toast = true) { + if (!this._cast) return; + this._clearCast(); + this.character.setCasting?.(false); + if (toast) this.onToast(reason === 'moved' ? 'Cast interrupted' : 'Cast cancelled'); + this.onCastBar?.({ active: false, interrupted: true }); + } + + _clearCast() { + this._cast = null; + this.character.setCasting?.(false); + } + +`; + s = s.slice(0, i) + block + s.slice(i); + console.log('inserted cast helpers'); +} + +const oldChunk = ` if (!this._spendResources(costs.mana, costs.stamina, skill.label)) return false; + this._cdUntil.set(skill.id, this.elapsed + skill.cooldown); + + // Animation one-shot from equipped weapon pack (magic cast · sword attack · bow attack) + const animRole = bound + ? animRoleForSkill({ + labStyle: bound.labPack === 'magic' ? 'spell' : skill.style, + animation: null, + id: bound.skillId, + name: bound.name, + slotType: 'ability' + }) + : skill.animRole; + if (animRole === 'attack' || skill.animRole === 'attack') { + this.character.playWeaponCombat?.('attack') || + this.character.playWeaponAttack?.() || + this.character.requestOneShot?.('attack'); + } else { + this.character.playWeaponCombat?.('cast') || + this.character.requestOneShot?.(skill.animRole) || + this.character.playCastFlourish?.(); + } + + const yaw = this.character.facing; + if (this.aim?.valid) _fwd.copy(this.aim.forward); + else _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + this.character.getCastOrigin(_origin); + const pose = { + origin: this.character.position.clone(), + forward: _fwd.clone(), + aim: _end.copy(_origin).addScaledVector(_fwd, skill.rangeM * 0.65) + }; + + // Catalog VFX when bound; else DRC skill beauty + if (bound) {`; + +const newChunk = ` if (!this._spendResources(costs.mana, costs.stamina, skill.label)) return false; + this._cdUntil.set(skill.id, this.elapsed + skill.cooldown); + if (this._cast) { + this.onToast('Already casting'); + return false; + } + + const yaw = this.character.facing; + if (this.aim?.valid) _fwd.copy(this.aim.forward); + else _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + this.character.getCastOrigin(_origin); + const pose = { + origin: this.character.position.clone(), + forward: _fwd.clone(), + aim: _end.copy(_origin).addScaledVector(_fwd, skill.rangeM * 0.65) + }; + const aimPt = { x: pose.aim.x, y: pose.aim.y, z: pose.aim.z }; + const castDur = + skill.style === 'melee' + ? 0 + : Math.max(0, Number(skill.castDuration ?? skill.castTime ?? 0.55)); + + const releaseSpell = () => { + // Catalog VFX when bound; else DRC skill beauty + if (bound) {`; + +if (!s.includes(oldChunk)) { + console.error('oldChunk not found — already patched?'); +} else { + s = s.replace(oldChunk, newChunk); + console.log('wrapped releaseSpell start'); +} + +const oldEnd = ` // Bound catalog skill on a spell-less bar slot — still fire residual / path + if (bound) { + this._fireMeleeResidual(skill, pose); + this.onToast(\`\${boundName} · \${bound.skillId}\`); + return true; + } + + return false; + }`; + +const newEnd = ` // Bound catalog skill on a spell-less bar slot — still fire residual / path + if (bound) { + this._fireMeleeResidual(skill, pose); + this.onToast(\`\${boundName} · \${bound.skillId}\`); + return; + } + }; + + // Instant melee / near-zero castDuration + if (skill.style === 'melee' || castDur < 0.08) { + const animRole = bound + ? animRoleForSkill({ + labStyle: bound.labPack === 'magic' ? 'spell' : skill.style, + animation: null, + id: bound.skillId, + name: bound.name, + slotType: 'ability' + }) + : skill.animRole; + if (animRole === 'attack' || skill.animRole === 'attack' || skill.style === 'melee') { + this.character.playWeaponCombat?.('attack') || + this.character.playWeaponAttack?.() || + this.character.requestOneShot?.('attack'); + } else { + this.character.playWeaponCombat?.('cast') || + this.character.requestOneShot?.(skill.animRole) || + this.character.playCastFlourish?.(); + } + releaseSpell(); + return true; + } + + this._beginCast({ + label: skill.label, + duration: castDur, + element: skill.element || skill.abilityElement || 'arcane', + interruptible: true, + aim: aimPt, + onComplete: releaseSpell + }); + this.onToast(\`\${skill.label} · cast \${castDur.toFixed(1)}s\`); + return true; + }`; + +if (!s.includes(oldEnd)) { + console.error('oldEnd not found'); + // show nearby + const j = s.indexOf('Bound catalog skill'); + console.log(JSON.stringify(s.slice(j, j + 250))); + process.exit(1); +} +s = s.replace(oldEnd, newEnd); + +// Path cast: channel before fire +const pathOld = ` const costs = pathCastCosts(holdSec, length, kind, element); + if (!this._spendResources(costs.mana, costs.stamina, 'Path cast')) return null; + this.lastCastIntensity = costs.intensity; + const intensity = costs.intensity; + + // Signature VFX at high intensity (Inferno / Blizzard / etc.) + const sig = signatureForElement(element === 'arcane' ? 'arcane' : element); + if (intensity >= 2.4 && sig) { + this.vfx?.deploy?.(sig.impactEffectId, { + origin: this.character.position.clone(), + forward: _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)).clone(), + intensity: intensity * 0.85 + }); + } + + const facing = _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)).clone(); + const pathPose = { + origin: this.character.position.clone(), + forward: facing, + aim: curve.getPoint(1), + intensity + }; + + // AOE: compress path to short arc at endpoint for impact placement + if (kind === 'aoe') { + const end = curve.getPoint(1); + const mid = end.clone(); + mid.y += 0.4; + const start = end.clone().add(new Vector3(0.01, 0.8, 0.01)); + const short = new CatmullRomCurve3([start, mid, end], false, 'catmullrom', 0.5); + this.abilities.select(element); + this.abilities.cast(short, element); + this.vfx?.deployPresentation?.(element, { ...pathPose, aim: end.clone() }, { + pathKind: 'aoe', + meteor: element === 'fire' && intensity >= 2.2 + }); + } else { + this.abilities.select(element); + this.abilities.cast(curve, element); + this.vfx?.deployPresentation?.(element, pathPose, { + pathKind: kind, + meteor: kind === 'stream' && element === 'fire' && intensity >= 2.4, + volley: kind === 'stream' && (element === 'fire' || element === 'arcane'), + shield: kind === 'wall' && element === 'storm' + }); + } + + this.character.requestOneShot?.('cast') || this.character.playCastFlourish?.(); + const labels = { aoe: 'AOE place', spikes: 'Spikes', wall: 'Wall', stream: 'Stream' }; + const sigName = intensity >= 2.4 && sig ? \` · \${sig.label}\` : ''; + this.onToast( + \`Staff · \${labels[kind]} (\${element}) · ×\${intensity.toFixed(1)} · −\${costs.mana}MP −\${costs.stamina}STA\${sigName}\` + ); + return { kind, element, intensity, mana: costs.mana, stamina: costs.stamina }; + } +}`; + +const pathNew = ` const costs = pathCastCosts(holdSec, length, kind, element); + if (!this._spendResources(costs.mana, costs.stamina, 'Path cast')) return null; + this.lastCastIntensity = costs.intensity; + const intensity = costs.intensity; + // Cast time scales with hold (min 0.35s, max 1.6s) — bar + cast anim before release + const pathCastTime = MathUtils.clamp(0.35 + holdSec * 0.35 + length * 0.02, 0.35, 1.6); + const endPt = curve.getPoint(1); + const aimPt = { x: endPt.x, y: endPt.y + 0.2, z: endPt.z }; + const labels = { aoe: 'AOE place', spikes: 'Spikes', wall: 'Wall', stream: 'Stream' }; + + const releasePath = () => { + const sig = signatureForElement(element === 'arcane' ? 'arcane' : element); + if (intensity >= 2.4 && sig) { + this.vfx?.deploy?.(sig.impactEffectId, { + origin: this.character.position.clone(), + forward: _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)).clone(), + intensity: intensity * 0.85 + }); + } + const facing = _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)).clone(); + const pathPose = { + origin: this.character.position.clone(), + forward: facing, + aim: curve.getPoint(1), + intensity + }; + if (kind === 'aoe') { + const end = curve.getPoint(1); + const mid = end.clone(); + mid.y += 0.4; + const start = end.clone().add(new Vector3(0.01, 0.8, 0.01)); + const short = new CatmullRomCurve3([start, mid, end], false, 'catmullrom', 0.5); + this.abilities.select(element); + this.abilities.cast(short, element); + this.vfx?.deployPresentation?.(element, { ...pathPose, aim: end.clone() }, { + pathKind: 'aoe', + meteor: element === 'fire' && intensity >= 2.2 + }); + } else { + this.abilities.select(element); + this.abilities.cast(curve, element); + this.vfx?.deployPresentation?.(element, pathPose, { + pathKind: kind, + meteor: kind === 'stream' && element === 'fire' && intensity >= 2.4, + volley: kind === 'stream' && (element === 'fire' || element === 'arcane'), + shield: kind === 'wall' && element === 'storm' + }); + } + const sigName = intensity >= 2.4 && sig ? \` · \${sig.label}\` : ''; + this.onToast( + \`Staff · \${labels[kind]} (\${element}) · ×\${intensity.toFixed(1)} · −\${costs.mana}MP −\${costs.stamina}STA\${sigName}\` + ); + }; + + if (this._cast) this._interruptCast('replaced', false); + this._beginCast({ + label: \`\${labels[kind]} · \${element}\`, + duration: pathCastTime, + element, + interruptible: true, + aim: aimPt, + onComplete: releasePath + }); + return { kind, element, intensity, mana: costs.mana, stamina: costs.stamina, castTime: pathCastTime }; + } +}`; + +if (s.includes(pathOld)) { + s = s.replace(pathOld, pathNew); + console.log('patched path cast channel'); +} else { + console.warn('path cast block not matched (manual check)'); +} + +// Fix returns inside releaseSpell that still say return true (buff branches) +// They become return; already handled for end; buffs still return true inside releaseSpell which exits only the arrow - OK as return true from arrow is fine. + +fs.writeFileSync(p, s); +console.log('wrote', p); +console.log('has getCastBarState', s.includes('getCastBarState')); +console.log('has releaseSpell', s.includes('releaseSpell')); +console.log('has pathCastTime', s.includes('pathCastTime')); diff --git a/scripts/patch-fill-api.mjs b/scripts/patch-fill-api.mjs new file mode 100644 index 0000000..bc38309 --- /dev/null +++ b/scripts/patch-fill-api.mjs @@ -0,0 +1,179 @@ +import fs from 'node:fs'; + +const p = 'src/ui/InventoryPanel.js'; +let s = fs.readFileSync(p, 'utf8'); +const start = s.indexOf(' /* ── Fleet API'); +const end = s.indexOf(' dispose() {', start); +if (start < 0 || end < 0) { + console.error('markers', start, end); + process.exit(1); +} + +const neu = ` /* ── Fleet API ─────────────────────────────────────────────── */ + + _fillApi() { + const host = this.el.querySelector('[data-panel="api"]'); + if (!host || this._tab !== 'api') return; + + const health = this.api.lastHealth; + const fleet = fleetDeploySnapshot(); + const assets = UI_ASSET_CATALOG.map( + (a) => + \`
\${a.id}\${a.role}
\` + ).join(''); + const catCounts = this._gameItems?.counts + ? Object.entries(this._gameItems.counts) + .map(([k, v]) => \`\${k}:\${v}\`) + .join(' · ') + : 'not loaded'; + + host.innerHTML = \` +

Dev + production — icons CDN 496 + lab minerals; catalogs info/objectstore; player bag Railway.

+
+
API base\${(FLEET_API_DEFAULT || 'same-origin').replace('https://', '')}
+
Health\${health ? (health.ok ? \`OK \${health.latencyMs}ms\` : health.message) : 'not checked'}
+
Token
+
Catalog\${catCounts}
+
Player\${fleet.authority.player}
+
Binaries\${fleet.authority.binaries}
+
+
+ + + + +
+
+ + + +
+ +
+ +
+

UI assets (\${UI_ASSET_CATALOG.length}) · harvest → Main bag + DropBag with icons

+
\${assets}
+
+ + \`; + + const out = host.querySelector('[data-api-out]'); + host.querySelector('[data-token]').textContent = this.api.getToken() ? 'present' : 'none'; + + host.querySelector('[data-fleet-bundle]')?.addEventListener('click', async () => { + out.textContent = 'loading…'; + const b = await this.api.fleetStatusBundle(); + host.querySelector('[data-health]').textContent = b.health?.ok + ? \`OK \${b.health.latencyMs}ms\` + : b.health?.message || 'fail'; + host.querySelector('[data-token]').textContent = b.hasToken ? 'present' : 'none'; + out.textContent = JSON.stringify(b, null, 2); + this.onToast( + b.health?.ok + ? \`Fleet · chars \${b.characters?.characters?.length ?? 0} · inv \${b.inventory?.items?.length ?? 0}\` + : b.health?.message || 'done' + ); + }); + + host.querySelector('[data-ping]')?.addEventListener('click', async () => { + out.textContent = 'ping…'; + const st = await this.api.health(); + host.querySelector('[data-health]').textContent = st.ok ? \`OK \${st.latencyMs}ms\` : st.message; + out.textContent = JSON.stringify({ health: st, fleet: fleet.authority }, null, 2); + this.onToast(st.ok ? 'API healthy' : 'API unreachable'); + }); + + host.querySelector('[data-chars]')?.addEventListener('click', async () => { + out.textContent = 'fetch…'; + const r = await this.api.listCharacters(); + out.textContent = JSON.stringify( + { + message: r.message, + count: r.characters.length, + sample: r.characters.slice(0, 5).map((c) => ({ + id: c.id || c.characterId, + name: c.name || c.displayName, + race: c.race || c.raceId + })) + }, + null, + 2 + ); + this.onToast(r.message); + }); + + host.querySelector('[data-inv]')?.addEventListener('click', async () => { + out.textContent = 'inventory…'; + const r = await this.api.listInventory(); + out.textContent = JSON.stringify(r, null, 2); + this.onToast(r.message); + }); + + host.querySelector('[data-load-cat]')?.addEventListener('click', async () => { + out.textContent = 'catalog…'; + await this._warmCatalogIcons(); + const c = this._gameItems; + host.querySelector('[data-cat]').textContent = c?.counts + ? Object.entries(c.counts) + .map(([k, v]) => \`\${k}:\${v}\`) + .join(' · ') + : 'fail'; + out.textContent = JSON.stringify({ counts: c?.counts, urls: c?.urls }, null, 2); + this.onToast('Catalog loaded · bag icons refreshed'); + this.refresh(); + }); + + host.querySelector('[data-seed-t0]')?.addEventListener('click', async () => { + try { + const cat = this._gameItems || (await loadGameItemCatalog()); + this._gameItems = cat; + const t0 = cat?.byCategory?.t0 || []; + let n = 0; + for (const row of t0.slice(0, 8)) { + const bi = bagItemFromCatalogRow(row); + if (bi) { + bagAdd(bi); + n++; + } + } + for (const id of ['t0-sword', 't0-wand', 't0-tool']) { + const row = t0.find((r) => r.id === id); + if (row) bagAdd(bagItemFromCatalogRow(row)); + else + bagAdd( + enrichBagSlotIcon({ + id, + name: id, + kind: 'weapon', + qty: 1, + slotHint: 'mainHand' + }) + ); + } + this.onToast(\`Imported \${n} T0 rows → bag\`); + this._setTab('inventory'); + } catch (err) { + this.onToast(err?.message || 'T0 import fail'); + } + }); + + host.querySelector('[data-open-equip]')?.addEventListener('click', () => { + this.openTab('character'); + this.onToast('Character paperdoll · RMB equip menu'); + }); + } + +`; + +fs.writeFileSync(p, s.slice(0, start) + neu + s.slice(end)); +console.log('patched _fillApi ok'); diff --git a/scripts/scaffold-t0-weapon-play.mjs b/scripts/scaffold-t0-weapon-play.mjs new file mode 100644 index 0000000..1afed79 --- /dev/null +++ b/scripts/scaffold-t0-weapon-play.mjs @@ -0,0 +1,162 @@ +/** + * Scaffold a T0 weapon play-stack checklist manifest. + * + * Does NOT invent catalog weapons — writes docs/play-stack/.play-stack.json + * so agents/authors track mesh · pack · loco · skills · VFX · deploy. + * + * Usage: + * node scripts/scaffold-t0-weapon-play.mjs --id t0-sword + * node scripts/scaffold-t0-weapon-play.mjs --list + * + * Docs: docs/T0_WEAPON_PLAY_STACK_SSOT.md · skill casting-t0-weapon-play + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const OUT_DIR = path.join(ROOT, 'docs/play-stack'); + +const args = process.argv.slice(2); +function flag(name, def = null) { + const i = args.indexOf(name); + if (i < 0) return def; + return args[i + 1] && !args[i + 1].startsWith('-') ? args[i + 1] : true; +} + +const weaponId = flag('--id'); +const list = flag('--list'); + +/** Known T0 starters (catalog SSOT — do not invent beyond this without ObjectStore). */ +const T0_KNOWN = [ + 't0-sword', + 't0-axe1h', + 't0-dagger', + 't0-hammer1h', + 't0-spear', + 't0-greatsword', + 't0-greataxe', + 't0-hammer2h', + 't0-bow', + 't0-crossbow', + 't0-gun', + 't0-wand', + 't0-nature-staff', + 't0-tool', + 't0-offhand-tome' +]; + +function packHint(id) { + const s = String(id || '').toLowerCase(); + if (/bow|crossbow/.test(s)) return 'longbow'; + if (/gun|pistol|rifle/.test(s)) return 'pistol'; + if (/wand|staff|tome|magic/.test(s)) return 'magic'; + return 'sword_shield'; +} + +if (!weaponId && !list) { + console.log(`Usage: + node scripts/scaffold-t0-weapon-play.mjs --id + node scripts/scaffold-t0-weapon-play.mjs --list + +Known T0: ${T0_KNOWN.join(', ')} +Doc: docs/T0_WEAPON_PLAY_STACK_SSOT.md +`); + process.exit(1); +} + +fs.mkdirSync(OUT_DIR, { recursive: true }); + +if (list) { + const files = fs.readdirSync(OUT_DIR).filter((f) => f.endsWith('.json')); + console.log(`Play-stack manifests: ${files.length}`); + for (const f of files) console.log(' ', f); + process.exit(0); +} + +const id = String(weaponId); +if (!T0_KNOWN.includes(id) && flag('--force') !== true) { + console.warn( + `Warning: ${id} not in known T0 list. Pass --force if catalog already has it.` + ); +} + +const outPath = path.join(OUT_DIR, `${id}.play-stack.json`); +if (fs.existsSync(outPath) && flag('--force') !== true) { + console.error(`Exists: ${outPath} (pass --force to overwrite)`); + process.exit(2); +} + +const template = { + $schema: 'casting-t0-weapon-play-stack', + id, + product: 'weapon', + note: + 'Checklist only. Catalog rows + skill ids stay ObjectStore / t0-weapons SSOT. Do not invent skills.', + docs: [ + 'docs/T0_WEAPON_PLAY_STACK_SSOT.md', + 'docs/T0_WEAPONS_SSOT.md', + 'docs/WEAPON_SKILL_PRODUCTION_SSOT.md' + ], + catalog: { + t0Url: 'https://info.grudge-studio.com/api/v1/t0-weapons.json', + skillsBrowse: 'https://info.grudge-studio.com/WEAPON_SKILLS.html', + verified: false + }, + layers: { + identity: { done: false, weaponType: null, tier: 0 }, + mesh: { done: false, modelUrl: null, meshSlot: null }, + icon: { done: false, iconUrl: null }, + animPack: { done: false, pack: packHint(id) }, + locomotion: { + done: false, + roles: ['idle', 'walk', 'run', 'jump', 'fall', 'roll', 'dodge'] + }, + traversal: { + done: false, + note: 'Usually N/A for T0; back/ride separate (windsurf / wings)' + }, + skills: { + done: false, + slot1: null, + slot2: null, + slot3Options: [], + scaffoldCmd: + 'node scripts/scaffold-weapon-skill.mjs --id --weapon TYPE' + }, + productionOverrides: { done: false, paths: [] }, + projectiles: { done: false, meshUrls: [] }, + dualLoadout: { + done: false, + note: 'Weapon 1/2 paperdoll · combat Tap Q swap' + }, + prefabExport: { done: false }, + smoke: { + done: false, + url: `https://casting-abilities-threejs.vercel.app/?t0=${id}` + }, + deploy: { done: false, vercel: false, skillDo: false } + }, + checklist: { + catalogIdVerified: false, + equipLive: false, + animPackBound: false, + skillsFire: false, + projectilesSI: false, + dualQSwap: false, + readinessGreen: false, + smokeCastingLab: false + } +}; + +fs.writeFileSync(outPath, JSON.stringify(template, null, 2) + '\n'); +console.log(`Wrote ${outPath}`); +console.log(` +Next: + 1. Open t0-weapons.json — verify ${id} + 2. Equip lab ?t0=${id} + 3. Scaffold each skill: node scripts/scaffold-weapon-skill.mjs --id … + 4. Tick layers in this JSON as you complete them + 5. Skill: casting-t0-weapon-play +`); diff --git a/scripts/scaffold-weapon-skill.mjs b/scripts/scaffold-weapon-skill.mjs new file mode 100644 index 0000000..90c5d27 --- /dev/null +++ b/scripts/scaffold-weapon-skill.mjs @@ -0,0 +1,125 @@ +/** + * Scaffold a production override for one catalog weapon skill. + * + * Does NOT invent catalog skill ids — only writes optional override JSON + * so agents can perfect anim/VFX/physics/status per skill. + * + * Usage: + * node scripts/scaffold-weapon-skill.mjs --id staff_fire_bolt + * node scripts/scaffold-weapon-skill.mjs --id t0_sword_practice_slash --weapon SWORD + * node scripts/scaffold-weapon-skill.mjs --id staff_ice_nova --list-ready + * + * Output: skills/production/.json + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const OUT_DIR = path.join(ROOT, 'skills/production'); +const PUBLIC_DIR = path.join(ROOT, 'public/skills/production'); + +const args = process.argv.slice(2); +function flag(name, def = null) { + const i = args.indexOf(name); + if (i < 0) return def; + return args[i + 1] && !args[i + 1].startsWith('-') ? args[i + 1] : true; +} + +const skillId = flag('--id'); +const weapon = flag('--weapon', 'STAFF'); +const listReady = flag('--list-ready'); + +if (!skillId && !listReady) { + console.log(`Usage: + node scripts/scaffold-weapon-skill.mjs --id [--weapon SWORD|STAFF|BOW|…] + node scripts/scaffold-weapon-skill.mjs --list-ready + +Catalog browse: https://info.grudge-studio.com/WEAPON_SKILLS.html +Pattern doc: docs/WEAPON_SKILL_PRODUCTION_SSOT.md +`); + process.exit(1); +} + +fs.mkdirSync(OUT_DIR, { recursive: true }); +fs.mkdirSync(PUBLIC_DIR, { recursive: true }); + +if (listReady) { + const files = fs.readdirSync(OUT_DIR).filter((f) => f.endsWith('.json')); + console.log(`Production overrides: ${files.length}`); + for (const f of files) console.log(' ', f); + process.exit(0); +} + +const outPath = path.join(OUT_DIR, `${skillId}.json`); +const publicPath = path.join(PUBLIC_DIR, `${skillId}.json`); +if (fs.existsSync(outPath) && flag('--force') !== true) { + console.error(`Exists: ${outPath} (pass --force to overwrite)`); + process.exit(2); +} + +const template = { + $schema: 'weapon-skill-production-override', + id: skillId, + weaponTypeId: String(weapon).toUpperCase(), + note: + 'Optional overrides only. Catalog id/dmg/cd/effects stay SSOT from master-weaponSkills / t0-weapons.', + catalogUrl: 'https://info.grudge-studio.com/WEAPON_SKILLS.html', + // ── Animation (perfect this skill’s cast) ── + animRole: null, + animClip: null, + hitFrameDelay: 0.18, + comboStages: null, + // ── VFX layers ── + castEffectId: null, + travelEffectId: null, + impactEffectId: null, + meshId: null, + projectileMeshUrl: null, + chargeMeshUrl: null, + intensity: 1, + // ── Delivery ── + delivery: null, + element: null, + // ── Physics (SI) ── + force: null, + knockbackMm: null, + knockupVy: null, + aoeM: null, + projectileSpeed: null, + contactRadius: null, + collider: null, + // ── Statuses (or leave null to parse catalog effects[]) ── + statuses: null, + // Example statuses when authoring freeze/stun/push: + // statuses: [ + // { id: 'freeze', durationSec: 2.5, magnitude: 1 }, + // { id: 'push', durationSec: 0.35, magnitude: 200 }, + // { id: 'stun', durationSec: 1.0, magnitude: 1 } + // ], + checklist: { + catalogIdVerified: false, + animClipVerified: false, + vfxCastTravelImpact: false, + physicsSI: false, + statusesMapped: false, + readinessGreen: false, + smokeCastingLab: false + } +}; + +const body = JSON.stringify(template, null, 2) + '\n'; +fs.writeFileSync(outPath, body); +fs.writeFileSync(publicPath, body); +console.log(`Wrote ${outPath}`); +console.log(`Wrote ${publicPath} (runtime fetch ./skills/production/${skillId}.json)`); +console.log(` +Next: + 1. Open https://info.grudge-studio.com/WEAPON_SKILLS.html — verify id ${skillId} + 2. Fill animRole/clip + VFX ids + physics SI in BOTH copies (or edit skills/ then re-copy) + 3. Map statuses (freeze/stun/push/slow/burn/root) + 4. Runtime: loadProductionOverride + compileProductionWeaponSkill + 5. Smoke casting lab: equip weapon · Q · digit skill + 6. assessProductionReadiness → green +`); diff --git a/scripts/split-element-attack-meshes.mjs b/scripts/split-element-attack-meshes.mjs new file mode 100644 index 0000000..b6ad044 --- /dev/null +++ b/scripts/split-element-attack-meshes.mjs @@ -0,0 +1,522 @@ +/** + * Split element attack meshes for Casting lab: + * - assorted_rock_pack → rock-0..7 (earth pull / linear / aim) + * - bubbles_2 → bubble-0..5 sample frames (water / freeze VFX) — NOT whole 37MB pack + * - teleport_arrow → arrow-path (linear attack path) + * - arrow_curved → arrow-loft (throw / place / trap / summon) + * + * Usage: node scripts/split-element-attack-meshes.mjs + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const PUB = path.join(ROOT, 'public/models/vfx'); + +const ROCK_DIAM = 0.55; +const BUBBLE_DIAM = 0.28; +const ARROW_PATH_LEN = 1.1; +const ARROW_LOFT_LEN = 0.95; + +function readGlb(file) { + const buf = fs.readFileSync(file); + if (buf.toString('utf8', 0, 4) !== 'glTF') throw new Error('not glb: ' + file); + const jsonLen = buf.readUInt32LE(12); + const json = JSON.parse(buf.slice(20, 20 + jsonLen).toString('utf8')); + let off = 20 + jsonLen; + while (off % 4 !== 0) off++; + let bin = Buffer.alloc(0); + if (off + 8 <= buf.length) { + const chunkLen = buf.readUInt32LE(off); + bin = buf.slice(off + 8, off + 8 + chunkLen); + } + return { json, bin }; +} + +function writeGlb(json, bin) { + let jsonBuf = Buffer.from(JSON.stringify(json), 'utf8'); + const jsonPad = (4 - (jsonBuf.length % 4)) % 4; + if (jsonPad) jsonBuf = Buffer.concat([jsonBuf, Buffer.alloc(jsonPad, 0x20)]); + let binBuf = bin || Buffer.alloc(0); + const binPad = (4 - (binBuf.length % 4)) % 4; + if (binPad) binBuf = Buffer.concat([binBuf, Buffer.alloc(binPad, 0)]); + const totalLen = 12 + 8 + jsonBuf.length + (binBuf.length ? 8 + binBuf.length : 0); + const out = Buffer.alloc(totalLen); + out.write('glTF', 0); + out.writeUInt32LE(2, 4); + out.writeUInt32LE(totalLen, 8); + out.writeUInt32LE(jsonBuf.length, 12); + out.write('JSON', 16); + jsonBuf.copy(out, 20); + if (binBuf.length) { + const bo = 20 + jsonBuf.length; + out.writeUInt32LE(binBuf.length, bo); + out.write('BIN\0', bo + 4); + binBuf.copy(out, bo + 8); + } + return out; +} + +function mat4Compose(t = [0, 0, 0], r = null, s = [1, 1, 1]) { + const [tx, ty, tz] = t; + const [sx, sy, sz] = s; + let x = 0, + y = 0, + z = 0, + w = 1; + if (r && r.length === 4) { + x = r[0]; + y = r[1]; + z = r[2]; + w = r[3]; + } + const x2 = x + x, + y2 = y + y, + z2 = z + z; + const xx = x * x2, + xy = x * y2, + xz = x * z2; + const yy = y * y2, + yz = y * z2, + zz = z * z2; + const wx = w * x2, + wy = w * y2, + wz = w * z2; + return [ + (1 - (yy + zz)) * sx, + (xy + wz) * sx, + (xz - wy) * sx, + 0, + (xy - wz) * sy, + (1 - (xx + zz)) * sy, + (yz + wx) * sy, + 0, + (xz + wy) * sz, + (yz - wx) * sz, + (1 - (xx + yy)) * sz, + 0, + tx, + ty, + tz, + 1 + ]; +} + +function mat4Mul(a, b) { + const o = new Array(16); + for (let c = 0; c < 4; c++) { + for (let r = 0; r < 4; r++) { + o[c * 4 + r] = + a[0 * 4 + r] * b[c * 4 + 0] + + a[1 * 4 + r] * b[c * 4 + 1] + + a[2 * 4 + r] * b[c * 4 + 2] + + a[3 * 4 + r] * b[c * 4 + 3]; + } + } + return o; +} + +function mat4TransformPoint(m, p) { + const x = p[0], + y = p[1], + z = p[2]; + const w = m[3] * x + m[7] * y + m[11] * z + m[15] || 1; + return [ + (m[0] * x + m[4] * y + m[8] * z + m[12]) / w, + (m[1] * x + m[5] * y + m[9] * z + m[13]) / w, + (m[2] * x + m[6] * y + m[10] * z + m[14]) / w + ]; +} + +function nodeLocalMatrix(n) { + if (n.matrix && n.matrix.length === 16) return n.matrix.slice(); + return mat4Compose(n.translation, n.rotation, n.scale); +} + +function computeWorldMatrices(nodes) { + const world = new Array(nodes.length); + const childOf = new Map(); + nodes.forEach((n, i) => { + for (const c of n.children || []) childOf.set(c, i); + }); + function worldOf(i) { + if (world[i]) return world[i]; + const local = nodeLocalMatrix(nodes[i] || {}); + const p = childOf.get(i); + world[i] = p === undefined ? local : mat4Mul(worldOf(p), local); + return world[i]; + } + for (let i = 0; i < nodes.length; i++) worldOf(i); + return world; +} + +function accessorBounds(json, accessorIndex) { + const acc = json.accessors[accessorIndex]; + if (!acc?.min || !acc?.max) return null; + return { min: acc.min.slice(0, 3), max: acc.max.slice(0, 3) }; +} + +function transformBounds(min, max, world) { + const outMin = [Infinity, Infinity, Infinity]; + const outMax = [-Infinity, -Infinity, -Infinity]; + for (const x of [min[0], max[0]]) + for (const y of [min[1], max[1]]) + for (const z of [min[2], max[2]]) { + const p = mat4TransformPoint(world, [x, y, z]); + for (let k = 0; k < 3; k++) { + outMin[k] = Math.min(outMin[k], p[k]); + outMax[k] = Math.max(outMax[k], p[k]); + } + } + return { min: outMin, max: outMax }; +} + +function compactBin(srcJson, srcBin, usedViewIndices) { + const sorted = [...usedViewIndices].sort((a, b) => a - b); + const chunks = []; + const viewMap = new Map(); + let cursor = 0; + const newViews = []; + for (const vi of sorted) { + const bv = srcJson.bufferViews[vi]; + const offset = bv.byteOffset || 0; + const length = bv.byteLength; + const pad = (4 - (cursor % 4)) % 4; + if (pad) { + chunks.push(Buffer.alloc(pad, 0)); + cursor += pad; + } + chunks.push(srcBin.slice(offset, offset + length)); + viewMap.set(vi, newViews.length); + newViews.push({ + buffer: 0, + byteOffset: cursor, + byteLength: length, + ...(bv.byteStride !== undefined ? { byteStride: bv.byteStride } : {}), + ...(bv.target !== undefined ? { target: bv.target } : {}) + }); + cursor += length; + } + return { bin: Buffer.concat(chunks.length ? chunks : [Buffer.alloc(0)]), newViews, viewMap }; +} + +function extractCluster(srcJson, srcBin, meshNodeIndices, world, clusterId, targetDiameter, matTint) { + const nodes = srcJson.nodes; + const meshNodes = [...meshNodeIndices].filter((i) => nodes[i].mesh !== undefined); + if (!meshNodes.length) throw new Error(`cluster ${clusterId}: no mesh nodes`); + + let gMin = [Infinity, Infinity, Infinity]; + let gMax = [-Infinity, -Infinity, -Infinity]; + for (const i of meshNodes) { + const mesh = srcJson.meshes[nodes[i].mesh]; + for (const prim of mesh.primitives || []) { + const pos = prim.attributes?.POSITION; + if (pos === undefined) continue; + const b = accessorBounds(srcJson, pos); + if (!b) continue; + const wb = transformBounds(b.min, b.max, world[i]); + for (let k = 0; k < 3; k++) { + gMin[k] = Math.min(gMin[k], wb.min[k]); + gMax[k] = Math.max(gMax[k], wb.max[k]); + } + } + } + if (!Number.isFinite(gMin[0])) throw new Error(`cluster ${clusterId}: no bounds`); + + const center = [ + (gMin[0] + gMax[0]) / 2, + (gMin[1] + gMax[1]) / 2, + (gMin[2] + gMax[2]) / 2 + ]; + const size = [gMax[0] - gMin[0], gMax[1] - gMin[1], gMax[2] - gMin[2]]; + const maxDim = Math.max(...size); + const scale = maxDim > 1e-6 ? targetDiameter / maxDim : 1; + + const usedMeshes = new Map(); + const usedMats = new Map(); + const usedAccessors = new Set(); + const usedViews = new Set(); + + for (const i of meshNodes) { + const mi = nodes[i].mesh; + if (!usedMeshes.has(mi)) usedMeshes.set(mi, usedMeshes.size); + const mesh = srcJson.meshes[mi]; + for (const prim of mesh.primitives || []) { + if (prim.material !== undefined && !usedMats.has(prim.material)) { + usedMats.set(prim.material, usedMats.size); + } + for (const key of Object.keys(prim.attributes || {})) { + usedAccessors.add(prim.attributes[key]); + } + if (prim.indices !== undefined) usedAccessors.add(prim.indices); + } + } + for (const ai of usedAccessors) { + const acc = srcJson.accessors[ai]; + if (acc?.bufferView !== undefined) usedViews.add(acc.bufferView); + } + + const { bin: compact, newViews, viewMap } = compactBin(srcJson, srcBin, usedViews); + const accList = [...usedAccessors].sort((a, b) => a - b); + const accMap = new Map(accList.map((a, i) => [a, i])); + const meshList = [...usedMeshes.keys()]; + const meshMap = new Map(meshList.map((m, i) => [m, i])); + const matList = [...usedMats.keys()]; + const matMap = new Map(matList.map((m, i) => [m, i])); + + const newAccessors = accList.map((ai) => { + const a = { ...srcJson.accessors[ai] }; + if (a.bufferView !== undefined) a.bufferView = viewMap.get(a.bufferView); + return a; + }); + + const newMeshes = meshList.map((mi) => { + const mesh = srcJson.meshes[mi]; + return { + name: mesh.name || `mesh_${mi}`, + primitives: (mesh.primitives || []).map((prim) => { + const attrs = {}; + for (const [k, v] of Object.entries(prim.attributes || {})) { + attrs[k] = accMap.get(v); + } + const out = { attributes: attrs, mode: prim.mode ?? 4 }; + if (prim.indices !== undefined) out.indices = accMap.get(prim.indices); + if (prim.material !== undefined) out.material = matMap.get(prim.material); + return out; + }) + }; + }); + + const newMaterials = matList.map((mi, idx) => { + const base = srcJson.materials?.[mi] || {}; + if (matTint) { + return { + name: `${clusterId}_mat_${idx}`, + doubleSided: true, + pbrMetallicRoughness: { + baseColorFactor: matTint.color || [0.55, 0.55, 0.55, 1], + metallicFactor: matTint.metal ?? 0.05, + roughnessFactor: matTint.rough ?? 0.75 + }, + emissiveFactor: matTint.emissive || [0, 0, 0] + }; + } + return { ...base, name: base.name || `${clusterId}_mat_${idx}`, doubleSided: true }; + }); + if (!newMaterials.length) { + newMaterials.push({ + name: `${clusterId}_default`, + doubleSided: true, + pbrMetallicRoughness: { + baseColorFactor: matTint?.color || [0.6, 0.6, 0.6, 1], + metallicFactor: 0.05, + roughnessFactor: 0.7 + } + }); + } + + const newNodes = [ + { + name: clusterId, + children: meshNodes.map((_, i) => i + 1), + translation: [0, 0, 0], + scale: [scale, scale, scale] + } + ]; + for (const i of meshNodes) { + const t = mat4TransformPoint(world[i], [0, 0, 0]); + newNodes.push({ + name: nodes[i].name || `leaf_${i}`, + mesh: meshMap.get(nodes[i].mesh), + translation: [t[0] - center[0], t[1] - center[1], t[2] - center[2]] + }); + } + + const outJson = { + asset: { version: '2.0', generator: 'split-element-attack-meshes' }, + scene: 0, + scenes: [{ name: clusterId, nodes: [0] }], + nodes: newNodes, + meshes: newMeshes, + materials: newMaterials, + accessors: newAccessors, + bufferViews: newViews, + buffers: [{ byteLength: compact.length }] + }; + + return { glb: writeGlb(outJson, compact), maxDim, scale }; +} + +function meshNodeIndices(json) { + const out = []; + json.nodes.forEach((n, i) => { + if (n.mesh !== undefined) out.push(i); + }); + return out; +} + +function main() { + // ── Rocks ────────────────────────────────────────────── + const rockDir = path.join(PUB, 'rocks'); + fs.mkdirSync(rockDir, { recursive: true }); + const rockSrc = path.join(rockDir, 'assorted_rock_pack_src.glb'); + const { json: rockJson, bin: rockBin } = readGlb(rockSrc); + const rockWorld = computeWorldMatrices(rockJson.nodes); + const rockNodes = meshNodeIndices(rockJson); + const rockManifest = { source: 'assorted_rock_pack.glb', diameterM: ROCK_DIAM, rocks: [] }; + rockNodes.forEach((ni, idx) => { + const id = `rock-${idx}`; + const { glb, maxDim, scale } = extractCluster( + rockJson, + rockBin, + new Set([ni]), + rockWorld, + id, + ROCK_DIAM, + { + color: [0.45 + idx * 0.03, 0.4, 0.35, 1], + metal: 0.08, + rough: 0.85 + } + ); + fs.writeFileSync(path.join(rockDir, `${id}.glb`), glb); + rockManifest.rocks.push({ + id, + path: `models/vfx/rocks/${id}.glb`, + diameterM: ROCK_DIAM, + rawMax: maxDim, + scale + }); + console.log(`rock ${id} raw=${maxDim.toFixed(2)} scale=${scale.toFixed(5)}`); + }); + fs.writeFileSync(path.join(rockDir, 'rock-manifest.json'), JSON.stringify(rockManifest, null, 2)); + + // ── Bubbles (sample frames only — never ship whole pack as one VFX) ── + const bubDir = path.join(PUB, 'bubbles'); + fs.mkdirSync(bubDir, { recursive: true }); + const bubSrc = path.join(bubDir, 'bubbles_2_src.glb'); + const { json: bubJson, bin: bubBin } = readGlb(bubSrc); + const bubWorld = computeWorldMatrices(bubJson.nodes); + const bubNodes = meshNodeIndices(bubJson); + // Evenly sample 6 frames from 178 + const sampleCount = 6; + const samples = []; + for (let s = 0; s < sampleCount; s++) { + const i = Math.round((s / Math.max(1, sampleCount - 1)) * (bubNodes.length - 1)); + samples.push(bubNodes[i]); + } + const bubManifest = { + source: 'bubbles_2.glb', + note: 'Sample frames only — do not load bubbles_2_src.glb whole at runtime', + diameterM: BUBBLE_DIAM, + bubbles: [] + }; + samples.forEach((ni, idx) => { + const id = `bubble-${idx}`; + const { glb, maxDim, scale } = extractCluster( + bubJson, + bubBin, + new Set([ni]), + bubWorld, + id, + BUBBLE_DIAM, + { + color: [0.55, 0.85, 1, 0.55], + metal: 0.05, + rough: 0.2, + emissive: [0.1, 0.35, 0.55] + } + ); + fs.writeFileSync(path.join(bubDir, `${id}.glb`), glb); + bubManifest.bubbles.push({ + id, + path: `models/vfx/bubbles/${id}.glb`, + diameterM: BUBBLE_DIAM, + rawMax: maxDim, + scale + }); + console.log(`bubble ${id} raw=${maxDim.toFixed(2)} scale=${scale.toFixed(5)}`); + }); + fs.writeFileSync(path.join(bubDir, 'bubble-manifest.json'), JSON.stringify(bubManifest, null, 2)); + + // ── Arrows ───────────────────────────────────────────── + const arrDir = path.join(PUB, 'arrows'); + fs.mkdirSync(arrDir, { recursive: true }); + + // Linear path arrow (teleport_arrow — flatter long shape) + { + const src = path.join(arrDir, 'teleport_arrow_src.glb'); + const { json, bin } = readGlb(src); + const world = computeWorldMatrices(json.nodes); + const nodes = meshNodeIndices(json); + const { glb, maxDim, scale } = extractCluster( + json, + bin, + new Set(nodes), + world, + 'arrow-path', + ARROW_PATH_LEN, + { + color: [0.35, 0.75, 1, 1], + metal: 0.35, + rough: 0.35, + emissive: [0.1, 0.4, 0.8] + } + ); + fs.writeFileSync(path.join(arrDir, 'arrow-path.glb'), glb); + console.log(`arrow-path raw=${maxDim.toFixed(2)} scale=${scale.toFixed(5)}`); + } + + // Lofted throw / place arrow (arrow_curved — more loft visual) + { + const src = path.join(arrDir, 'arrow_curved_src.glb'); + const { json, bin } = readGlb(src); + const world = computeWorldMatrices(json.nodes); + const nodes = meshNodeIndices(json); + const { glb, maxDim, scale } = extractCluster( + json, + bin, + new Set(nodes), + world, + 'arrow-loft', + ARROW_LOFT_LEN, + { + color: [1, 0.55, 0.2, 1], + metal: 0.25, + rough: 0.4, + emissive: [0.6, 0.25, 0.05] + } + ); + fs.writeFileSync(path.join(arrDir, 'arrow-loft.glb'), glb); + console.log(`arrow-loft raw=${maxDim.toFixed(2)} scale=${scale.toFixed(5)}`); + } + + const arrowManifest = { + systems: { + path: { + id: 'arrow-path', + mesh: 'models/vfx/arrows/arrow-path.glb', + source: 'teleport_arrow.glb', + lengthM: ARROW_PATH_LEN, + loft: 0, + role: 'linear attack path — distance sets end event (explode/aoe/blink/return)', + endEvents: ['impact', 'explode', 'aoe', 'blink', 'return'] + }, + loft: { + id: 'arrow-loft', + mesh: 'models/vfx/arrows/arrow-loft.glb', + source: 'arrow_curved.glb', + lengthM: ARROW_LOFT_LEN, + loft: 0.42, + role: 'throw / place device / trap / summon arrow', + endEvents: ['throw', 'place_device', 'trap', 'summon'] + } + } + }; + fs.writeFileSync(path.join(arrDir, 'arrow-manifest.json'), JSON.stringify(arrowManifest, null, 2)); + console.log('done'); +} + +main(); diff --git a/scripts/split-gd-orbs-and-charge.mjs b/scripts/split-gd-orbs-and-charge.mjs new file mode 100644 index 0000000..0137afe --- /dev/null +++ b/scripts/split-gd-orbs-and-charge.mjs @@ -0,0 +1,516 @@ +/** + * Split gd_orbs_pack.glb → 6 individual element orbs (sphere+torus each). + * Normalize kamehameha_charging.glb → small charge shell (SI ~0.35 m). + * + * Never load whole multipacks as projectiles — use per-orb / charge files only. + * + * Usage: node scripts/split-gd-orbs-and-charge.mjs + */ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const ROOT = path.resolve(__dirname, '..'); +const ORB_SRC = path.join(ROOT, 'public/models/vfx/orbs/gd_orbs_pack_src.glb'); +const CHARGE_SRC = path.join(ROOT, 'public/models/vfx/charge/kamehameha_charging_src.glb'); +const ORB_OUT = path.join(ROOT, 'public/models/vfx/orbs'); +const CHARGE_OUT = path.join(ROOT, 'public/models/vfx/charge'); + +const ORB_DIAMETER_M = 0.45; +const CHARGE_DIAMETER_M = 0.35; + +/** Sphere root + Torus ring pairs (inspected hierarchy). */ +const ORB_DEFS = [ + { + id: 'orb-fire', + label: 'Fire Orb', + element: 'fire', + color: [1, 0.35, 0.08, 1], + emissive: [1, 0.2, 0.02], + roots: ['Sphere_0', 'Torus_1'] + }, + { + id: 'orb-ice', + label: 'Ice Orb', + element: 'ice', + color: [0.35, 0.82, 1, 1], + emissive: [0.1, 0.45, 0.85], + roots: ['Sphere.001_2', 'Torus.001_3'] + }, + { + id: 'orb-nature', + label: 'Nature Orb', + element: 'nature', + color: [0.35, 0.85, 0.28, 1], + emissive: [0.08, 0.55, 0.12], + roots: ['Sphere.002_4', 'Torus.002_5'] + }, + { + id: 'orb-storm', + label: 'Storm Orb', + element: 'storm', + color: [0.55, 0.85, 1, 1], + emissive: [0.2, 0.55, 1], + roots: ['Sphere.003_6', 'Torus.003_7'] + }, + { + id: 'orb-holy', + label: 'Holy Orb', + element: 'holy', + color: [1, 0.9, 0.45, 1], + emissive: [0.95, 0.75, 0.2], + roots: ['Sphere.004_8', 'Torus.004_9'] + }, + { + id: 'orb-arcane', + label: 'Arcane Orb', + element: 'arcane', + color: [0.7, 0.35, 1, 1], + emissive: [0.45, 0.12, 0.9], + roots: ['Sphere.005_10', 'Torus.005_11'] + } +]; + +function readGlb(file) { + const buf = fs.readFileSync(file); + if (buf.toString('utf8', 0, 4) !== 'glTF') throw new Error('not glb: ' + file); + const jsonLen = buf.readUInt32LE(12); + const json = JSON.parse(buf.slice(20, 20 + jsonLen).toString('utf8')); + let off = 20 + jsonLen; + while (off % 4 !== 0) off++; + let bin = Buffer.alloc(0); + if (off + 8 <= buf.length) { + const chunkLen = buf.readUInt32LE(off); + bin = buf.slice(off + 8, off + 8 + chunkLen); + } + return { json, bin }; +} + +function writeGlb(json, bin) { + let jsonBuf = Buffer.from(JSON.stringify(json), 'utf8'); + const jsonPad = (4 - (jsonBuf.length % 4)) % 4; + if (jsonPad) jsonBuf = Buffer.concat([jsonBuf, Buffer.alloc(jsonPad, 0x20)]); + let binBuf = bin || Buffer.alloc(0); + const binPad = (4 - (binBuf.length % 4)) % 4; + if (binPad) binBuf = Buffer.concat([binBuf, Buffer.alloc(binPad, 0)]); + + const totalLen = 12 + 8 + jsonBuf.length + (binBuf.length ? 8 + binBuf.length : 0); + const out = Buffer.alloc(totalLen); + out.write('glTF', 0); + out.writeUInt32LE(2, 4); + out.writeUInt32LE(totalLen, 8); + out.writeUInt32LE(jsonBuf.length, 12); + out.write('JSON', 16); + jsonBuf.copy(out, 20); + if (binBuf.length) { + const bo = 20 + jsonBuf.length; + out.writeUInt32LE(binBuf.length, bo); + out.write('BIN\0', bo + 4); + binBuf.copy(out, bo + 8); + } + return out; +} + +function mat4Compose(t = [0, 0, 0], r = null, s = [1, 1, 1]) { + const [tx, ty, tz] = t; + const [sx, sy, sz] = s; + let x = 0, + y = 0, + z = 0, + w = 1; + if (r && r.length === 4) { + x = r[0]; + y = r[1]; + z = r[2]; + w = r[3]; + } + const x2 = x + x, + y2 = y + y, + z2 = z + z; + const xx = x * x2, + xy = x * y2, + xz = x * z2; + const yy = y * y2, + yz = y * z2, + zz = z * z2; + const wx = w * x2, + wy = w * y2, + wz = w * z2; + return [ + (1 - (yy + zz)) * sx, + (xy + wz) * sx, + (xz - wy) * sx, + 0, + (xy - wz) * sy, + (1 - (xx + zz)) * sy, + (yz + wx) * sy, + 0, + (xz + wy) * sz, + (yz - wx) * sz, + (1 - (xx + yy)) * sz, + 0, + tx, + ty, + tz, + 1 + ]; +} + +function mat4Mul(a, b) { + const o = new Array(16); + for (let c = 0; c < 4; c++) { + for (let r = 0; r < 4; r++) { + o[c * 4 + r] = + a[0 * 4 + r] * b[c * 4 + 0] + + a[1 * 4 + r] * b[c * 4 + 1] + + a[2 * 4 + r] * b[c * 4 + 2] + + a[3 * 4 + r] * b[c * 4 + 3]; + } + } + return o; +} + +function mat4TransformPoint(m, p) { + const x = p[0], + y = p[1], + z = p[2]; + const w = m[3] * x + m[7] * y + m[11] * z + m[15] || 1; + return [ + (m[0] * x + m[4] * y + m[8] * z + m[12]) / w, + (m[1] * x + m[5] * y + m[9] * z + m[13]) / w, + (m[2] * x + m[6] * y + m[10] * z + m[14]) / w + ]; +} + +function nodeLocalMatrix(n) { + if (n.matrix && n.matrix.length === 16) return n.matrix.slice(); + return mat4Compose(n.translation, n.rotation, n.scale); +} + +function computeWorldMatrices(nodes) { + const world = new Array(nodes.length); + const childOf = new Map(); + nodes.forEach((n, i) => { + for (const c of n.children || []) childOf.set(c, i); + }); + function worldOf(i) { + if (world[i]) return world[i]; + const local = nodeLocalMatrix(nodes[i] || {}); + const p = childOf.get(i); + world[i] = p === undefined ? local : mat4Mul(worldOf(p), local); + return world[i]; + } + for (let i = 0; i < nodes.length; i++) worldOf(i); + return world; +} + +function collectSubtree(nodes, rootIdx, out = new Set()) { + if (out.has(rootIdx)) return out; + out.add(rootIdx); + for (const c of nodes[rootIdx].children || []) collectSubtree(nodes, c, out); + return out; +} + +function accessorBounds(json, accessorIndex) { + const acc = json.accessors[accessorIndex]; + if (!acc?.min || !acc?.max) return null; + return { min: acc.min.slice(0, 3), max: acc.max.slice(0, 3) }; +} + +function transformBounds(min, max, world) { + const outMin = [Infinity, Infinity, Infinity]; + const outMax = [-Infinity, -Infinity, -Infinity]; + for (const x of [min[0], max[0]]) + for (const y of [min[1], max[1]]) + for (const z of [min[2], max[2]]) { + const p = mat4TransformPoint(world, [x, y, z]); + for (let k = 0; k < 3; k++) { + outMin[k] = Math.min(outMin[k], p[k]); + outMax[k] = Math.max(outMax[k], p[k]); + } + } + return { min: outMin, max: outMax }; +} + +function compactBin(srcJson, srcBin, usedViewIndices) { + const sorted = [...usedViewIndices].sort((a, b) => a - b); + const chunks = []; + const viewMap = new Map(); + let cursor = 0; + const newViews = []; + for (const vi of sorted) { + const bv = srcJson.bufferViews[vi]; + const offset = bv.byteOffset || 0; + const length = bv.byteLength; + const pad = (4 - (cursor % 4)) % 4; + if (pad) { + chunks.push(Buffer.alloc(pad, 0)); + cursor += pad; + } + chunks.push(srcBin.slice(offset, offset + length)); + viewMap.set(vi, newViews.length); + newViews.push({ + buffer: 0, + byteOffset: cursor, + byteLength: length, + ...(bv.byteStride !== undefined ? { byteStride: bv.byteStride } : {}), + ...(bv.target !== undefined ? { target: bv.target } : {}) + }); + cursor += length; + } + return { bin: Buffer.concat(chunks.length ? chunks : [Buffer.alloc(0)]), newViews, viewMap }; +} + +function extractCluster(srcJson, srcBin, nodeIndices, world, clusterId, targetDiameter, colorDef) { + const nodes = srcJson.nodes; + const meshNodes = [...nodeIndices].filter((i) => nodes[i].mesh !== undefined); + if (!meshNodes.length) throw new Error(`cluster ${clusterId}: no mesh nodes`); + + let gMin = [Infinity, Infinity, Infinity]; + let gMax = [-Infinity, -Infinity, -Infinity]; + for (const i of meshNodes) { + const mesh = srcJson.meshes[nodes[i].mesh]; + for (const prim of mesh.primitives || []) { + const pos = prim.attributes?.POSITION; + if (pos === undefined) continue; + const b = accessorBounds(srcJson, pos); + if (!b) continue; + const wb = transformBounds(b.min, b.max, world[i]); + for (let k = 0; k < 3; k++) { + gMin[k] = Math.min(gMin[k], wb.min[k]); + gMax[k] = Math.max(gMax[k], wb.max[k]); + } + } + } + if (!Number.isFinite(gMin[0])) throw new Error(`cluster ${clusterId}: no bounds`); + + const center = [ + (gMin[0] + gMax[0]) / 2, + (gMin[1] + gMax[1]) / 2, + (gMin[2] + gMax[2]) / 2 + ]; + const size = [gMax[0] - gMin[0], gMax[1] - gMin[1], gMax[2] - gMin[2]]; + const maxDim = Math.max(...size); + const scale = maxDim > 1e-6 ? targetDiameter / maxDim : 1; + + const usedMeshes = new Map(); + const usedMats = new Map(); + const usedAccessors = new Set(); + const usedViews = new Set(); + + for (const i of meshNodes) { + const mi = nodes[i].mesh; + if (!usedMeshes.has(mi)) usedMeshes.set(mi, usedMeshes.size); + const mesh = srcJson.meshes[mi]; + for (const prim of mesh.primitives || []) { + if (prim.material !== undefined && !usedMats.has(prim.material)) { + usedMats.set(prim.material, usedMats.size); + } + for (const key of Object.keys(prim.attributes || {})) { + usedAccessors.add(prim.attributes[key]); + } + if (prim.indices !== undefined) usedAccessors.add(prim.indices); + } + } + for (const ai of usedAccessors) { + const acc = srcJson.accessors[ai]; + if (acc?.bufferView !== undefined) usedViews.add(acc.bufferView); + } + + const { bin: compact, newViews, viewMap } = compactBin(srcJson, srcBin, usedViews); + + const accList = [...usedAccessors].sort((a, b) => a - b); + const accMap = new Map(accList.map((a, i) => [a, i])); + const meshList = [...usedMeshes.keys()]; + const meshMap = new Map(meshList.map((m, i) => [m, i])); + const matList = [...usedMats.keys()]; + const matMap = new Map(matList.map((m, i) => [m, i])); + + const newAccessors = accList.map((ai) => { + const a = { ...srcJson.accessors[ai] }; + if (a.bufferView !== undefined) a.bufferView = viewMap.get(a.bufferView); + return a; + }); + + const newMeshes = meshList.map((mi) => { + const mesh = srcJson.meshes[mi]; + return { + name: mesh.name || `mesh_${mi}`, + primitives: (mesh.primitives || []).map((prim) => { + const attrs = {}; + for (const [k, v] of Object.entries(prim.attributes || {})) { + attrs[k] = accMap.get(v); + } + const out = { attributes: attrs, mode: prim.mode ?? 4 }; + if (prim.indices !== undefined) out.indices = accMap.get(prim.indices); + if (prim.material !== undefined) out.material = matMap.get(prim.material); + return out; + }) + }; + }); + + // Elemental materials (author pack is black — bake base color + emissive) + const newMaterials = matList.map((mi, idx) => { + const base = srcJson.materials?.[mi] || {}; + const isRing = idx > 0; // second+ mats get brighter ring look + const col = colorDef?.color || [0.8, 0.8, 0.8, 1]; + const em = colorDef?.emissive || [0.2, 0.2, 0.2]; + const ringCol = isRing + ? [Math.min(1, col[0] * 1.15), Math.min(1, col[1] * 1.15), Math.min(1, col[2] * 1.15), 0.92] + : col; + return { + name: `${clusterId}_${base.name || 'mat'}_${idx}`, + doubleSided: true, + alphaMode: isRing ? 'BLEND' : 'OPAQUE', + pbrMetallicRoughness: { + baseColorFactor: ringCol, + metallicFactor: isRing ? 0.15 : 0.05, + roughnessFactor: isRing ? 0.25 : 0.35 + }, + emissiveFactor: isRing + ? [Math.min(1, em[0] * 1.2), Math.min(1, em[1] * 1.2), Math.min(1, em[2] * 1.2)] + : em + }; + }); + + // Flatten meshes under one root at origin with SI scale + const newNodes = [ + { + name: clusterId, + children: meshNodes.map((_, i) => i + 1), + translation: [0, 0, 0], + scale: [scale, scale, scale] + } + ]; + for (const i of meshNodes) { + const w = world[i]; + // Bake world position relative to center into translation + const t = mat4TransformPoint(w, [0, 0, 0]); + newNodes.push({ + name: nodes[i].name || `leaf_${i}`, + mesh: meshMap.get(nodes[i].mesh), + translation: [t[0] - center[0], t[1] - center[1], t[2] - center[2]] + }); + } + + const outJson = { + asset: { version: '2.0', generator: 'split-gd-orbs-and-charge' }, + scene: 0, + scenes: [{ name: clusterId, nodes: [0] }], + nodes: newNodes, + meshes: newMeshes, + materials: newMaterials, + accessors: newAccessors, + bufferViews: newViews, + buffers: [{ byteLength: compact.length }] + }; + + return { + glb: writeGlb(outJson, compact), + maxDim, + scale, + center, + size + }; +} + +function nodeIndexByName(nodes, name) { + const i = nodes.findIndex((n) => n.name === name); + if (i < 0) throw new Error(`node not found: ${name}`); + return i; +} + +function main() { + fs.mkdirSync(ORB_OUT, { recursive: true }); + fs.mkdirSync(CHARGE_OUT, { recursive: true }); + + if (!fs.existsSync(ORB_SRC)) throw new Error('missing ' + ORB_SRC); + if (!fs.existsSync(CHARGE_SRC)) throw new Error('missing ' + CHARGE_SRC); + + const { json: orbJson, bin: orbBin } = readGlb(ORB_SRC); + const orbWorld = computeWorldMatrices(orbJson.nodes); + const manifest = { + source: 'gd_orbs_pack.glb', + diameterM: ORB_DIAMETER_M, + note: 'Individual orbs for staff attacks — never load gd_orbs_pack_src whole as projectile', + orbs: [] + }; + + for (const def of ORB_DEFS) { + const indices = new Set(); + for (const rootName of def.roots) { + const ri = nodeIndexByName(orbJson.nodes, rootName); + collectSubtree(orbJson.nodes, ri, indices); + } + const { glb, maxDim, scale } = extractCluster( + orbJson, + orbBin, + indices, + orbWorld, + def.id, + ORB_DIAMETER_M, + def + ); + const outPath = path.join(ORB_OUT, `${def.id}.glb`); + fs.writeFileSync(outPath, glb); + console.log( + `wrote ${def.id}.glb rawMax=${maxDim.toFixed(3)} scale=${scale.toFixed(4)} → ${ORB_DIAMETER_M}m` + ); + manifest.orbs.push({ + id: def.id, + label: def.label, + element: def.element, + path: `models/vfx/orbs/${def.id}.glb`, + diameterM: ORB_DIAMETER_M, + color: def.color, + emissive: def.emissive, + staffUse: { + projectileScale: 1, + tipScale: 0.55, + chargeScale: 0.8 + } + }); + } + fs.writeFileSync(path.join(ORB_OUT, 'orb-manifest.json'), JSON.stringify(manifest, null, 2)); + console.log('wrote orb-manifest.json'); + + // Charge shell + const { json: chJson, bin: chBin } = readGlb(CHARGE_SRC); + const chWorld = computeWorldMatrices(chJson.nodes); + const allCharge = new Set(chJson.nodes.map((_, i) => i)); + const chargeColor = { + color: [0.55, 0.85, 1, 0.85], + emissive: [0.25, 0.55, 1] + }; + const { glb: chargeGlb, maxDim: cMax, scale: cScale } = extractCluster( + chJson, + chBin, + allCharge, + chWorld, + 'staff-charge', + CHARGE_DIAMETER_M, + chargeColor + ); + fs.writeFileSync(path.join(CHARGE_OUT, 'staff-charge.glb'), chargeGlb); + // Also keep a neutral alias for elemental tint at runtime + fs.writeFileSync(path.join(CHARGE_OUT, 'kamehameha-charge.glb'), chargeGlb); + console.log( + `wrote staff-charge.glb rawMax=${cMax.toFixed(3)} scale=${cScale.toFixed(4)} → ${CHARGE_DIAMETER_M}m` + ); + + const chargeManifest = { + source: 'kamehameha_charging.glb', + diameterM: CHARGE_DIAMETER_M, + path: 'models/vfx/charge/staff-charge.glb', + alias: 'models/vfx/charge/kamehameha-charge.glb', + note: 'Small charge shell at staff tip / hand during cast — tint with elemental shaders' + }; + fs.writeFileSync( + path.join(CHARGE_OUT, 'charge-manifest.json'), + JSON.stringify(chargeManifest, null, 2) + ); + console.log('done'); +} + +main(); diff --git a/src/abilities/Ability.js b/src/abilities/Ability.js index e0380a2..701f41f 100644 --- a/src/abilities/Ability.js +++ b/src/abilities/Ability.js @@ -3,6 +3,7 @@ import { settings } from '../config/settings.js'; import { saturate, smoothstep, Easing } from '../utils/math.js'; import { getColor } from '../utils/color.js'; import { LAYER } from '../core/Layers.js'; +import { playForImpact } from '../audio/skillSfx.js'; export const AbilityPhase = Object.freeze({ IDLE: 'idle', @@ -278,11 +279,18 @@ export class Ability { if (reachedEnd) { this.phase = AbilityPhase.IMPACT; this.impactTime = 0; + try { + playForImpact(this.element, { kind: this.element === 'fire' ? 'fire' : 'impact' }); + } catch (_) {} this.onImpact(); } // A path that outlives its element still has to end eventually. if (this.age > this.config.lifetime * settings.global.lifetime * 4) { this.phase = AbilityPhase.IMPACT; + this.impactTime = 0; + try { + playForImpact(this.element, { kind: this.element === 'fire' ? 'fire' : 'impact' }); + } catch (_) {} this.onImpact(); } break; diff --git a/src/abilities/AbilityManager.js b/src/abilities/AbilityManager.js index 76c243b..a39fde2 100644 --- a/src/abilities/AbilityManager.js +++ b/src/abilities/AbilityManager.js @@ -2,10 +2,14 @@ import { FireAbility } from './FireAbility.js'; import { WaterAbility } from './WaterAbility.js'; import { EarthAbility } from './EarthAbility.js'; import { WindAbility } from './WindAbility.js'; -import { ELEMENTS } from '../config/settings.js'; +import { ELEMENTS, abilityKeyForElement } from '../config/settings.js'; import { ObjectPool } from '../utils/ObjectPool.js'; +import { playForElementCast } from '../audio/skillSfx.js'; -/** Registry: adding an element means adding one line here. */ +/** + * Ability pool keys (class implementations). Product ELEMENTS map via abilityKeyForElement: + * fire→fire, ice→water, nature→earth, storm|holy|arcane→wind. + */ const ABILITY_TYPES = { fire: FireAbility, water: WaterAbility, @@ -18,7 +22,7 @@ const MAX_CONCURRENT = 8; /** * Spawns, updates and recycles abilities. * - * Instances are pooled per element: casting fire fifty times constructs at most + * Instances are pooled per ability key: casting fire fifty times constructs at most * a handful of FireAbility objects, and every one of them keeps its meshes and * materials for the lifetime of the app. Nothing is built during a cast. */ @@ -30,12 +34,13 @@ export class AbilityManager { constructor(context) { this.ctx = context; this.active = []; + /** @type {string} product element id (ELEMENTS) */ this.selected = ELEMENTS[0]; this.pools = new Map(); - for (const [element, Type] of Object.entries(ABILITY_TYPES)) { + for (const [key, Type] of Object.entries(ABILITY_TYPES)) { this.pools.set( - element, + key, new ObjectPool(() => { const ability = new Type(this.ctx); this.ctx.scene.add(ability.group); @@ -46,17 +51,23 @@ export class AbilityManager { } } + /** @param {string} element product element (fire|storm|ice|nature|holy|arcane) or legacy */ select(element) { - if (!ABILITY_TYPES[element]) return; - this.selected = element; + if (!element) return; + const key = abilityKeyForElement(element); + if (!ABILITY_TYPES[key]) return; + this.selected = ELEMENTS.includes(element) ? element : element; } /** * Cast the currently selected element along `curve`. + * @param {import('three').Curve} curve + * @param {string} [element] product or legacy element id * @returns {import('./Ability.js').Ability|null} */ cast(curve, element = this.selected) { - if (!ABILITY_TYPES[element]) return null; + const key = abilityKeyForElement(element); + if (!ABILITY_TYPES[key]) return null; // Retire the oldest cast rather than letting the scene grow without bound. if (this.active.length >= MAX_CONCURRENT) { @@ -65,9 +76,12 @@ export class AbilityManager { this.pools.get(oldest.element).release(oldest); } - const ability = this.pools.get(element).acquire(); + const ability = this.pools.get(key).acquire(); ability.spawn(curve); this.active.push(ability); + try { + playForElementCast(element || this.selected || key); + } catch (_) {} return ability; } diff --git a/src/abilities/README.md b/src/abilities/README.md new file mode 100644 index 0000000..2e7fc64 --- /dev/null +++ b/src/abilities/README.md @@ -0,0 +1,16 @@ +# Abilities (path-cast pools) + +Product language: these are the **path-cast engines under staff weapons**, not free “elements.” + +| Ability class | Pool key | Staff weapons that use it | +|---------------|----------|---------------------------| +| `FireAbility` | `fire` | Fire staff | +| `WaterAbility` | `water` | Ice staff | +| `EarthAbility` | `earth` | Nature staff | +| `WindAbility` | `wind` | Storm · Holy · Arcane staffs | + +**Staff product ids** (hotbar 1–6): `WEAPON_STAFF_IDS` in `src/config/settings.js` +**Catalog combat weapons** (sword, gun, wand…): `equipWeaponById` + `docs/T0_WEAPON_PLAY_STACK_SSOT.md` +**Agent skill:** `casting-t0-weapon-play` + +Do not add a parallel element skill tree. Extend catalog weapons + production compile, or map a new staff brand onto an existing pool. diff --git a/src/animation/CharacterController.js b/src/animation/CharacterController.js index c765b96..ce511c0 100644 --- a/src/animation/CharacterController.js +++ b/src/animation/CharacterController.js @@ -1,41 +1,95 @@ import { AnimationMixer, Box3, + ClampToEdgeWrapping, + Euler, Group, - LinearMipmapLinearFilter, + LoopOnce, LoopRepeat, MathUtils, - MeshStandardMaterial, - NearestFilter, - RepeatWrapping, + Quaternion, SRGBColorSpace, Vector3 } from 'three'; -import { settings } from '../config/settings.js'; +// Box3 used by getWeaponTip mesh-tip measure +import { applyWeaponHoldPose, normalizeHoldKind } from '../character/weaponHoldPose.js'; +import { WeaponSheathRuntime } from '../character/WeaponSheathRuntime.js'; +import { + buildWeaponMeshVolume, + getVolumeAxisWorld, + getVolumeTipWorld, + WEAPON_COLLIDER_PAD_M +} from '../character/weaponMeshCollider.js'; +import { + ANIM_PACKS, + DODGE_ROLE, + ROLL_ROLE, + DEFAULT_RACE, + FALLBACK_PRESETS, + GEAR_PRESETS_URL, + bakedClipUrlsForRole +} from '../config/assets.js'; +import { animPackForLoadout, activeWeaponSlot, packCombatBlurb } from '../config/weaponAnimPack.js'; +import { describeAnimLibrary, roleBlurb } from '../config/animLibrary.js'; +import { pistolTimeScale, FLINTLOCK_FIRE } from '../config/pistolAnimSsot.js'; +import { PistolReloadPose, findWeaponAttach, getMuzzleWorld, getBarrelForward } from './pistolReloadPose.js'; +import { getWeaponAttachFromHand } from '../character/WeaponMeshAttach.js'; +import { + atlasUrlForRace, + kitUrlForRace, + loadoutToMeshIds, + logSSOT, + isToonRtsKitUrl, + raceDef, + GRUDGE6_SSOT_VERSION +} from '../config/grudge6SSOT.js'; +import { EquipmentManager } from '../character/EquipmentManager.js'; +import { + deployToonPlayKit, + reGroundToonKit, + applyMeshIdsExclusive, + diagnoseCharacterLook, + countSkeletons +} from '../character/toonKitPlay.js'; +import { RideIK } from '../character/RideIK.js'; +import { BackSlotEquip } from '../character/BackSlotEquip.js'; import { LAYER } from '../core/Layers.js'; +import { settings } from '../config/settings.js'; import { disposeObject } from '../utils/dispose.js'; -import { SittingPose } from './SittingPose.js'; +import { loadBakedClipJson, rematchClipToSkeleton } from './bakeClip.js'; +import { + loadFbxClipRematched, + FLIP_FBX_URLS, + FALL_FBX_URLS, + MELEE_FBX_URLS +} from './fbxClip.js'; +import { + FALL_ROLES, + FALL_THRESHOLDS, + pickLandRole, + shouldEnterFall +} from '../config/fallAnimSsot.js'; -const CHARACTER_URL = './models/Standing Idle.fbx'; -/** Hand-authored albedo atlas replacing the FBX's unresolvable texture refs. */ -const CHARACTER_TEXTURE_URL = './angtexture.png'; -/** Mixamo exports in centimetres. */ -const FBX_SCALE = 0.01; -/** Rigs vary; normalise to a believable human height so the world scale holds. */ -const TARGET_HEIGHT = 1.78; +const _castOrigin = new Vector3(); +const _rideFwd = new Vector3(); +const _rideLeft = new Vector3(); +/** Local +X on tilt — backflip spin axis (pitch) */ +const _flipAxis = new Vector3(1, 0, 0); /** - * Loads the rigged FBX, normalises it for the scene and drives its animation. + * Toon RTS / grudge6 combat hero — ObjectStore loadRaceKit parity ONLY. + * + * RIGHT SOURCE: info.grudge-studio.com/js/grudge6-kit.js + race-scenes + * Toon GLB → clone → mesh_ids → bone SI fit → yaw 0 → one mixer * - * The character is intentionally stationary — it only ever idles. The class is - * still built around a small action registry with cross-fading so additional - * clips (a casting flourish, a reaction) can be dropped in without touching - * anything else. + * Purged (wrong paths that keep breaking Warlords heroes): + * - unifySkeletons + pose() on partial head skins → head at feet + * - setFromObject skinned mesh SI fit → explode / under-scale + * - facePlusZ π/2 on Toon play GLB + * - races bake / metaverse / FBX play fallback + * - SittingPose Mixamo / soft HandIK every frame * - * On top of the clip sits a second, procedural layer: `SittingPose` bakes a - * cross-legged meditation pose straight onto the skeleton, and `settings. - * character.pose` cross-fades the two. Because that layer runs *after* the - * mixer each frame it needs no clip of its own. + * RideIK only while WalkController ride is active. */ export class CharacterController { constructor(environment) { @@ -43,9 +97,6 @@ export class CharacterController { this.root = new Group(); this.root.name = 'Character'; - // Position and heading live on `root`; the bank (walk mode leans into its - // turns) lives on a joint underneath it, so the two never fight over the - // same rotation. this.tilt = new Group(); this.tilt.name = 'CharacterTilt'; this.root.add(this.tilt); @@ -55,233 +106,2021 @@ export class CharacterController { this.current = null; this.height = 1.8; this.headPosition = new Vector3(0, 1.5, 0); - /** The rig's own forward, in model space — the axis a bank rotates about. */ this.forwardAxis = new Vector3(0, 0, 1); + this.equipment = null; + /** @type {{ rHand?: object, lHand?: object, pelvis?: object }|null} */ + this.bones = null; + this.raceId = DEFAULT_RACE; + this.animPackId = 'magic'; + this.presetId = 'mage'; + this.presets = FALLBACK_PRESETS.slice(); + + /** 'idle' | 'walk' | 'run' | 'cast_loop' | 'attack' */ + this.animState = 'idle'; + this._attackTimer = 0; + this._oneShotTimer = 0; + this._castingExternal = false; + this._boundPacks = new Set(); + this._gait = 0; + this._gaitKey = '0:fwd'; + this._strafe = null; + this._gaitLocked = false; + /** Hold jump clip until land (first jump) */ + this._airJumpHold = false; + /** Melee combo index 0..2 after last light hit; -1 = idle */ + this._meleeComboStep = -1; + /** performance.now()/1000 deadline to continue combo */ + this._meleeComboUntil = 0; + this.sitting = null; - this._poseWeight = 0; // 0 = idle clip, 1 = seated - this._poseTime = 0; - this._poseBlend = null; // seconds, overrides settings for one transition + /** @type {import('../character/RideIK.js').RideIK|null} */ + this.rideIk = null; + /** @type {import('../character/BackSlotEquip.js').BackSlotEquip|null} */ + this.backSlot = null; + /** + * Hand ↔ hip/back sheath for traversal + mount. + * Hold-pose residual only while drawn. + * @type {WeaponSheathRuntime} + */ + this.weaponSheath = new WeaponSheathRuntime({ + getModel: () => this.model, + getBones: () => this.bones || this.equipment?.findBones?.() || {}, + getAttach: () => this.weaponAttach, + setAttach: (a) => { + this.weaponAttach = a; + } + }); + this._rideActive = false; + /** Last sprint flag from setGait (for sheath policy) */ + this._sprinting = false; + /** World heading for ride pole vectors (set by WalkController) */ + this._rideYaw = 0; + /** + * When true, root is parented under windsurf seat — do not write world XZ/Y + * to root.position (board vehicle owns transform until dismount). + */ + this._rideParented = false; + /** Cached world feet for position getter while parented */ + this._worldPos = new Vector3(); + this.ik = null; + + /** Procedural flip on tilt (backflip / frontflip deploy) */ + this._flipActive = false; + this._flipTime = 0; + /** −1 = backflip, +1 = frontflip (pitch about local right) */ + this._flipSign = -1; + this._flipDuration = 0.55; + /** True when playing a real clip (no tilt spin) */ + this._flipUseClip = false; + /** Look yaw to hold on camera during backflip setup */ + this._flipCameraHoldYaw = null; } /** * @param {import('../loaders/AssetLoader.js').AssetLoader} assets + * @param {{ raceId?: string, presetId?: string }} [opts] */ - async load(assets) { - const [fbx, albedo] = await Promise.all([ - assets.loadFBX(CHARACTER_URL), - assets.loadTexture(CHARACTER_TEXTURE_URL) - ]); - // The FBX resolves before its textures do; material prep inspects them. + async load(assets, opts = {}) { + this.assets = assets; + this.raceId = opts.raceId || DEFAULT_RACE; + this.presetId = opts.presetId || 'mage'; + logSSOT(); + + await this._loadPresets(); + + const race = raceDef(this.raceId); + // PLAY: Toon RTS GLB only — no races-bake / metaverse / FBX fallback + const kitUrl = kitUrlForRace(this.raceId); + const atlasUrl = atlasUrlForRace(this.raceId); + + let gltf = null; + try { + gltf = await assets.loadGLTF(kitUrl); + } catch (err) { + console.error('[CharacterController] Toon RTS kit failed (no fallback)', kitUrl, err); + throw err; + } + if (!gltf) throw new Error(`No Toon RTS race GLB: ${kitUrl}`); + if (!isToonRtsKitUrl(kitUrl)) { + throw new Error(`[CharacterController] refuse non-Toon play kit: ${kitUrl}`); + } + + // Atlas optional — Toon embeds usually enough; only for missing maps + const atlas = await assets.loadTexture(atlasUrl).catch((err) => { + console.warn('[CharacterController] atlas failed (ok if embeds)', err); + return null; + }); await assets.settled(); - // A small pixel-art atlas: keep the texels hard under magnification, but - // let mipmaps handle minification so the face doesn't shimmer at distance. - albedo.colorSpace = SRGBColorSpace; - albedo.magFilter = NearestFilter; - albedo.minFilter = LinearMipmapLinearFilter; - albedo.generateMipmaps = true; - albedo.anisotropy = 4; - // The rig's UVs run outside the unit square (u ∈ [-1, 1], v ∈ [1, 2]), so - // the default clamp would drag one edge row across the whole body. - albedo.wrapS = RepeatWrapping; - albedo.wrapT = RepeatWrapping; - albedo.needsUpdate = true; - this.albedo = albedo; - - fbx.scale.setScalar(FBX_SCALE); - fbx.updateMatrixWorld(true); - - const box = new Box3().setFromObject(fbx); - const size = new Vector3(); - const center = new Vector3(); - box.getSize(size); - - // Normalise the rig's height, then drop it onto y = 0 and centre it. - fbx.scale.setScalar(FBX_SCALE * (TARGET_HEIGHT / Math.max(0.001, size.y))); - fbx.updateMatrixWorld(true); - box.setFromObject(fbx); - box.getSize(size); - box.getCenter(center); - this.height = size.y; - fbx.position.x -= center.x; - fbx.position.z -= center.z; - fbx.position.y -= box.min.y; - - this._prepareMaterials(fbx); - - this.tilt.add(fbx); - this.model = fbx; - this.headPosition.set(0, size.y * 0.86, 0); - - // Bake the seated pose while the rig is still untouched by the mixer. - this.sitting = new SittingPose(fbx); - if (this.sitting.valid) this.forwardAxis.copy(this.sitting.forward); - - // The idle clip ships inside the same file. - this.mixer = new AnimationMixer(fbx); - const clips = fbx.animations ?? []; - if (clips.length === 0) { - console.warn('[CharacterController] no animation clips found in the FBX'); + if (atlas) { + atlas.colorSpace = SRGBColorSpace; + atlas.flipY = false; + atlas.wrapS = ClampToEdgeWrapping; + atlas.wrapT = ClampToEdgeWrapping; + atlas.anisotropy = 8; + atlas.needsUpdate = true; + this.atlas = atlas; + } + + // Clear previous kit + while (this.tilt.children.length) { + const c = this.tilt.children[0]; + this.tilt.remove(c); + disposeObject(c); + } + + // Preset → mesh_ids (no bag/wood/quiver in combat showcase) + const preset = this.presets.find((p) => p.id === this.presetId) || this.presets[0]; + this.animPackId = this._packFromPreset(preset); + const cleanLoadout = { ...(preset?.loadout || { body: 'A', arms: 'A', legs: 'A', head: 'A' }) }; + delete cleanLoadout.bag; + delete cleanLoadout.wood; + delete cleanLoadout.quiver; + delete cleanLoadout.carry; + delete cleanLoadout.showUtility; + const meshIds = loadoutToMeshIds(race.prefix, cleanLoadout); + + // ★ ObjectStore loadRaceKit parity — no unify/pose, bone SI fit, yaw 0 + const deployed = deployToonPlayKit(gltf.scene, { meshIds }); + const kit = deployed.root; + kit.name = `${race.short}_toon`; + kit.userData.importUrl = kitUrl; + kit.userData.playMesh = 'toon-rts'; + kit.userData.ssotVersion = GRUDGE6_SSOT_VERSION; + const report = deployed.equip; + + // Always re-apply exclusive loadout. preserveVisibility alone left multipack + // kits fully visible when mesh_id fuzzy match missed (all weapons/armor on). + this.equipment = new EquipmentManager(kit, { preserveVisibility: true }); + this.equipment.carryMode = false; + const equipReport = this.equipment.applyLoadout(cleanLoadout); + // Second pass by mesh_ids catches author naming that classifyMesh misses + if ((equipReport?.matched || 0) < 3) { + const byIds = this.equipment.applyMeshIds(meshIds); + console.info( + `[CharacterController] equip fallback mesh_ids matched=${byIds.matched} missing=${byIds.missing?.length || 0}`, + ); } else { - clips.forEach((clip, index) => { - const name = clip.name && clip.name !== 'mixamo.com' ? clip.name : index === 0 ? 'idle' : `clip_${index}`; - const action = this.mixer.clipAction(clip); - action.setLoop(LoopRepeat, Infinity); - action.clampWhenFinished = false; - this.actions.set(name, action); + console.info( + `[CharacterController] equip loadout matched=${equipReport.matched} missing=${(equipReport.missing || []).join(',')}`, + ); + } + this.equipment.hideUtility(); + // Fail-closed: never leave hero fully invisible after exclusive equip + { + let vis = 0; + kit.traverse((o) => { + if ((o.isMesh || o.isSkinnedMesh) && o.visible) vis += 1; }); - this.play([...this.actions.keys()][0], 0); + if (vis < 1) { + console.warn('[CharacterController] equip left 0 meshes visible — default A armor rescue'); + applyMeshIdsExclusive(kit, []); + this.equipment.hideUtility(); + } + } + + kit.traverse((o) => { + if (!o.isMesh && !o.isSkinnedMesh) return; + o.layers.set(LAYER.WORLD); + o.layers.enable(LAYER.CONTACT); + o.castShadow = true; + o.receiveShadow = true; + if (o.isSkinnedMesh) o.frustumCulled = false; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (const m of mats) if (m) this.environment?.registerShadowCaster?.(m); + }); + + // root (world feet + yaw) → tilt → model + this.tilt.add(kit); + this.model = kit; + this.root.position.set(0, 0, 0); + this.root.rotation.set(0, 0, 0); + this.tilt.position.set(0, 0, 0); + this.tilt.quaternion.identity(); + + this.height = deployed.height || kit.userData.deployHeightM || 1.8; + this.headPosition.set(0, this.height * 0.86, 0); + this.bones = this.equipment.findBones(); + + // Single AnimationMixer — Bip001 packs, position tracks stripped, bones-only rematch + this.mixer = new AnimationMixer(kit); + this.actions.clear(); + this._boundPacks.clear(); + + // Critical path: primary pack only (idle/walk/run/cast) so loader can finish. + // combat_mobility + longbow + reactions are large and 404-heavy — load async. + await this._bindPack(this.animPackId); + if (this.animPackId !== 'magic' && this.animPackId !== 'sword_shield') { + await this._bindPack('magic'); } + if (this.actions.has('idle')) this.play('idle', 0); + else if (this.actions.size) this.play([...this.actions.keys()][0], 0); + + this.mixer.update(1 / 30); + // Re-ground after idle sample + exclusive equip (visibility changes bbox) + reGroundToonKit(kit, 0); + this.root.position.y = 0; + this.height = kit.userData.deployHeightM || this.height; + // Face camera +Z (Toon play) — never leave sideways bind + kit.rotation.set(0, 0, 0); + + // Secondary packs — do not block boot / loader hide + const secondary = [ + this._bindPack('combat_mobility'), + this._bindPack('reactions'), + ]; + if (this.animPackId !== 'longbow') secondary.push(this._bindPack('longbow')); + void Promise.all(secondary).then(() => { + console.info('[CharacterController] secondary anim packs ready'); + }).catch((err) => { + console.warn('[CharacterController] secondary packs', err); + }); + + if (this.rideIk) this.rideIk.rebind(kit); + else this.rideIk = new RideIK(kit); + this._rideActive = false; + this.weaponSheath?.rebind?.(); + + // Back-slot utility (windsurf stow on spine) — same equip family as hands + const backId = settings.walk?.backSlot || 'windsurf'; + if (this.backSlot) this.backSlot.rebind(kit); + else this.backSlot = new BackSlotEquip(kit); + if (backId && backId !== 'none') { + this.backSlot.equip(backId).catch((err) => console.warn('[Character] back slot', err)); + } + + const look = this.diagnoseLook(); + const vis = this._countVisibleSkinned(); + const uprightOk = deployed.upright !== false; + console.info( + `[CharacterController] toon-rts★ ${this.raceId}/${this.presetId} ` + + `kit=${kitUrl.split('/').pop()} path=objectstore-parity ` + + `h=${this.height.toFixed(2)}m equip=${report.matched} vis=${vis} clips=${this.actions.size} ` + + `upright=${deployed.upright} headY=${deployed.headY?.toFixed(2)} footY=${deployed.footY?.toFixed(2)} ` + + (uprightOk && vis >= 3 ? 'OK' : `WARN ${JSON.stringify(look.errors || [])}`), + ); + return this; } - /** Convert imported materials to PBR and hook them into the shadow system. */ - _prepareMaterials(root) { - const converted = new Map(); - - root.traverse((node) => { - if (!node.isMesh && !node.isSkinnedMesh) return; - - node.castShadow = true; - node.receiveShadow = true; - node.frustumCulled = false; - node.layers.set(LAYER.WORLD); - node.layers.enable(LAYER.CONTACT); // captured by the contact shadow pass - - const source = Array.isArray(node.material) ? node.material : [node.material]; - const result = source.map((material) => { - if (!material) return material; - if (converted.has(material)) return converted.get(material); - - // FBX gives us Phong/Lambert; move to Standard so IBL and CSM apply. - // The FBX references its own textures by absolute local path, so they - // never resolve — the authored atlas is substituted wholesale, with a - // white base colour so the map's tones come through untinted. - const standard = new MeshStandardMaterial({ - name: material.name, - color: 0xffffff, - map: this.albedo, - normalMap: material.normalMap ?? null, - roughness: 0.85, - metalness: 0, - transparent: material.transparent ?? false, - opacity: material.opacity ?? 1, - side: material.side - }); - - this.environment.registerShadowCaster(standard); - material.dispose(); - converted.set(material, standard); - return standard; - }); + /** Visible skinned pieces (body/arms/legs/head should be ≥3–4). */ + _countVisibleSkinned() { + let n = 0; + this.model?.traverse((o) => { + if (o.isSkinnedMesh && o.visible) n++; + }); + return n; + } - node.material = Array.isArray(node.material) ? result : result[0]; + /** World AABB of visible body (for camera / debug). */ + getWorldBodyBox(out = new Box3()) { + if (!this.model) return out.makeEmpty(); + this.root.updateMatrixWorld(true); + out.makeEmpty(); + let any = false; + this.model.traverse((o) => { + if (!o.isSkinnedMesh || !o.visible) return; + if (/weapon|shield|staff|sword|bow|axe|hammer/i.test(o.name || '')) return; + try { + const b = new Box3().setFromObject(o, true); + if (b.isEmpty()) return; + if (!any) { + out.copy(b); + any = true; + } else out.union(b); + } catch { + /* */ + } }); + return out; + } + + /** Snap root feet to world XZ (physics / spawn). No-op while parented to board. */ + placeAt(x, y, z) { + if (this._rideParented || this._rideActive) return; + this.root.position.set(x, y ?? 0, z); + } + + diagnoseLook() { + if (!this.model) return { ok: false, reason: 'no-model' }; + const d = diagnoseCharacterLook(this.model, 0); + const bones = this.equipment?.findBones?.() || this.bones || {}; + // Don't throw — boot must complete even if bone helpers missing + let skelCount = 0; + try { + skelCount = countSkeletons(this.model); + } catch { + skelCount = -1; + } + const heightOk = (d.height ?? 0) >= 1.55 && (d.height ?? 0) <= 2.15; + const feetOk = Math.abs((d.feetMinY ?? 99) - 0) < 0.12; + return { + ok: heightOk && feetOk && !!(bones.pelvis || d.bip001?.count >= 18), + heightM: +(d.height ?? 0).toFixed(3), + feetMinY: +(d.feetMinY ?? 0).toFixed(3), + heightOk, + feetOk, + pelvis: !!bones.pelvis, + rHand: !!bones.rHand, + errors: d.errors || [], + equipMatched: this.equipment?.loadout || {}, + skeletons: skelCount, + bip001: d.bip001?.count ?? 0 + }; + } + + async _loadPresets() { + try { + const res = await fetch(GEAR_PRESETS_URL, { mode: 'cors' }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + const list = json.presets || json; + if (Array.isArray(list) && list.length) this.presets = list; + } catch (err) { + console.warn('[CharacterController] presets CDN fail; using fallbacks', err); + this.presets = FALLBACK_PRESETS.slice(); + } + } + + _packFromPreset(preset) { + const pack = preset?.pack || 'magic'; + if (pack === 'magic' || pack.startsWith('magic')) return 'magic'; + if (pack.includes('sword') || pack.includes('shield') || pack === '2h_melee') return 'sword_shield'; + if (pack.includes('pistol') || pack.includes('handgun') || pack === 'gun') return 'pistol'; + if (pack.includes('bow') || pack === 'longbow') return 'longbow'; + return ANIM_PACKS[pack] ? pack : 'magic'; + } + + /** + * Swap Toon RTS race kit (all 6 races) — full reload, same preset when possible. + * @param {string} raceId WK|ELF|BRB|ORC|UD|DWF + */ + async setRace(raceId) { + if (!this.assets) throw new Error('CharacterController.setRace: call load() first'); + const next = raceId || DEFAULT_RACE; + if (next === this.raceId && this.model) return this; + await this.load(this.assets, { raceId: next, presetId: this.presetId }); + return this; + } + + /** + * Bind / activate a weapon locomotion + skill anim pack. + * @param {string} packId magic | sword_shield | longbow | locomotion_8way + */ + async setAnimPack(packId) { + const id = ANIM_PACKS[packId] ? packId : 'magic'; + this.animPackId = id; + // Re-bind so primary role names (idle/walk/attack) match this pack + this._boundPacks.delete(id); + const pack = ANIM_PACKS[id]; + if (pack) { + for (const role of Object.keys(pack)) { + const prev = this.actions.get(role); + if (prev) { + try { + prev.stop(); + } catch { + /* ignore */ + } + this.actions.delete(role); + } + } + } + await this._bindPack(id); + if (this.actions.has('idle')) this.play('idle', 0.25); + else if (this.actions.has(`${id}:idle`)) this.play(`${id}:idle`, 0.25); + return id; + } + + /** Bound clip roles for anim library UI. */ + listAnimRoles() { + return [...this.actions.keys()].sort(); + } + + /** + * Animation library snapshot (packs, families, mobility, play API). + * Use for Showcase Anims tab + agent diagnostics — do not fork role names. + * @see config/animLibrary.js · docs/ANIM_LIBRARY_SSOT.md + */ + getAnimLibrary() { + return describeAnimLibrary(this); + } + + /** Human blurb for a bound role (toast / lab). */ + describeRole(roleName) { + return roleBlurb(roleName); + } + + /** Play a library clip by role name (one-shot for attack/block/jump). */ + playLibraryClip(role) { + if (!role || !this.actions.has(role)) return false; + const once = + /attack|block|parry|jump|cast|dodge|roll|slide|gunplay|spin|draw|reload|skill\d/i.test( + role + ); + // Combat flourishes need weapon in hand before clip starts + if (/attack|gunplay|spin|cast|draw|reload|block|parry|skill\d/i.test(role)) { + this.weaponSheath?.unsheath?.('combat-clip'); + } + this.play(role, once ? 0.12 : 0.25); + if (once) { + const act = this.actions.get(role); + // T0 pistol: fire / draw / reload timing from pistolAnimSsot + if (this.animPackId === 'pistol' && act) { + if (/reload/i.test(role)) act.timeScale = pistolTimeScale('reload'); + else if (/draw|cast|block/i.test(role)) act.timeScale = pistolTimeScale('draw'); + else if (/attack|gunplay|spin/i.test(role)) act.timeScale = pistolTimeScale('fire'); + else if (/charged|skill2/i.test(role)) act.timeScale = pistolTimeScale('charged'); + else if (/whip|skill3/i.test(role)) act.timeScale = pistolTimeScale('whip'); + } else if (act && act.timeScale !== 1) { + act.timeScale = 1; + } + const dur = (act?.getClip?.()?.duration ?? 0.6) / (act?.timeScale || 1); + this._oneShotTimer = Math.max(this._oneShotTimer, dur * 0.95); + this._gaitLocked = true; + this.animState = /attack|gunplay|spin/i.test(role) + ? 'attack' + : /reload/i.test(role) + ? 'reload' + : /roll/i.test(role) + ? 'roll' + : /slide/i.test(role) + ? 'slide' + : /dodge/i.test(role) + ? 'dodge' + : this.animState; + } + return true; + } + + /** + * Procedural flintlock powder reload: gun to body middle · L-hand to barrel · tilt up. + * @param {{ power?: boolean, durationSec?: number }} [opts] + */ + playPistolReload(opts = {}) { + if (!this._pistolReload) this._pistolReload = new PistolReloadPose(); + return this._pistolReload.start(this, opts); + } + + /** + * Charged Shot wind-up — blend into charged-pistol / skill2, slow timeScale. + * @param {string} [role] + */ + beginWeaponChargeAnim(role = 'skill2') { + const name = + (this.actions.has(role) && role) || + (this.actions.has('skill2') && 'skill2') || + (this.actions.has('cast') && 'cast') || + (this.actions.has('attack') && 'attack'); + if (!name) return false; + this._weaponChargeAnim = name; + this._gaitLocked = true; + this.animState = 'charge'; + // Longer blend into charge pose (production weapon UX) + const blend = settings.character?.chargeBlend ?? 0.22; + this.play(name, blend); + const act = this.actions.get(name); + if (act) { + try { + act.setLoop(LoopOnce, 1); + act.clampWhenFinished = true; + // Slow wind — charged-pistol ~2.5s feels like draw power + act.timeScale = + this.animPackId === 'pistol' + ? Math.min(0.75, pistolTimeScale('charged') * 0.55) + : 0.65; + act.time = 0; + } catch { + /* */ + } + } + this._oneShotTimer = 99; // hold until endWeaponChargeAnim + return true; + } + + /** + * Advance charge pose (scrub into clip by progress). + * @param {number} progress01 + * @param {number} holdSec + */ + tickWeaponChargeAnim(progress01, holdSec = 0) { + const name = this._weaponChargeAnim; + if (!name) return; + const act = this.actions.get(name); + if (!act) return; + const clip = act.getClip?.(); + const dur = clip?.duration ?? 1; + // Scrub first ~55% of charged clip while holding (windup rest pose) + const t = Math.min(0.55, Math.max(0, progress01) * 0.55) * dur; + try { + act.time = t; + act.paused = progress01 > 0.92; // hold full pose near max charge + if (!act.paused && !act.isRunning()) act.play(); + } catch { + /* */ + } + this._oneShotTimer = 99; + } + + /** + * End charge windup — fire snap or cancel restore gait blend. + * @param {boolean} [fireCharged] + */ + endWeaponChargeAnim(fireCharged = false) { + const name = this._weaponChargeAnim; + this._weaponChargeAnim = null; + const act = name ? this.actions.get(name) : null; + if (act) { + try { + act.paused = false; + if (fireCharged) { + // Snap remaining clip at fire scale + act.timeScale = + this.animPackId === 'pistol' ? pistolTimeScale('charged') : 1.1; + act.time = Math.max(act.time, (act.getClip?.()?.duration ?? 1) * 0.45); + act.setLoop(LoopOnce, 1); + act.clampWhenFinished = true; + if (!act.isRunning()) act.play(); + this._oneShotTimer = Math.max( + 0.35, + ((act.getClip?.()?.duration ?? 0.6) - act.time) / Math.max(0.1, act.timeScale) + ); + this.animState = 'attack'; + return true; + } + // Cancel — fade out charge, restore gait + act.fadeOut(settings.character?.gaitBlend ?? 0.28); + } catch { + /* */ + } + } + this._oneShotTimer = 0.08; + this._gaitLocked = false; + this.animState = 'idle'; + const g = this._gait; + this._gait = -1; + this.setGait?.(g, g >= 2); + return true; + } + + /** Refresh weaponAttach pointer from R_hand (after equip). */ + syncWeaponAttach() { + const hand = this.bones?.rHand; + this.weaponAttach = + getWeaponAttachFromHand(hand) || findWeaponAttach(this) || this.weaponAttach || null; + // Fresh equip → always drawn (combat ready); re-evaluate next frame + this.weaponSheath?.rebind?.(); + this.weaponSheath?.unsheath?.('equip'); + return this.weaponAttach; + } + + /** True when weapon is on hand socket (not hip/back stow). */ + get isWeaponDrawn() { + return this.weaponSheath ? this.weaponSheath.isDrawn : true; + } + + /** + * Manual sheath lock (UI). null = follow traversal/mount policy. + * @param {boolean|null} sheath + */ + setWeaponSheathed(sheath) { + this.weaponSheath?.setManual?.(sheath, 'ui'); + } + + /** + * Directional dodge one-shot — longbow standing dodge L/R/F/B preferred. + * @param {'left'|'right'|'forward'|'back'} dir + * @returns {boolean} + */ + playDodge(dir) { + const role = DODGE_ROLE[dir] || 'dodgeB'; + const candidates = [role, `longbow:${role}`, `combat_mobility:${role}`]; + for (const name of candidates) { + if (this.actions.has(name)) { + this.play(name, 0.08, { exclusive: true }); + const act = this.actions.get(name); + const dur = act?.getClip?.()?.duration ?? settings.drc?.dodgeDuration ?? 0.42; + this._oneShotTimer = Math.max(this._oneShotTimer, dur * 0.92); + this._gaitLocked = true; + this.animState = 'dodge'; + return true; + } + } + // Fallback: jump clip as mobility tell + if (this.actions.has('jump')) { + this.play('jump', 0.08, { exclusive: true }); + this._oneShotTimer = 0.35; + this._gaitLocked = true; + this.animState = 'dodge'; + return true; + } + return false; + } + + /** + * Directional roll one-shot — Ghost Rider roll_* preferred (Ctrl+A/D). + * Exclusive mixer weight so retargeted rolls do not dual-bind deform. + * @param {'left'|'right'|'forward'|'back'} dir + * @returns {{ ok: boolean, duration: number, role: string|null }} + */ + playRoll(dir) { + const role = ROLL_ROLE[dir] || 'rollB'; + const candidates = [role, `combat_mobility:${role}`, DODGE_ROLE[dir], `longbow:${DODGE_ROLE[dir]}`]; + for (const name of candidates) { + if (!name || !this.actions.has(name)) continue; + // Exclusive: fade every other action (idle/walk residual = rubber limbs) + this.play(name, 0.06, { exclusive: true }); + const act = this.actions.get(name); + const clipDur = act?.getClip?.()?.duration ?? settings.drc?.rollDuration ?? 0.55; + // Prefer full clip; impulse syncs to this in DRC + const dur = Math.max(0.4, clipDur * 0.98); + this._oneShotTimer = Math.max(this._oneShotTimer, dur); + this._gaitLocked = true; + this.animState = 'roll'; + this._airJumpHold = false; + return { ok: true, duration: dur, role: name }; + } + const dodged = this.playDodge(dir); + return { ok: !!dodged, duration: settings.drc?.dodgeDuration ?? 0.42, role: dodged ? 'dodge' : null }; + } + + /** Last exclusive roll clip duration (for impulse sync). */ + getRollClipDuration(dir) { + const role = ROLL_ROLE[dir] || 'rollB'; + for (const name of [role, `combat_mobility:${role}`]) { + const act = this.actions.get(name); + const d = act?.getClip?.()?.duration; + if (d > 0) return d; + } + return settings.drc?.rollDuration ?? 0.55; + } + + /** + * Sprint slide one-shot — prod running-slide (Shift+Ctrl while sprint). + * @returns {boolean} + */ + playSlide() { + for (const name of ['slide', 'combat_mobility:slide']) { + if (!this.actions.has(name)) continue; + this.play(name, 0.06); + const act = this.actions.get(name); + const dur = act?.getClip?.()?.duration ?? settings.drc?.slideDuration ?? 0.72; + this._oneShotTimer = Math.max(this._oneShotTimer, dur * 0.92); + this._gaitLocked = true; + this.animState = 'slide'; + return true; + } + // Fallback: forward roll if slide bake missing + return this.playRoll('forward'); + } + + /** Parry / block one-shot (sword_shield block clip as longbow has no parry bake). */ + playParry() { + for (const name of ['parry', 'block', 'sword_shield:block', 'combat_mobility:parry']) { + if (this.actions.has(name)) { + this.play(name, 0.08); + const act = this.actions.get(name); + const dur = act?.getClip?.()?.duration ?? 0.45; + this._oneShotTimer = Math.max(this._oneShotTimer, dur * 0.9); + this._gaitLocked = true; + this.animState = 'parry'; + return true; + } + } + return false; + } + + /** Summary for lab Character tab. */ + getLabSummary() { + const race = raceDef(this.raceId); + const loadout = this.equipment?.loadout || {}; + const weaponSlot = ['sword', 'axe', 'hammer', 'spear', 'staff', 'bow'].find( + (s) => loadout[s] && loadout[s] !== 'none' + ); + return { + raceId: this.raceId, + raceLabel: race?.label || this.raceId, + raceShort: race?.short || '', + kitUrl: kitUrlForRace(this.raceId), + atlasUrl: atlasUrlForRace(this.raceId), + presetId: this.presetId, + animPackId: this.animPackId, + heightM: this.height, + weaponSlot: weaponSlot || null, + weaponVariant: weaponSlot ? loadout[weaponSlot] : null, + loadout: { ...loadout }, + clips: this.listAnimRoles(), + rideActive: !!this._rideActive + }; + } + + async _bindPack(packId) { + if (this._boundPacks.has(packId)) return; + const pack = ANIM_PACKS[packId]; + if (!pack) return; + + const roleMap = { + idle: LoopRepeat, + cast: LoopRepeat, + attack: LoopOnce, + attack1: LoopOnce, + attack2: LoopOnce, + attack3: LoopOnce, + finisher: LoopOnce, + finisherAir: LoopOnce, + hitReact: LoopOnce, + knockedUp: LoopOnce, + block: LoopOnce, + parry: LoopOnce, + walk: LoopRepeat, + run: LoopRepeat, + walkL: LoopRepeat, + walkR: LoopRepeat, + runL: LoopRepeat, + runR: LoopRepeat, + jump: LoopOnce, + fallLoop: LoopRepeat, + fall: LoopRepeat, + fallIdle: LoopRepeat, + fallLand: LoopOnce, + fallRoll: LoopOnce, + dodgeL: LoopOnce, + dodgeR: LoopOnce, + dodgeF: LoopOnce, + dodgeB: LoopOnce, + rollL: LoopOnce, + rollR: LoopOnce, + rollF: LoopOnce, + rollB: LoopOnce, + slide: LoopOnce, + frontflip: LoopOnce, + backflip: LoopOnce + }; + + for (const [role, rel] of Object.entries(pack)) { + const name = + this.actions.has(role) && packId !== this.animPackId ? `${packId}:${role}` : role; + const urls = bakedClipUrlsForRole(rel); + let loaded = false; + /** @type {{ url: string, matched: import('three').AnimationClip }|null} */ + let fallbackNoHands = null; + + // 2H melee idle — author FBX first (sword_shield pack stance) + if ( + !loaded && + role === 'idle' && + packId === 'sword_shield' && + this.model && + MELEE_FBX_URLS?.idle2h + ) { + for (const fbxUrl of MELEE_FBX_URLS.idle2h) { + try { + const clip = await loadFbxClipRematched(fbxUrl, this.model, name); + if (clip?.tracks?.length) { + this._registerClip(name, clip, LoopRepeat); + console.info( + `[CharacterController] clip ${name} ← 2H MELEE FBX ${fbxUrl} tracks=${clip.tracks.length}` + ); + loaded = true; + break; + } + } catch { + /* next url */ + } + } + } + + for (const url of urls) { + if (loaded) break; + try { + const raw = await loadBakedClipJson(url); + raw.name = name; + const matched = rematchClipToSkeleton(this.model, raw, { stripPositions: true }); + if (!matched.tracks.length) { + console.warn(`[CharacterController] empty tracks: ${url}`); + continue; + } + // Prefer clips that drive hands for idle (prod standing-idle has hands; + // open magic/standing idle does not → broken bind-pose hands on Toon). + if (role === 'idle') { + const hasHand = matched.tracks.some((t) => /Hand\.quaternion$/i.test(t.name)); + if (!hasHand) { + if (!fallbackNoHands) fallbackNoHands = { url, matched }; + console.warn( + `[CharacterController] idle without Hand tracks (try next): ${url}` + ); + continue; + } + } + this._registerClip(name, matched, roleMap[role] ?? LoopRepeat); + console.info(`[CharacterController] clip ${name} ← ${url} tracks=${matched.tracks.length}`); + loaded = true; + break; + } catch { + /* try next URL */ + } + } + if (!loaded && fallbackNoHands) { + this._registerClip(name, fallbackNoHands.matched, roleMap[role] ?? LoopRepeat); + console.warn( + `[CharacterController] idle fallback (no hands) ← ${fallbackNoHands.url}` + ); + loaded = true; + } + if (!loaded) { + // Real flip FBX from Open when baked JSON missing (striker / extra packs) + if ((role === 'frontflip' || role === 'backflip') && this.model) { + const fbxList = FLIP_FBX_URLS[role] || []; + for (const fbxUrl of fbxList) { + try { + const clip = await loadFbxClipRematched(fbxUrl, this.model, role); + if (clip?.tracks?.length) { + this._registerClip(name, clip, LoopOnce); + console.info( + `[CharacterController] clip ${name} ← FBX ${fbxUrl} tracks=${clip.tracks.length}` + ); + loaded = true; + break; + } + } catch { + /* next */ + } + } + } + // Fall locomotion FBX (author D:\Games\Models → public/anim/locomotion/fall) + const fallRoles = ['fallLoop', 'fall', 'fallLand', 'fallRoll', 'fallIdle']; + if (!loaded && fallRoles.includes(role) && this.model) { + const fbxList = FALL_FBX_URLS[role] || []; + const loop = + role === 'fallLoop' || role === 'fall' || role === 'fallIdle' + ? LoopRepeat + : LoopOnce; + for (const fbxUrl of fbxList) { + try { + const clip = await loadFbxClipRematched(fbxUrl, this.model, role); + if (clip?.tracks?.length) { + this._registerClip(name, clip, loop); + console.info( + `[CharacterController] clip ${name} ← FALL FBX ${fbxUrl} tracks=${clip.tracks.length}` + ); + loaded = true; + break; + } + } catch { + /* next */ + } + } + } + } + if (!loaded) { + console.warn(`[CharacterController] clip fail role=${role}`, rel); + } + } + + this._boundPacks.add(packId); + } + + _registerClip(name, clip, loopMode) { + if (!this.mixer) return; + const action = this.mixer.clipAction(clip); + action.setLoop(loopMode, loopMode === LoopOnce ? 1 : Infinity); + action.clampWhenFinished = loopMode === LoopOnce; + this.actions.set(name, action); + } + + applyLoadout(loadout, meta = {}) { + if (!this.model) return { matched: 0, missing: ['no-model'] }; + if (meta.presetId) this.presetId = meta.presetId; + const race = raceDef(this.raceId); + const clean = { ...loadout }; + delete clean.bag; + delete clean.wood; + delete clean.quiver; + delete clean.carry; + delete clean.showUtility; + const meshIds = loadoutToMeshIds(race.prefix, clean); + // Prefer EquipmentManager exclusive apply (slot-safe) then mesh_ids safety net + let report = this.equipment?.applyLoadout?.(clean); + if (!report || (report.matched || 0) < 2) { + report = this.equipment?.applyMeshIds?.(meshIds) || applyMeshIdsExclusive(this.model, meshIds); + } + this.equipment?.hideUtility?.(); + reGroundToonKit(this.model, 0); + this.height = this.model.userData.deployHeightM || this.height; + this.headPosition.set(0, this.height * 0.86, 0); + this.bones = this.equipment?.findBones?.() || this.bones; + + // Weapon mesh → anim pack (staff→magic, bow→longbow, melee→sword_shield) + const wantPack = animPackForLoadout(clean, meta.pack || this.animPackId); + if (wantPack !== this.animPackId || meta.forcePack) { + // Fire-and-forget bind; callers can await syncAnimPackFromLoadout + this._pendingPack = wantPack; + this.syncAnimPackFromLoadout({ packHint: meta.pack }).catch(() => {}); + } else if (this.actions.has('idle') && this.animState === 'idle') { + this.play('idle', 0.2); + } + return report; + } + + /** + * Bind anim pack from current equipment loadout (weapon slot SSOT). + * @param {{ packHint?: string }} [opts] + */ + async syncAnimPackFromLoadout(opts = {}) { + const loadout = this.equipment?.loadout || {}; + const pack = animPackForLoadout(loadout, opts.packHint || this.animPackId); + const slot = activeWeaponSlot(loadout); + await this.setAnimPack(pack); + await this._bindPack('combat_mobility'); + await this._bindPack('reactions'); + console.info( + `[CharacterController] weapon pack ${pack} slot=${slot || 'none'} · ${packCombatBlurb(pack)}` + ); + return pack; + } + + applyPreset(presetId) { + const preset = this.presets.find((p) => p.id === presetId); + if (!preset) return null; + this.presetId = presetId; + this.animPackId = this._packFromPreset(preset); + return this.applyLoadout(preset.loadout, { pack: preset.pack, presetId, forcePack: true }); + } + + /** + * Combat one-shot for equipped weapon pack (attack vs cast). + * @param {'attack'|'cast'|'block'|'finisher'|'finisherAir'} intent + */ + playWeaponCombat(intent = 'attack') { + const pack = this.animPackId || 'magic'; + if (intent === 'block') return this.playParry() || this.requestOneShot('block'); + if (pack === 'magic' || intent === 'cast') { + return this.requestOneShot('cast') || this.requestOneShot('attack'); + } + if (intent === 'finisherAir') { + return this.playMeleeFinisher({ airborne: true }); + } + if (intent === 'finisher') { + return this.playMeleeFinisher({ airborne: false }); + } + // Default light: advance 3-hit combo (not the jump-dash finisher clip) + if (pack === 'sword_shield' || intent === 'attack') { + return this.playMeleeComboLight() || this.requestOneShot('attack'); + } + return this.requestOneShot('attack') || this.requestOneShot('cast'); + } + + /** + * Grounded light melee — cycles attack1 → attack2 → attack3 within chain window. + * Does NOT play the jump-dash finisher (that is playMeleeFinisher). + * @returns {{ ok: boolean, step: number, role: string|null, kind: 'light'|'none' }} + */ + playMeleeComboLight() { + const cfg = settings.meleeCombo || {}; + const maxHits = Math.max(1, Math.min(3, cfg.hits ?? 3)); + const windowS = cfg.chainWindow ?? 0.85; + const now = performance.now() / 1000; + let step = 0; + if (this._meleeComboStep >= 0 && now <= this._meleeComboUntil) { + step = Math.min(this._meleeComboStep + 1, maxHits - 1); + } else { + step = 0; + } + const roles = [`attack${step + 1}`, 'attack1', 'attack2', 'attack3']; + // Prefer exact step role, then any light role, never jump-dash `attack` first + let played = null; + for (const role of roles) { + if (this.requestOneShot(role)) { + played = role; + break; + } + } + if (!played) { + // Last resort: CDN slash if bound under attack without combo bakes + if (this.requestOneShot('attack')) { + played = 'attack'; + } + } + if (!played) return { ok: false, step, role: null, kind: 'none' }; + this._meleeComboStep = step; + this._meleeComboUntil = now + windowS; + this.animState = 'attack'; + return { ok: true, step, role: played, kind: 'light' }; + } + + /** + * Finisher: current sword_shield “attack” (jump/dash) or air drop. + * Resets light combo chain. + * @param {{ airborne?: boolean }} [opts] + * @returns {{ ok: boolean, step: number, role: string|null, kind: 'finisher'|'finisherAir'|'none' }} + */ + playMeleeFinisher(opts = {}) { + const air = !!opts.airborne; + this._meleeComboStep = -1; + this._meleeComboUntil = 0; + const prefer = air + ? ['finisherAir', 'attack', 'finisher', 'attack3'] + : ['finisher', 'attack', 'finisherAir', 'attack3']; + for (const role of prefer) { + if (this.requestOneShot(role)) { + this.animState = 'attack'; + return { ok: true, step: -1, role, kind: air ? 'finisherAir' : 'finisher' }; + } + } + return { ok: false, step: -1, role: null, kind: 'none' }; + } + + /** + * Resolve next melee one-shot from combat context. + * @param {{ airborne?: boolean, largeMmTowardTarget?: boolean, forceFinisher?: boolean }} ctx + */ + playMeleeAttack(ctx = {}) { + const pack = this.animPackId || 'magic'; + if (pack === 'magic') { + const ok = this.requestOneShot('cast') || this.requestOneShot('attack'); + return { ok, step: -1, role: ok ? 'cast' : null, kind: ok ? 'cast' : 'none' }; + } + if (pack !== 'sword_shield' && pack !== 'longbow') { + const ok = this.requestOneShot('attack'); + return { ok, step: -1, role: ok ? 'attack' : null, kind: ok ? 'light' : 'none' }; + } + if (pack === 'longbow') { + const ok = this.requestOneShot('attack'); + return { ok, step: -1, role: ok ? 'attack' : null, kind: ok ? 'ranged' : 'none' }; + } + // sword_shield: air or large MM → finisher; else light combo + if (ctx.forceFinisher || ctx.airborne || ctx.largeMmTowardTarget) { + return this.playMeleeFinisher({ airborne: !!ctx.airborne }); + } + return this.playMeleeComboLight(); } - /** Cross-fade to a named action. */ - play(name, fadeDuration = 0.35) { + /** + * Play clip with improved locomotion/combat blending (Warlords production feel). + * Gait uses longer fades; attacks/casts shorter. + * @param {string} name + * @param {number} [fadeDuration] + * @param {{ exclusive?: boolean }} [opts] exclusive = fade out every other action (rolls) + */ + play(name, fadeDuration = 0.35, opts = {}) { const next = this.actions.get(name); - if (!next || next === this.current) return; + if (!next) return; + if (next === this.current && next.isRunning() && !opts.exclusive) return; + + // Smoother gait blends; snappier combat one-shots + const isGait = + /^(idle|walk|run|walkL|walkR|runL|runR)$/i.test(name) || + /:idle$|:walk$|:run$/i.test(name); + const isCombat = + /attack|finisher|cast|dodge|roll|slide|jump|parry|block|skill|gunplay|charg|reload|whip/i.test( + name + ); + let fade = fadeDuration; + if (isGait) fade = Math.max(fade, settings.character?.gaitBlend ?? 0.28); + else if (/charg|skill2/i.test(name)) + fade = Math.max(fade, settings.character?.chargeBlend ?? 0.2); + else if (isCombat) fade = Math.min(fade, settings.character?.combatBlend ?? 0.12); + if (opts.exclusive) fade = Math.min(fade, 0.08); + + // Rolls/dodges: kill residual weights so limbs don't dual-bind (deform) + if (opts.exclusive) { + for (const [, act] of this.actions) { + if (act === next) continue; + try { + if (act.isRunning() || act.getEffectiveWeight() > 0.02) { + act.fadeOut(fade); + } + } catch { + /* */ + } + } + } next.reset(); next.enabled = true; next.setEffectiveTimeScale(1); next.setEffectiveWeight(1); + try { + if (opts.exclusive || isCombat) { + next.setLoop(LoopOnce, 1); + next.clampWhenFinished = true; + } + } catch { + /* */ + } - if (this.current && fadeDuration > 0) { - next.crossFadeFrom(this.current, fadeDuration, true); + if (this.current && this.current !== next && fade > 0 && !opts.exclusive) { + next.crossFadeFrom(this.current, fade, true); + } else if (opts.exclusive) { + next.fadeIn(fade); + } else if (this.current && this.current !== next && fade > 0) { + next.crossFadeFrom(this.current, fade, true); } next.play(); this.current = next; } - /* ------------------------------------------------------------------ */ - /* pose layer */ - /* ------------------------------------------------------------------ */ + /** + * Channel cast pose — loops cast clip while true; restores gait when false. + * @param {boolean} isCasting + * @param {{ aimX?: number, aimY?: number, aimZ?: number }} [aim] + */ + setCasting(isCasting, aim = null) { + const next = !!isCasting; + if (aim && Number.isFinite(aim.aimX)) { + this._castAim = { x: aim.aimX, y: aim.aimY ?? 1.2, z: aim.aimZ ?? 0 }; + } + if (next === this._castingExternal) return; + this._castingExternal = next; + if (next) { + // Cast needs drawn weapon in hand + this.weaponSheath?.unsheath?.('cast'); + // Hold gait lock + loop cast for channel window + this._gaitLocked = true; + this._oneShotTimer = 0; + if (this.actions.has('cast')) { + const act = this.actions.get('cast'); + try { + act.setLoop(LoopRepeat, Infinity); + act.clampWhenFinished = false; + } catch { + /* three version variance */ + } + this.animState = 'cast_loop'; + this.play('cast', 0.12); + } else if (this.actions.has('attack')) { + this.animState = 'cast_loop'; + this.play('attack', 0.12); + } + } else { + this._gaitLocked = false; + this._castAim = null; + // Restore one-shot loop mode on cast clip if needed + if (this.actions.has('cast')) { + try { + const act = this.actions.get('cast'); + act.setLoop(LoopOnce, 1); + act.clampWhenFinished = true; + } catch { + /* */ + } + } + if (this.animState === 'cast_loop') { + this.animState = 'idle'; + if (this.actions.has('idle')) this.play('idle', 0.2); + } + } + } /** - * @param {'idle'|'sitting'} pose - * @param {number|null} [blend] seconds for *this* transition only; the - * configured blend time is used again as soon as the pose is set without one. + * @param {0|1|2|number} level 0 idle, 1 walk, 2 run + * @param {boolean} [sprinting] + * @param {{ strafe?: 'left'|'right'|null }} [opts] focus-mode side gait */ - setPose(pose, blend = null) { - this._poseBlend = blend; - settings.character.pose = pose === 'sitting' ? 'sitting' : 'idle'; - return settings.character.pose; + setGait(level, sprinting = false, opts = {}) { + if (this._gaitLocked) return; + if (this._castingExternal && level === 0) return; + const g = sprinting ? 2 : MathUtils.clamp(level | 0, 0, 2); + const strafe = opts.strafe === 'left' || opts.strafe === 'right' ? opts.strafe : null; + const key = `${g}:${strafe || 'fwd'}`; + if (key === this._gaitKey && this.animState !== 'attack') return; + this._gait = g; + this._gaitKey = key; + this._strafe = strafe; + this._sprinting = !!sprinting || g >= 2; + + if (g === 0) { + if (this.actions.has('idle') && this.animState !== 'cast_loop') { + this.animState = 'idle'; + this.play('idle', 0.2); + } + return; + } + + // Focus strafe: prefer walkL/R or runL/R; fall back to forward walk/run + if (strafe) { + const side = strafe === 'left' ? 'L' : 'R'; + const prefer = + g >= 2 + ? [`run${side}`, `walk${side}`, 'run', 'walk'] + : [`walk${side}`, `run${side}`, 'walk', 'run']; + for (const role of prefer) { + if (this.actions.has(role)) { + this.animState = role.startsWith('run') ? 'run' : 'walk'; + this.play(role, 0.14); + return; + } + } + } + + if (g === 1) { + const walk = this.actions.has('walk') ? 'walk' : this.actions.has('run') ? 'run' : null; + if (walk) { + this.animState = 'walk'; + this.play(walk, 0.18); + } + } else { + const run = this.actions.has('run') ? 'run' : this.actions.has('walk') ? 'walk' : null; + if (run) { + this.animState = 'run'; + this.play(run, 0.15); + } + } } - /** Flip between the idle clip and the meditation sit. @returns {string} the new pose */ - togglePose() { - return this.setPose(settings.character.pose === 'sitting' ? 'idle' : 'sitting'); + requestOneShot(role) { + let name = null; + const tryNames = []; + if (role === 'attack') { + tryNames.push('attack', 'sword_shield:attack', 'finisher', 'cast'); + } else if (role === 'cast') { + tryNames.push('cast', 'attack'); + } else if (role === 'block') { + tryNames.push('block'); + } else if (role === 'finisher') { + tryNames.push('finisher', 'attack', 'sword_shield:attack'); + } else if (role === 'finisherAir') { + tryNames.push('finisherAir', 'attack', 'finisher'); + } else if (/^attack[123]$/.test(role)) { + tryNames.push(role, `sword_shield:${role}`, 'attack1', 'attack2', 'attack3'); + } else if (role === 'hitReact' || role === 'knockedUp') { + tryNames.push('hitReact', 'knockedUp', 'reactions:hitReact', 'reactions:knockedUp'); + } else { + tryNames.push(role); + } + for (const n of tryNames) { + if (this.actions.has(n)) { + name = n; + break; + } + } + if (!name) return false; + + this._gaitLocked = true; + this.animState = + role === 'cast' + ? 'cast_loop' + : role === 'reload' || /reload/i.test(name) + ? 'reload' + : 'attack'; + this.play(name, 0.1); + const act = this.actions.get(name); + // Pistol fire / draw / reload cadence (TPS-aligned timeScale) + if (this.animPackId === 'pistol' && act) { + if (/reload/i.test(role + name)) act.timeScale = pistolTimeScale('reload'); + else if (/draw|block|cast/i.test(role + name)) act.timeScale = pistolTimeScale('draw'); + else if (/whip|skill3/i.test(role + name)) act.timeScale = pistolTimeScale('whip'); + else if (/charged|skill2/i.test(role + name)) act.timeScale = pistolTimeScale('charged'); + else act.timeScale = pistolTimeScale('fire'); + } else if (act && act.timeScale !== 1) { + act.timeScale = 1; + } + const duration = + (act?.getClip?.()?.duration ?? 0.8) / Math.max(0.05, act?.timeScale || 1); + // Light combo steps: don't lock gait for full clip (chain window needs free click) + const isLight = /^attack[123]$/.test(role) || /^attack[123]$/.test(name); + const lockDur = isLight ? Math.min(duration, 0.55) + 0.02 : duration + 0.04; + this._oneShotTimer = lockDur; + this._attackTimer = this._oneShotTimer; + return true; } - get isSitting() { - return settings.character.pose === 'sitting'; + playWeaponAttack() { + // Light combo for sword_shield; finisher only via playMeleeAttack context + if (this.animPackId === 'sword_shield') { + const r = this.playMeleeComboLight(); + return !!r.ok; + } + return this.requestOneShot('attack'); } - /** How far the seated pose has actually blended in, 0..1. */ - get poseWeight() { - return this._poseWeight; + /** + * Physical hit reaction — knocked-up bake (reactions pack). + * @returns {boolean} + */ + playHitReaction() { + if (this.requestOneShot('hitReact') || this.requestOneShot('knockedUp')) { + this.animState = 'attack'; + return true; + } + // Soft fallback: brief gait lock + this._gaitLocked = true; + this._oneShotTimer = Math.max(this._oneShotTimer, 0.45); + return false; + } + + playCastFlourish() { + this.requestOneShot('cast'); + } + + /** + * Jump one-shot (blend from gait). With holdAir, pose stays until land. + * @param {number} [fade=0.08] + * @param {{ holdAir?: boolean }} [opts] + */ + playJump(fade = 0.08, opts = {}) { + this._airJumpHold = !!opts.holdAir; + if (this.actions.has('jump')) { + this._gaitLocked = true; + this.animState = 'jump'; + this.play('jump', fade); + const duration = this.actions.get('jump')?.getClip()?.duration ?? 0.55; + // Hold: clamp near end of jump clip until clearAirJumpHold (landing) + if (this._airJumpHold) { + this._oneShotTimer = 999; + try { + const act = this.actions.get('jump'); + act.clampWhenFinished = true; + } catch { + /* */ + } + } else { + this._oneShotTimer = Math.min(duration, 0.7) + 0.02; + } + this._attackTimer = this._oneShotTimer; + return true; + } + this._gaitLocked = true; + this.animState = 'jump'; + this._oneShotTimer = this._airJumpHold ? 999 : 0.45; + return false; + } + + /** Keep jump pose while airborne (called from combat loco). */ + holdAirJumpPose() { + if (!this._airJumpHold) return; + if (this._flipActive) return; + if (this.animState === 'attack' || this.animState === 'cast_loop') return; + if (this.animState === 'fall' || this.animState === 'fallLoop') return; + if (this.actions.has('jump') && this.animState !== 'jump') { + this.animState = 'jump'; + this.play('jump', 0.12); + } + this._gaitLocked = true; + this._oneShotTimer = Math.max(this._oneShotTimer, 0.5); + } + + /** + * Deterministic air locomotion: jump hold → fallLoop when descending. + * @param {{ airborne: boolean, vy: number, airTime: number, flipping?: boolean }} state + */ + updateAirLocomotion(state) { + if (!state?.airborne) return; + if (this._flipActive || state.flipping) return; + if (this.animState === 'attack' || this.animState === 'cast_loop' || this.animState === 'charge') { + return; + } + if (this.animState === 'fallLand' || this.animState === 'fallRoll') return; + + if (shouldEnterFall(state)) { + this.playFallLoop(FALL_THRESHOLDS.fallInBlend); + } else if (this._airJumpHold) { + this.holdAirJumpPose(); + } + } + + /** + * Loop falling cycle (Fall A Loop.fbx) — preferred while descending. + * @param {number} [fade] + */ + playFallLoop(fade = FALL_THRESHOLDS.fallInBlend) { + const role = + (this.actions.has('fallLoop') && 'fallLoop') || + (this.actions.has('combat_mobility:fallLoop') && 'combat_mobility:fallLoop') || + (this.actions.has('fall') && 'fall') || + (this.actions.has('combat_mobility:fall') && 'combat_mobility:fall') || + (this.actions.has('fallIdle') && 'fallIdle') || + null; + if (!role) { + // No fall clips — keep jump hold + this.holdAirJumpPose(); + return false; + } + if (this.animState === 'fallLoop' || this.animState === 'fall') { + this._gaitLocked = true; + this._oneShotTimer = Math.max(this._oneShotTimer, 0.5); + return true; + } + this._airJumpHold = false; + this._gaitLocked = true; + this.animState = role.includes('fallLoop') || role === 'fallLoop' ? 'fallLoop' : 'fall'; + this.play(role, fade); + // Loop clips stay locked until land + this._oneShotTimer = 999; + const act = this.actions.get(role); + try { + if (act) { + act.setLoop(LoopRepeat, Infinity); + act.clampWhenFinished = false; + act.timeScale = 1; + } + } catch { + /* */ + } + return true; + } + + /** + * Landing recovery — fallLand or fallRoll (deterministic pick). + * @param {{ impactVy: number, horizSpeed: number, wantRoll?: boolean }} impact + * @returns {{ role: string, ok: boolean }} + */ + playFallLand(impact = {}) { + const pick = pickLandRole(impact); + const candidates = + pick === FALL_ROLES.fallRoll + ? ['fallRoll', 'combat_mobility:fallRoll', 'fallLand', 'combat_mobility:fallLand', 'rollF'] + : ['fallLand', 'combat_mobility:fallLand', 'fallRoll', 'combat_mobility:fallRoll']; + let role = null; + for (const r of candidates) { + if (this.actions.has(r)) { + role = r; + break; + } + } + this._airJumpHold = false; + if (!role) { + this.clearAirJumpHold(); + return { role: 'idle', ok: false }; + } + const fade = FALL_THRESHOLDS.landOutBlend; + this.play(role, fade, { exclusive: true }); + const act = this.actions.get(role); + const dur = act?.getClip?.()?.duration ?? FALL_THRESHOLDS.landLockFloor; + try { + if (act) { + act.setLoop(LoopOnce, 1); + act.clampWhenFinished = true; + act.timeScale = 1; + act.paused = false; + } + } catch { + /* */ + } + this._gaitLocked = true; + this._oneShotTimer = Math.max(FALL_THRESHOLDS.landLockFloor, dur * 0.95); + this.animState = pick === FALL_ROLES.fallRoll ? 'fallRoll' : 'fallLand'; + return { role, ok: true }; + } + + /** Release jump hold on land. Prefer playFallLand when fall clips exist. */ + clearAirJumpHold() { + this._airJumpHold = false; + if ( + this.animState === 'jump' || + this.animState === 'fall' || + this.animState === 'fallLoop' + ) { + this._gaitLocked = false; + this._oneShotTimer = 0; + if (this.actions.has('idle')) { + this.animState = 'idle'; + this.play('idle', FALL_THRESHOLDS.landOutBlend); + } + } + } + + /** + * Backflip — prefer real striker/backflip clip; procedural tilt only as fallback. + * Camera hold yaw is set by combat (setup move — do not whip cam 180°). + * @param {number} duration seconds (used if procedural) + * @param {{ holdYaw?: number }} [opts] + */ + playBackflip(duration = 0.55, opts = {}) { + this._airJumpHold = false; + this._flipSign = -1; + this._flipCameraHoldYaw = Number.isFinite(opts.holdYaw) ? opts.holdYaw : this.facing; + // Real clip path + if (this.actions.has('backflip') || this.actions.has('combat_mobility:backflip')) { + const role = this.actions.has('backflip') ? 'backflip' : 'combat_mobility:backflip'; + this.play(role, 0.07, { exclusive: true }); + const act = this.actions.get(role); + const dur = act?.getClip?.()?.duration ?? duration; + this._flipUseClip = true; + this._flipActive = false; // no procedural tilt spin + this._flipTime = 0; + this._flipDuration = Math.max(0.35, dur * 0.98); + this._oneShotTimer = Math.max(this._oneShotTimer, this._flipDuration); + this._gaitLocked = true; + this.animState = 'flip'; + return true; + } + // Procedural fallback + this._flipUseClip = false; + this._flipTime = 0; + this._flipDuration = Math.max(0.2, duration); + this._flipActive = true; + this.playJump(0.05, { holdAir: false }); + this._oneShotTimer = Math.max(this._oneShotTimer, duration + 0.05); + this.animState = 'jump'; + return true; + } + + /** + * Frontflip — real extra/front-flip clip preferred (double-jump / windsurf). + * @param {number} duration + */ + playFrontflip(duration = 0.48) { + this._airJumpHold = false; + this._flipSign = 1; + this._flipCameraHoldYaw = null; // frontflip may follow motion + if (this.actions.has('frontflip') || this.actions.has('combat_mobility:frontflip')) { + const role = this.actions.has('frontflip') ? 'frontflip' : 'combat_mobility:frontflip'; + this.play(role, 0.07, { exclusive: true }); + const act = this.actions.get(role); + const dur = act?.getClip?.()?.duration ?? duration; + this._flipUseClip = true; + this._flipActive = false; + this._flipDuration = Math.max(0.3, dur * 0.98); + this._oneShotTimer = Math.max(this._oneShotTimer, this._flipDuration); + this._gaitLocked = true; + this.animState = 'flip'; + return true; + } + this._flipUseClip = false; + this._flipTime = 0; + this._flipDuration = Math.max(0.22, duration); + this._flipActive = true; + this.playJump(0.05, { holdAir: false }); + this._oneShotTimer = Math.max(this._oneShotTimer, duration + 0.05); + this.animState = 'jump'; + return true; + } + + /** True while clip or procedural flip is live. */ + get isFlipping() { + return !!(this._flipActive || (this._flipUseClip && this.animState === 'flip')); + } + + get isBackflip() { + return this.isFlipping && this._flipSign < 0; + } + + /** Cancel flip (land). */ + clearFlip() { + this._flipActive = false; + this._flipTime = 0; + this._flipSign = -1; + this._flipUseClip = false; + this._flipCameraHoldYaw = null; + this.setLean(0); + if (this.animState === 'flip') { + this._gaitLocked = false; + this.animState = 'idle'; + } + } + + /** World-space cast / projectile origin (hand container or approx chest). */ + getCastOrigin(out) { + const target = out || _castOrigin; + const hand = this.bones?.rHand; + if (hand) { + hand.getWorldPosition(target); + return target; + } + this.root.getWorldPosition(target); + target.y += this.height * 0.72; + target.z += Math.cos(this.facing) * 0.25; + target.x += Math.sin(this.facing) * 0.25; + return target; + } + + /** + * Weapon tip / barrel muzzle world position. + * Pistol: WeaponAttach muzzle marker (barrel tip). Melee: facing offset from grip. + * @param {import('three').Vector3} [out] + * @param {number} [tipOffsetM] metres along blade from grip (melee fallback) + */ + getWeaponTip(out, tipOffsetM = 0.55) { + const target = out || _castOrigin; + const attach = + this.weaponAttach || getWeaponAttachFromHand(this.bones?.rHand) || findWeaponAttach(this); + if (attach) this.weaponAttach = attach; + + // Prefer mesh-fitted cylinder tip (weapon volume SSOT) + if (this.weaponVolume) { + getVolumeTipWorld(this.weaponVolume, target); + if (target.lengthSq() > 1e-8) return target; + } + + // Any WeaponAttach with muzzle/tip marker (pistol barrel OR measured tip) + if (attach?.userData?.muzzle) { + getMuzzleWorld(attach, target); + if (target.lengthSq() > 1e-8) return target; + } + if (attach?.userData?.profile === 'pistol') { + getMuzzleWorld(attach, target); + if (target.lengthSq() > 1e-8) return target; + } + + // Mesh tip: farthest visible weapon mesh AABB corner from hand (kit swords) + if (this._weaponTipFromVisibleMesh?.(target)) { + return target; + } + + this.getCastOrigin(target); + const off = + this.animPackId === 'pistol' + ? FLINTLOCK_FIRE.muzzleFallbackM + : Number.isFinite(tipOffsetM) + ? tipOffsetM + : 0.55; + // Blade roughly forward + slight up from hand (SI) + target.x += Math.sin(this.facing) * off; + target.z += Math.cos(this.facing) * off; + target.y += off * 0.12; + return target; + } + + /** + * Rebuild oriented cylinder (+0.02 m pad) from equipped weapon mesh. + * Call after equip / mesh_ids change. Drives tip, residual, parry, IK grip. + * @param {{ debug?: boolean, padM?: number }} [opts] + */ + rebuildWeaponVolume(opts = {}) { + const attach = + this.weaponAttach || getWeaponAttachFromHand(this.bones?.rHand) || findWeaponAttach(this); + const hand = this.bones?.rHand || null; + this.weaponVolume = buildWeaponMeshVolume(this.model, { + weaponAttach: attach, + handBone: hand, + padM: opts.padM ?? WEAPON_COLLIDER_PAD_M, + debug: !!opts.debug || !!this._debugWeaponVolume + }); + if (this.weaponVolume) { + this.weaponHoldKind = + this.weaponHoldKind || + normalizeHoldKind(attach?.userData?.profile || this.animPackId || 'sword'); + } + return this.weaponVolume; + } + + /** + * Farthest corner of visible kit/lab weapon mesh from R hand — blade tip proxy. + * @param {import('three').Vector3} out + * @returns {boolean} + */ + _weaponTipFromVisibleMesh(out) { + const hand = this.bones?.rHand; + const root = this.model; + if (!hand || !root || !out) return false; + hand.getWorldPosition(_castOrigin); + const handW = _castOrigin.clone(); + let bestD = -1; + let best = null; + const re = /sword|axe|hammer|spear|dagger|knife|blade|mace|staff|wand|club|pick|weapon/i; + const ex = /shield|quiver|bag|arrow/i; + root.traverse((o) => { + if (!o.isMesh || !o.visible) return; + const n = o.name || ''; + if (ex.test(n)) return; + if (!re.test(n) && !o.userData?.labWeapon && !o.userData?.weaponAttach) return; + o.updateWorldMatrix?.(true, false); + const box = new Box3().setFromObject(o); + if (box.isEmpty()) return; + const { min, max } = box; + for (const x of [min.x, max.x]) { + for (const y of [min.y, max.y]) { + for (const z of [min.z, max.z]) { + const d = + (x - handW.x) ** 2 + (y - handW.y) ** 2 + (z - handW.z) ** 2; + if (d > bestD) { + bestD = d; + best = { x, y, z }; + } + } + } + } + }); + if (!best || bestD < 1e-6) return false; + out.set(best.x, best.y, best.z); + return true; + } + + /** + * Barrel / blade forward unit (world). Pistol uses grip→muzzle. + * @param {import('three').Vector3} [out] + */ + getWeaponForward(out = new Vector3()) { + if (this.weaponVolume) { + getVolumeAxisWorld(this.weaponVolume, out); + if (out.lengthSq() > 1e-8) return out; + } + const attach = + this.weaponAttach || getWeaponAttachFromHand(this.bones?.rHand) || findWeaponAttach(this); + if (attach?.userData?.muzzle || attach?.userData?.profile === 'pistol') { + return getBarrelForward(attach, out); + } + out.set(Math.sin(this.facing), 0.05, Math.cos(this.facing)).normalize(); + return out; + } + + /** + * Enable windsurf deck/boom IK. Only while WalkController ride is live. + * DRC combat checks `_rideActive` and yields locomotion. + * @param {boolean} active + * @param {number} [yaw] board heading for knee/elbow poles + */ + setRideActive(active, yaw) { + this._rideActive = !!active; + if (Number.isFinite(yaw)) this._rideYaw = yaw; + if (this.rideIk) this.rideIk.setActive(this._rideActive); + if (this._rideActive) { + // Mount / freeride: hands free for boom/rail IK — sheath weapon + this.weaponSheath?.sheath?.('mount'); + // Hold idle gait under ride — mixer still runs, IK overrides limbs + this.setGait?.(0, false); + this._gaitLocked = true; + } else { + this._gaitLocked = false; + this._rideParented = false; + // Dismount → draw for combat/land ready + this.weaponSheath?.unsheath?.('dismount'); + } } - /* ------------------------------------------------------------------ */ - /* placement — driven by walk mode, inert otherwise */ - /* ------------------------------------------------------------------ */ + /** + * Mark that character.root is parented under the windsurf vehicle seat. + * While true, placeAt / world locomotion must not write root.position. + * @param {boolean} parented + */ + setRideParented(parented) { + this._rideParented = !!parented; + } + + get isRideParented() { + return !!this._rideParented; + } + + /** + * Vehicle deployed (hide stowed back mesh) or land (show stow). + * @param {boolean} deployed + */ + setBackSlotDeployed(deployed) { + this.backSlot?.setDeployed?.(!!deployed); + } + + /** + * Equip / clear back utility (windsurf, future glider…). + * @param {string} itemId + * @param {{ modelUrl?: string }} [opts] + */ + async equipBackSlot(itemId, opts = {}) { + if (!this.backSlot && this.model) this.backSlot = new BackSlotEquip(this.model); + if (!this.backSlot) return null; + settings.walk = settings.walk || {}; + settings.walk.backSlot = itemId || 'none'; + return this.backSlot.equip(itemId || 'none', opts); + } + + /** + * World sockets from HoverboardRide.getIkWorldTargets(). + * @param {Record} worldSockets + * @param {number} [yaw] + */ + setRideSockets(worldSockets, yaw) { + if (Number.isFinite(yaw)) this._rideYaw = yaw; + if (!this.rideIk) return; + this.rideIk.setTargets(worldSockets); + } + + setPose(pose) { + // Standing stance only on Bip001 (no Mixamo lotus) + settings.character.pose = 'idle'; + return 'idle'; + } + togglePose() { + return this.setPose('idle'); + } + get isSitting() { + return false; + } + get poseWeight() { + return 0; + } - /** Heading, radians about world +Y. 0 faces +Z. */ setFacing(yaw) { + // While parented to board, yaw is board group rotation — keep root local 0 + if (this._rideParented || this._rideActive) { + this._rideYaw = yaw; + this.root.rotation.y = 0; + return; + } this.root.rotation.y = yaw; } get facing() { + if (this._rideParented || this._rideActive) { + return this._rideYaw || 0; + } return this.root.rotation.y; } - /** - * Bank the body about its own forward axis. Positive angles roll the head to - * the rig's right, so leaning into a left-hand turn is a negative angle. - */ setLean(angle) { + if (this._flipActive) return; // backflip owns tilt + // Board banks the vehicle; keep mild body lean only when not parented + if (this._rideParented) return; this.tilt.quaternion.setFromAxisAngle(this.forwardAxis, angle); } - /** Put the character back on the floor, upright and facing where it was. */ resetPlacement() { + if (this._rideParented) return; // WalkController owns unparent first this.root.position.y = 0; this.setLean(0); + this.setRideActive(false); + } + + /** + * After windsurf dismount: land controller normal (no ride IK / no parent). + * Call only after WalkController has unparented root to the scene. + * @param {{ x?: number, y?: number, z?: number, yaw?: number }} [pose] + */ + restoreFromRide(pose = {}) { + this._rideParented = false; + this.setRideActive(false); + this._gaitLocked = false; + this._softHipRide = 0; + this.clearFlip?.(); + this.setLean(0); + if (this.rideIk) { + this.rideIk.setActive(false); + this.rideIk.weight = 0; + // Restore bind hip Y after absolute drop + if (this.rideIk.hips && Number.isFinite(this.rideIk._hipBindY)) { + this.rideIk.hips.position.y = this.rideIk._hipBindY; + } + } + this.setBackSlotDeployed(false); + // Free root orientation for land loco + this.root.rotation.x = 0; + this.root.rotation.z = 0; + this.root.scale.set(1, 1, 1); + if (Number.isFinite(pose.yaw)) this.root.rotation.y = pose.yaw; + if (Number.isFinite(pose.x) || Number.isFinite(pose.z)) { + this.root.position.set( + Number.isFinite(pose.x) ? pose.x : this.root.position.x, + Number.isFinite(pose.y) ? pose.y : 0, + Number.isFinite(pose.z) ? pose.z : this.root.position.z + ); + } else if (Number.isFinite(pose.y)) { + this.root.position.y = pose.y; + } else { + this.root.position.y = 0; + } + this.setGait?.(0, false); } update(dt) { if (!this.mixer) return; - // Editing the tuning sliders re-bakes the pose; cheap and rare. - if (this.sitting?.valid && this.sitting.stale) this.sitting.build(); + // While riding, do not run combat one-shot / cast gait machine over limbs + if (!this._rideActive) { + if (this._oneShotTimer > 0) { + this._oneShotTimer -= dt; + this._attackTimer = this._oneShotTimer; + if (this._oneShotTimer <= 0) { + this._gaitLocked = false; + if (this._castingExternal && this.actions.has('cast')) { + this.animState = 'cast_loop'; + this.play('cast', 0.2); + } else { + const g = this._gait; + this._gait = -1; + this.setGait(g, g >= 2); + } + } + } else if (!this._gaitLocked) { + if (this._castingExternal && this.actions.has('cast') && this._gait === 0) { + if (this.animState !== 'cast_loop') { + this.animState = 'cast_loop'; + this.play('cast', 0.15); + } + } else if (this.animState === 'cast_loop' && !this._castingExternal) { + this.animState = 'idle'; + if (this.actions.has('idle')) this.play('idle', 0.25); + } + } + } this.mixer.timeScale = settings.global.animationSpeed; this.mixer.update(dt); - if (!this.sitting?.valid) return; - this._poseTime += dt; + // Auto sheath / unsheath: traversal · mount · air · mobility vs combat draw + // oneShotActive only for combat flourishes (not jump/dodge timers) + const combatOneShot = + this._oneShotTimer > 0 && + !this._rideActive && + (this.animState === 'attack' || + this.animState === 'charge' || + this.animState === 'parry' || + this.animState === 'cast_loop'); + this.weaponSheath?.update?.({ + dt, + gait: this._gait, + sprinting: this._sprinting || this._gait >= 2, + rideActive: this._rideActive, + rideParented: this._rideParented, + animState: this.animState, + casting: this._castingExternal, + pistolReload: !!this._pistolReload?.active, + oneShotActive: combatOneShot + }); - const target = this.isSitting ? 1 : 0; - const step = dt / Math.max(0.001, this._poseBlend ?? settings.character.blendTime); - this._poseWeight = MathUtils.clamp( - this._poseWeight + MathUtils.clamp(target - this._poseWeight, -step, step), - 0, - 1 - ); + // Post-mixer weapon hold residual (SSOT: ObjectStore grudge6-weapon-hold-pose) + // Only while drawn — sheathed mesh rides hip/back stow, not hand residual. + if ( + this.mixer && + this.model && + !this._pistolReload?.active && + this.weaponSheath?.isDrawn !== false + ) { + const kind = + this.weaponHoldKind || + normalizeHoldKind(this.weaponAttach?.userData?.profile || this.animPackId || 'sword'); + applyWeaponHoldPose(this.mixer, this._gait, kind, { + THREE: { Quaternion, Euler }, + root: this.model, + hand: 'both', + offKind: this.weaponHoldOffKind || null + }); + } + + // Flintlock procedural reload (post-mixer bone / weapon overlay) + if (this._pistolReload?.active) { + this._pistolReload.update(dt); + } + + // Soft hand aim toward soft-lock / cast target (optional HandIK) + if (this.ik?.aimWeight > 1e-3) this.ik.update?.(); + + // Back-slot windsurf sail cloth (stow visible) — vertex wind only + this.backSlot?.update?.(dt, { wind: this._gait >= 2 ? 1.35 : this._gait >= 1 ? 1.1 : 0.85 }); + + // Procedural flip: full revolution about local right (X) on tilt + // _flipSign −1 backflip · +1 frontflip (windsurf deploy) + if (this._flipActive) { + this._flipTime += dt; + const u = MathUtils.clamp(this._flipTime / this._flipDuration, 0, 1); + const sign = this._flipSign >= 0 ? 1 : -1; + const angle = sign * u * Math.PI * 2; + this.tilt.quaternion.setFromAxisAngle(_flipAxis, angle); + if (u >= 1) { + this._flipActive = false; + this.setLean(0); + } + } - // Ease the blend so the sit settles instead of arriving at constant speed. - this.sitting.apply(MathUtils.smoothstep(this._poseWeight, 0, 1), this._poseTime); + // Ride IK: default here if WalkController did not call applyRiderIk this frame. + // Preferred order (SSOT): walk.update → character.update (mixer) → walk.applyRiderIk + if (!this._rideIkExternal) { + this.applyRideIk(dt); + } + this._rideIkExternal = false; + } + + /** + * Post-mixer windsurf IK (feet deck · hands boom). + * Call from WalkController.applyRiderIk after character.update so mixer wins first. + * @param {number} dt + */ + applyRideIk(dt) { + if (!this.rideIk || !(this._rideActive || this.rideIk.weight > 1e-3)) return; + const yaw = this._rideYaw || this.facing; + _rideFwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + _rideLeft.set(Math.cos(yaw), 0, -Math.sin(yaw)); + this.rideIk.update(dt, { + boardForward: _rideFwd, + boardLeft: _rideLeft, + hipDrop: (settings.walk?.hipDrop ?? 0.1) + (this._softHipRide || 0) + }); } + /** + * World feet position. While parented to windsurf, root.position is local — + * always return world so camera / dust / systems do not "drag" local coords. + */ get position() { + const p = this.root.parent; + const parented = + this._rideParented || + p?.name === 'RideSeat' || + p?.name?.startsWith?.('socket_') || + p?.name === 'RideBody'; + if (parented) { + this.root.getWorldPosition(this._worldPos); + return this._worldPos; + } return this.root.position; } + /** Write world feet into out (safe while parented). */ + getWorldPosition(out = this._worldPos) { + this.root.getWorldPosition(out); + return out; + } + dispose() { this.mixer?.stopAllAction(); this.mixer = null; diff --git a/src/animation/SittingPose.js b/src/animation/SittingPose.js index 2008bf4..774f408 100644 --- a/src/animation/SittingPose.js +++ b/src/animation/SittingPose.js @@ -2,13 +2,14 @@ import { MathUtils, Quaternion, Vector3 } from 'three'; import { settings } from '../config/settings.js'; /** - * SittingPose — a procedural cross-legged meditation pose for the Mixamo rig. + * SittingPose — a procedural cross-legged meditation pose. * - * The FBX only ships a standing idle, so the seated pose is authored here in - * code rather than imported as a second clip. Every joint is described in a - * *rig-relative* frame (forward / up / left, derived from the bind pose) which - * makes the table readable and keeps it independent of which way the rig - * happens to face in world space: + * Authored against Mixamo-style short bone names (Hips, LeftArm, …) and also + * accepts grudge6 **Bip001** skeletons via `toMixamoShort()` (Pelvis→Hips, + * L UpperArm→LeftArm, …). The seated pose is code-authored rather than a + * second clip. Every joint is described in a *rig-relative* frame + * (forward / up / left, derived from the bind pose) which makes the table + * readable and keeps it independent of which way the rig happens to face: * * - `dir` the world direction the bone should point at (bone → child). * Absolute, so tweaking the thigh never drags the shin off target. @@ -147,8 +148,9 @@ export class SittingPose { this.skinStride = Math.max(1, Math.ceil(node.geometry.attributes.position.count / 12000)); } if (!node.isBone) return; - // Exporters disagree on the namespace: "mixamorig:LeftArm", "mixamorigLeftArm". - const short = node.name.split(':').pop().replace(/^mixamorig/i, ''); + // Mixamo: "mixamorig:LeftArm" / "mixamorigLeftArm". Bip001: "Bip001 L UpperArm". + const raw = node.name.split(':').pop().replace(/^mixamorig/i, ''); + const short = toMixamoShort(raw); if (!this.bones.has(short)) this.bones.set(short, node); this.rest.set(node, { @@ -163,7 +165,7 @@ export class SittingPose { this.hips = this.bones.get('Hips') ?? null; if (!this.hips || this.entries.length === 0) { - console.warn('[SittingPose] rig does not look like a Mixamo skeleton — sitting disabled'); + console.warn('[SittingPose] no Hips/Pelvis skeleton — sitting disabled'); return; } @@ -440,6 +442,50 @@ export class SittingPose { } /** Match a bone name against the pose table, resolving side and mirroring. */ +/** + * Map grudge6 Bip001 / Mixamo raw names onto the Mixamo short names used by + * the pose tables (Hips, LeftArm, LeftUpLeg, …). + */ +function toMixamoShort(name) { + let n = String(name || '').trim(); + // Strip Bip001 prefix: "Bip001 Pelvis" / "Bip001_L_UpperArm" / "Bip001 L UpperArm" + n = n.replace(/^Bip001[\s._-]*/i, ''); + n = n.replace(/[._]+/g, ' ').replace(/\s+/g, ' ').trim(); + + const lower = n.toLowerCase(); + const BIP_MAP = { + pelvis: 'Hips', + hips: 'Hips', + spine: 'Spine', + spine1: 'Spine1', + spine2: 'Spine2', + neck: 'Neck', + head: 'Head', + 'l clavicle': 'LeftShoulder', + 'r clavicle': 'RightShoulder', + 'l upperarm': 'LeftArm', + 'r upperarm': 'RightArm', + 'l forearm': 'LeftForeArm', + 'r forearm': 'RightForeArm', + 'l hand': 'LeftHand', + 'r hand': 'RightHand', + 'l thigh': 'LeftUpLeg', + 'r thigh': 'RightUpLeg', + 'l calf': 'LeftLeg', + 'r calf': 'RightLeg', + 'l foot': 'LeftFoot', + 'r foot': 'RightFoot', + 'l toe0': 'LeftToeBase', + 'r toe0': 'RightToeBase', + 'l toe': 'LeftToeBase', + 'r toe': 'RightToeBase' + }; + if (BIP_MAP[lower]) return BIP_MAP[lower]; + + // Already Mixamo-ish (LeftArm, Hips, …) + return n.replace(/\s+/g, ''); +} + function resolveSpec(short) { if (CENTER[short]) return { spec: CENTER[short], sign: 1, side: '' }; for (const side of ['Left', 'Right']) { diff --git a/src/animation/WalkController.js b/src/animation/WalkController.js index c0d4ff1..8dc2a6b 100644 --- a/src/animation/WalkController.js +++ b/src/animation/WalkController.js @@ -1,134 +1,304 @@ import { MathUtils, Vector3 } from 'three'; import { settings } from '../config/settings.js'; -import { AirScooter } from '../effects/AirScooter.js'; +import { HoverboardRide } from '../effects/HoverboardRide.js'; import { DecalType } from '../effects/GroundDecals.js'; import { getColor } from '../utils/color.js'; import { clamp, damp, Easing, saturate } from '../utils/math.js'; +import { isDryLand, terrainOpts } from '../world/IslandHeightfield.js'; +import { canDeployBackItem, getBackMobility } from '../config/backSlotMobilitySsot.js'; const TAU = Math.PI * 2; const _p = new Vector3(); const _t = new Vector3(); const _side = new Vector3(); +const _deck = new Vector3(); +const _footL = new Vector3(); +const _footR = new Vector3(); -/** Shortest signed angle from `a` to `b`. */ const wrapAngle = (angle) => MathUtils.euclideanModulo(angle + Math.PI, TAU) - Math.PI; -/** - * Phases of a ride. `idle` is the only one in which the character is back under - * the animation clip's control. - */ -const Phase = Object.freeze({ +/** Local phase machine — also report every transition to SessionState.setRidePhase */ +export const Phase = Object.freeze({ IDLE: 'idle', - LEAP: 'leap', // arcing through the air toward the head of the path - RIDE: 'ride', // seated on the ball, following the path - DISMOUNT: 'dismount' // stepping off at the far end + LEAP: 'leap', // frontflip deploy → board + RIDE: 'ride', // path-follow + FREERIDE: 'freeride', // WASD boat (tslda-like) + DISMOUNT: 'dismount' }); /** - * Walk mode: turns a drawn path into a ride instead of a cast. + * Walk / windsurf mode — board is a tiny boat (back-slot deployable). * - * The sequence is + * Deploy contract (new): + * 1. Frontflip off land; sail/board materializes from back mid-flip + * 2. Land feet on deck sockets · hands on sail boom (RideIK) + * 3. Path ride OR freeride WASD (tslda / Wind Waker boat feel) + * 4. Soft-body lean + wave follow (rigged body, ragdoll-lite to ocean) + * 5. Space hop; skills allowed while freeride when settings.walk.skillsWhileRide * - * leap → land → fold into the meditation pose on an air scooter → - * ride the path, banking into its turns → dismount → standing idle + * Mount contract (until dismount): + * - Character reparented to unscaled RideSeat (never boardRoot scale) + * - RideIK feet→footL/R, hands→sailRail/boom (post-mixer; absolute hip Y) + * - Physics capsule glued to deck + * - Back-slot stow mesh hidden while vehicle live * - * and it is driven entirely from the curve `PathDrawer` already produces, so a - * walk and a cast are the same gesture. The controller owns the character's - * placement (position, heading, bank) for as long as a ride lasts and hands it - * straight back afterwards; `CharacterController` never knows about any of it - * beyond the small placement API it exposes. + * Ref: https://github.com/Robpayot/tslda · docs/WINDSURF_RIDE_SSOT.md */ export class WalkController { /** * @param {import('./CharacterController.js').CharacterController} character - * @param {object} ctx { scene, particles, lights, decals, bursts, shake } + * @param {object} ctx { scene, particles, lights, decals, bursts, shake, physics?, water?, session? } */ constructor(character, ctx) { this.character = character; this.ctx = ctx; + /** @type {import('../core/SessionState.js').SessionState|null} */ + this.session = ctx.session || null; - this.scooter = new AirScooter(ctx); + this.scooter = new HoverboardRide(ctx, ctx.assets || null); ctx.scene.add(this.scooter.group); + // Init without emit (session may attach later) this.phase = Phase.IDLE; this.curve = null; this.length = 0; - this.distance = 0; // metres ridden + this.distance = 0; this.speed = 0; - - this._from = new Vector3(); // where the leap started - this._target = new Vector3(); // where it lands - this._home = new Vector3(); // where the whole sequence started - this._exit = new Vector3(); // where the dismount started - this._anchor = new Vector3(); // where the ball is, which is his seat minus him + this._turnRate = 0; + + this._from = new Vector3(); + this._target = new Vector3(); + this._home = new Vector3(); + this._exit = new Vector3(); + this._anchor = new Vector3(); + this._vel = new Vector3(); // freeride XZ velocity + this._vy = 0; this._leapTime = 0; this._leapDuration = 0; this._rideTime = 0; this._dismountTime = 0; this._yaw = 0; this._lean = 0; - this._landStanding = false; // the leap home, which ends on foot + this._landStanding = false; + this._mounted = false; + this._rideShakeT = 0; + this._sailSpawned = false; + this._wasJump = false; + /** @type {Set|null} live keys from App while freeride */ + this._keys = null; } get active() { return this.phase !== Phase.IDLE; } - /** Height of the ball's centre above the floor. */ + get freeriding() { + return this.phase === Phase.FREERIDE; + } + + /** + * Single phase transition — reports to SessionState for gates/HUD. + * @param {string} next + */ + _setPhase(next) { + if (this.phase === next) return; + this.phase = next; + this.session?.setRidePhase?.(next); + } + + /** Optional inject after construction. */ + setSession(session) { + this.session = session || null; + this.session?.setRidePhase?.(this.phase, { silent: true }); + } + get ballHeight() { - return settings.walk.radius + settings.walk.hover; + return this.scooter.deckHeight || settings.walk.hover || 0.06; } - /** Height the seat rides at — the ball's top, minus how far he sinks into it. */ get seatHeight() { - const c = settings.walk; - return this.ballHeight + c.radius * (1 - c.seatSink); + const stand = settings.walk.standOffset ?? 0.02; + return this.ballHeight + stand; } - /* ------------------------------------------------------------------ */ - /* entry points */ - /* ------------------------------------------------------------------ */ + async load(assets) { + await this.scooter.load(assets); + } + + /** @param {import('../physics/PhysicsWorld.js').PhysicsWorld|null} physics */ + setPhysics(physics) { + this.ctx.physics = physics || null; + } + + /** Live keyboard for freeride (App passes InputManager.keys each frame). */ + setKeys(keys) { + this._keys = keys || null; + } /** - * Ride `curve`. Re-triggering mid-ride is allowed: he simply leaps off - * whatever he is doing and onto the head of the new path. - * - * @returns {boolean} whether the path was long enough to ride + * Quick ocean deploy: frontflip + sail from back, freeride WASD. + * **Water-only** — windsurf is not a land mobility tool. + * @param {{ yaw?: number, force?: boolean }} [opts] + * @returns {boolean} */ + beginFreeride(opts = {}) { + // Domain gate: windsurf deploy requires wet surface (not dry island pad) + if (!opts.force && !this._canDeployWindsurf()) { + this.ctx?.onToast?.( + 'Windsurf is water-only — stand in ocean / wet shore (not dry land)' + ); + console.info('[Walk] freeride blocked — dry land'); + return false; + } + + this._dismountRider(true); + this.scooter.cancel(); + this.character.setRideActive?.(false); + + if (!this.scooter.ready && this.ctx.assets) { + this.scooter.load(this.ctx.assets).catch((err) => console.warn('[Walk] board load', err)); + } + + this.curve = null; + this.length = 0; + this.distance = 0; + this.speed = 0; + this._vel.set(0, 0, 0); + this._vy = 0; + this._rideTime = 0; + this._landStanding = false; + this._turnRate = 0; + this._rideShakeT = 0; + this._sailSpawned = false; + this._wasJump = false; + + this._home.copy(this.character.position); + this._from.copy(this.character.position); + const yaw = Number.isFinite(opts.yaw) ? opts.yaw : this.character.facing; + this._yaw = yaw; + // Land a few metres into water along facing + const dist = 2.8; + this._target.set( + this._from.x + Math.sin(yaw) * dist, + this.seatHeight, + this._from.z + Math.cos(yaw) * dist + ); + this._startLeap({ freeride: true, frontflip: true }); + // Part identity toast (board / sail / front / engine sockets) + const parts = this.scooter?.describeParts?.(); + if (parts?.length) { + this.ctx?.onToast?.( + `Windsurf · ${parts.map((p) => p.label).join(' · ')}` + ); + } + return true; + } + + /** + * Windsurf may only deploy when feet or near-shore sample is **not dry land**. + * Uses IslandHeightfield.isDryLand (same as grass scatter). + */ + _canDeployWindsurf() { + const def = getBackMobility('windsurf'); + const pos = this.character?.position || this.character?.root?.position; + if (!pos) return false; + const opts = terrainOpts(); + // Allow if current feet wet OR a sample a few metres forward is wet + const yaw = this.character.facing || 0; + const samples = [ + [pos.x, pos.z], + [pos.x + Math.sin(yaw) * 2.5, pos.z + Math.cos(yaw) * 2.5], + [pos.x + Math.sin(yaw) * 4.5, pos.z + Math.cos(yaw) * 4.5] + ]; + let wet = false; + for (const [x, z] of samples) { + if (!isDryLand(x, z, opts, 0.02)) { + wet = true; + break; + } + } + const gate = canDeployBackItem(def, { onWater: wet, onLand: !wet }); + return !!gate.ok; + } + begin(curve) { const length = curve.getLength(); if (length < 0.5) return false; + this._dismountRider(true); this.scooter.cancel(); + this.character.setRideActive?.(false); + + if (!this.scooter.ready && this.ctx.assets) { + this.scooter.load(this.ctx.assets).catch((err) => console.warn('[Walk] board load', err)); + } this.curve = curve; this.length = length; this.distance = 0; this.speed = 0; + this._vel.set(0, 0, 0); + this._vy = 0; this._rideTime = 0; this._landStanding = false; + this._turnRate = 0; + this._rideShakeT = 0; + this._sailSpawned = false; + this._wasJump = false; if (!this.active) this._home.copy(this.character.position); this._from.copy(this.character.position); curve.getPointAt(0, this._target).setY(this.seatHeight); - this._startLeap(); + this._startLeap({ freeride: false, frontflip: true }); return true; } - /** Abandon the ride and put him back on his feet where he stands. */ + /** + * Hard abort: unparent rider, remove vehicle from scene, restore land loco. + * Prefer {@link requestDismount} for play (soft step-off + board fade). + */ cancel() { - if (!this.active) return; + if (!this.active && !this._mounted && !this.scooter.active) return; + this._exitRideHard({ snapFeet: true }); + } + + /** + * Player get-off while mounted (freeride / path ride). + * Soft step-off → board release (fade out) → land controller normal. + * @returns {boolean} true if dismount started + */ + requestDismount() { + if (this.phase !== Phase.RIDE && this.phase !== Phase.FREERIDE) return false; + if (this.phase === Phase.DISMOUNT) return true; + this._startDismount(); + return true; + } + + /** + * Full teardown: vehicle gone + character controller land state. + * @param {{ snapFeet?: boolean }} [opts] + */ + _exitRideHard(opts = {}) { + const snapFeet = opts.snapFeet !== false; + this._dismountRider(snapFeet); + // Instant remove from scene (no lingering vehicle mesh) this.scooter.cancel(); - this.phase = Phase.IDLE; + this.character.setRideActive?.(false); + this.character.setRideParented?.(false); + this.character.restoreFromRide?.(snapFeet ? { y: 0 } : undefined); + this._setPhase(Phase.IDLE); this.curve = null; - this.character.setPose('idle', settings.walk.poseBlend); - this.character.resetPlacement(); + this._vel.set(0, 0, 0); + this._vy = 0; + this._mounted = false; + this._sailSpawned = false; + this._softHip = 0; + this.character.setPose?.('idle', settings.walk.poseBlend); + this._syncPhysicsToCharacter(); } - /* ------------------------------------------------------------------ */ - update(dt) { if (dt > 0) { switch (this.phase) { @@ -138,6 +308,9 @@ export class WalkController { case Phase.RIDE: this._updateRide(dt); break; + case Phase.FREERIDE: + this._updateFreeride(dt); + break; case Phase.DISMOUNT: this._updateDismount(dt); break; @@ -146,15 +319,185 @@ export class WalkController { } } - // The ball keeps animating through the dismount and the fade after it, so - // it is updated outside the phase switch. It only tracks him while he is - // riding: once he steps off, it stays where he left it and dissipates there. + // Board motion always when visible (includes releasing death) if (this.scooter.active) { - if (this.phase === Phase.RIDE) { - this._anchor.set(this.character.position.x, this.ballHeight, this.character.position.z); + _side.set(Math.cos(this._yaw), 0, -Math.sin(this._yaw)); + this.scooter.update( + dt, + this._anchor, + _side, + this.distance, + this.speed, + this._yaw, + this._turnRate + ); + } + + // After board bank/sway: mount pose + sockets (IK applied post-mixer via applyRiderIk) + if ((this.phase === Phase.RIDE || this.phase === Phase.FREERIDE) && this._mounted) { + this._syncMountedRider(dt); + } else if (this.scooter.active && this.scooter.ready && this.phase === Phase.LEAP) { + // Pre-land: aim hands/feet toward board sockets once board exists + if (this.scooter.group.visible && this.scooter._birth > 0.2) { + this.character.setRideSockets?.(this.scooter.getIkWorldTargets(), this._yaw); + } + } + } + + /** + * SSOT apply order: walk.update → character.update (mixer) → walk.applyRiderIk. + * Plants feet on deck straps + hands on boom after mixer owns the pose. + * @param {number} dt + */ + applyRiderIk(dt) { + // Only while vehicle-mounted — not during dismount/idle land + if (this.phase !== Phase.RIDE && this.phase !== Phase.FREERIDE && this.phase !== Phase.LEAP) { + return; + } + if (!this.character._rideActive && this.phase !== Phase.LEAP) return; + if (this.character._softHipRide !== undefined) { + this.character._softHipRide = this._softHip || 0; + } else { + this.character._softHipRide = this._softHip || 0; + } + this.character._rideIkExternal = true; + this.character.applyRideIk?.(dt); + } + + /* ------------------------------------------------------------------ */ + /* mount / physics */ + /* ------------------------------------------------------------------ */ + + /** + * Parent character.root under unscaled RideSeat (not boardRoot). + * Vehicle group owns world transform until _dismountRider — do not + * write world XZ to character while mounted. + */ + _mountRider() { + const seat = this.scooter.getSeat?.() || this.scooter.seatRoot || this.scooter.sockets?.deckCenter; + if (!seat || !this.scooter.ready) { + console.warn('[Walk] mount deferred — seat not ready'); + return; + } + + // Never inherit birth scale 0.01 (rips / shrinks hero off screen) + this.scooter.forceFullSize?.(); + this.scooter.group.updateWorldMatrix(true, true); + seat.updateWorldMatrix(true, true); + + // Re-assert parent every call (recover if something stole root) + if (this.character.root.parent !== seat) { + seat.attach(this.character.root); + } + + // Local feet on deck only — vehicle carries world pose; scale identity SI + const stand = settings.walk.standOffset ?? 0.02; + this.character.root.position.set(0, stand - (this._softHip || 0), 0); + this.character.root.rotation.set(0, 0, 0); + this.character.root.scale.set(1, 1, 1); + this.character.clearFlip?.(); + this.character.setLean(0); + + this._mounted = true; + this.character.setRideParented?.(true); + this.character.setRideActive?.(true, this._yaw); + this.character.setRideSockets?.(this.scooter.getIkWorldTargets(), this._yaw); + // Back-slot stow mesh off while vehicle is live + this.character.setBackSlotDeployed?.(true); + this._syncPhysicsToCharacter(); + if (!this._mountLogged) { + this._mountLogged = true; + console.info('[Walk] mounted — character parented under', seat.name || 'RideSeat'); + } + } + + /** + * Unparent character to scene; keep world position. + * Only call when ride ends — not mid-ride. + * @param {boolean} [snapY] force feet to y=0 + */ + _dismountRider(snapY = false) { + this._mountLogged = false; + if (!this._mounted) { + this.character.setRideParented?.(false); + if (this.character.root.parent !== this.ctx.scene) { + this.ctx.scene.attach(this.character.root); } - _side.set(Math.cos(this._yaw), 0, -Math.sin(this._yaw)); // the rider's left - this.scooter.update(dt, this._anchor, _side, this.distance, this.speed); + return; + } + + // World pose before unparent + this.character.root.updateWorldMatrix(true, false); + this.character.root.getWorldPosition(_p); + const worldYaw = this._yaw; + + this.ctx.scene.attach(this.character.root); + this.character.setRideParented?.(false); + this.character.setLean(0); + this.character.root.position.set(_p.x, snapY ? 0 : Math.max(0, _p.y), _p.z); + this.character.root.rotation.set(0, worldYaw, 0); + this.character.root.scale.set(1, 1, 1); + this._mounted = false; + this.character.setRideActive?.(false); + // Stow back-slot item mesh again (windsurf utility) + this.character.setBackSlotDeployed?.(false); + this._syncPhysicsToCharacter(); + } + + /** Place Rapier capsule under rider so combat resume doesn't drop through. */ + _syncPhysicsToCharacter() { + const phys = this.ctx.physics; + if (!phys?.ready || !phys.setPlayerFeet) return; + this.character.root.updateWorldMatrix(true, false); + this.character.root.getWorldPosition(_p); + // Feet ≈ root when grounded kit; while mounted root is near deck + phys.setPlayerFeet(_p.x, Math.max(0, _p.y), _p.z); + } + + /** + * Each ride frame after board update: keep parent, local stand, IK, physics. + * Never writes world XZ onto character.root — vehicle group owns that. + */ + _syncMountedRider(dt) { + if (!this.scooter.ready) return; + + // Hard guarantee: stay parented under seat for whole ride + this._mountRider(); + + const stand = settings.walk.standOffset ?? 0.02; + const soft = this._softHip || 0; + // Local-only stand pose (parent = deck) + this.character.root.position.set(0, stand - soft, 0); + this.character.root.rotation.set(0, 0, 0); + this.character.root.scale.set(1, 1, 1); + + // Board yaw is world; IK poles use _rideYaw, root stays local 0 + this.character.setRideActive?.(true, this._yaw); + this._rideYawForIk = this._yaw; + + this.scooter.group.updateWorldMatrix(true, true); + const targets = this.scooter.getIkWorldTargets(); + this.character.setRideSockets?.(targets, this._yaw); + + if (targets.deckCenter) { + _deck.copy(targets.deckCenter); + } else { + this.scooter.getSocketWorld('deckCenter', _deck); + } + const phys = this.ctx.physics; + if (phys?.ready && phys.setPlayerFeet) { + phys.setPlayerFeet(_deck.x, Math.max(0, _deck.y), _deck.z); + } + + this._rideShakeT += dt; + const c = settings.walk; + const refSp = this.phase === Phase.FREERIDE ? c.freerideSpeed ?? 7 : c.speed; + const speedN = saturate(this.speed / Math.max(0.5, refSp)); + const shakeAmp = + (c.rideShake ?? 0.045) * speedN * (settings.global.cameraShake ?? 1) * settings.global.explosionIntensity; + if (shakeAmp > 0.004 && this.ctx.shake && this._rideShakeT > 0.08) { + this._rideShakeT = 0; + this.ctx.shake.add(shakeAmp, 0.55, 18 + speedN * 10); } } @@ -162,16 +505,28 @@ export class WalkController { /* leap */ /* ------------------------------------------------------------------ */ - _startLeap() { + /** + * @param {{ freeride?: boolean, frontflip?: boolean }} [opts] + */ + _startLeap(opts = {}) { const c = settings.walk; const reach = _p.copy(this._target).setY(0).distanceTo(_t.copy(this._from).setY(0)); - this.phase = Phase.LEAP; + this._setPhase(Phase.LEAP); this._leapTime = 0; this._leapDuration = clamp(reach / Math.max(0.5, c.jumpSpeed), c.jumpMin, c.jumpMax); this._yaw = this.character.facing; + this._enterFreeride = !!opts.freeride; + this._sailSpawned = false; + + // Frontflip while sailing out of the back slot + if (opts.frontflip !== false) { + this.character.playFrontflip?.(c.frontflipDuration ?? 0.72); + } + + // Board spawns mid-flip from "back" (sail deploy) — not immediately under feet + this._anchor.set(this._target.x, this.seatHeight, this._target.z); - // He pushes off: a ring of dust at the take-off point. _p.set(this._from.x, 0.02, this._from.z); this.ctx.decals.spawn(DecalType.DUSTRING, _p, { radius: 1.4, @@ -187,48 +542,96 @@ export class WalkController { this._leapTime += dt; const t = saturate(this._leapTime / Math.max(0.05, this._leapDuration)); - // Horizontal travel is near-linear (it is a jump, not an ease); the height - // is the parabola through both ends. + // Sail/board deploys from back mid-frontflip + const deployAt = c.sailDeployAt ?? 0.28; + if (!this._sailSpawned && t >= deployAt) { + this._sailSpawned = true; + // Spawn slightly behind rider (back slot) then board will track landing + const back = 0.6 * (1 - t); + this._anchor.set( + this._from.x + (this._target.x - this._from.x) * t - Math.sin(this._yaw) * back, + this.seatHeight + c.jumpHeight * 0.35, + this._from.z + (this._target.z - this._from.z) * t - Math.cos(this._yaw) * back + ); + if (this.scooter.ready && !this.scooter.active) { + // Point board along leap facing immediately (not default yaw 0) + this.scooter.spawn(this._anchor, this._yaw); + } + } + + // Keep board gliding toward landing deck + if (this._sailSpawned) { + this._anchor.lerp( + _t.set(this._target.x, this.seatHeight, this._target.z), + 1 - Math.pow(0.02, dt) + ); + } + _p.lerpVectors(this._from, this._target, t); _p.y += c.jumpHeight * 4 * t * (1 - t); - this.character.root.position.copy(_p); + if (!this._mounted) { + if (this.character.root.parent !== this.ctx.scene) { + this.ctx.scene.attach(this.character.root); + } + this.character.root.position.copy(_p); + } this._faceLeap(dt, t); - this._lean = damp(this._lean, 0, 0.01, dt); // any bank from a cut-short ride - this.character.setLean(this._lean); - - // Legs fold up in the air so he arrives already in the pose — held back to - // `tuck` so the fold reads as part of the landing, not of the take-off. - if (!this._landStanding && t >= c.tuck && !this.character.isSitting) { - this.character.setPose('sitting', c.poseBlend); + this._lean = damp(this._lean, 0, 0.01, dt); + // Frontflip owns tilt — don't fight with setLean + if (!this.character._flipActive) this.character.setLean(this._lean); + + if (!this._landStanding && t >= (c.tuck ?? 0.55)) { + this.character.setPose('idle', c.poseBlend); + // Pre-weight RideIK toward boom/feet before contact + if (this.scooter.active) { + this.character.setRideActive?.(true, this._yaw); + this.character.setRideSockets?.(this.scooter.getIkWorldTargets(), this._yaw); + } } if (t < 1) return; - /* ---- landing ---- */ - this.character.root.position.copy(this._target); - if (this._landStanding) { + this._dismountRider(true); + this.character.clearFlip?.(); this.character.resetPlacement(); - this.phase = Phase.IDLE; + this.character.setRideActive?.(false); + this._setPhase(Phase.IDLE); this.curve = null; this._land(0.5); return; } - this._anchor.set(this._target.x, this.ballHeight, this._target.z); - this.scooter.spawn(this._anchor); - this.phase = Phase.RIDE; + // Land on deck — group at surface Y=0; force full board size; parent to unscaled seat + this._anchor.set(this._target.x, 0, this._target.z); + if (!this.scooter.active) this.scooter.spawn(this._anchor, this._yaw); + else { + this.scooter.group.rotation.y = this._yaw; + this.scooter._yaw = this._yaw; + } + this.scooter.forceFullSize?.(); + _side.set(Math.cos(this._yaw), 0, -Math.sin(this._yaw)); + this.scooter.update(0, this._anchor, _side, 0, 0, this._yaw, 0); + this.scooter.group.updateWorldMatrix(true, true); + + this.character.clearFlip?.(); + this._mountRider(); this._rideTime = 0; this.distance = 0; this.speed = 0; + this._vel.set(0, 0, 0); this._land(1); + this._syncMountedRider(0); + + if (this._enterFreeride || !this.curve) { + this._setPhase(Phase.FREERIDE); + this._enterFreeride = false; + } else { + this._setPhase(Phase.RIDE); + } } - /** - * Aim the leap. He starts out facing where he is going and turns onto the - * path's own heading as he comes down, so the ride begins already lined up. - */ _faceLeap(dt, t) { _t.copy(this._target).sub(this._from).setY(0); const travel = _t.lengthSq() > 1e-4 ? Math.atan2(_t.x, _t.z) : this._yaw; @@ -244,10 +647,11 @@ export class WalkController { this._turnTo(target, dt, 0.0005); } - /** Dust and a small camera kick when he touches down. */ _land(scale) { const c = settings.walk; - _p.set(this.character.position.x, 0.02, this.character.position.z); + this.character.root.updateWorldMatrix(true, false); + this.character.root.getWorldPosition(_p); + _p.y = 0.02; this.ctx.decals.spawn(DecalType.DUSTRING, _p, { radius: 2.2 * scale, life: 1.0, @@ -255,7 +659,7 @@ export class WalkController { colorA: getColor(c.colorInner), colorB: getColor(c.colorOuter) }); - this.ctx.shake.add(0.22 * c.landShake * settings.global.explosionIntensity * scale, 1.0, 24); + this.ctx.shake.add(0.28 * c.landShake * settings.global.explosionIntensity * scale, 1.0, 26); } /* ------------------------------------------------------------------ */ @@ -266,38 +670,178 @@ export class WalkController { const c = settings.walk; this._rideTime += dt; - /* ---- how fast he is going ---- */ const remaining = Math.max(0, this.length - this.distance); const rampIn = Easing.outCubic(saturate(this._rideTime / Math.max(0.01, c.accel))); - // Glide to a stop over the last `brake` seconds' worth of path, but never - // below a crawl or the ride would asymptote and never finish. const brakeDistance = Math.max(0.05, c.speed * c.brake); const rampOut = MathUtils.lerp(0.22, 1, Easing.outQuad(saturate(remaining / brakeDistance))); this.speed = c.speed * rampIn * rampOut; this.distance += this.speed * dt; - /* ---- where that puts him ---- */ const u = saturate(this.distance / this.length); this.curve.getPointAt(u, _p); this.curve.getTangentAt(u, _t).setY(0); const heading = _t.lengthSq() > 1e-6 ? Math.atan2(_t.x, _t.z) : this._yaw; - const turn = this._turnTo(heading, dt, c.turnDamping); + this._turnRate = this._turnTo(heading, dt, c.turnDamping); - // A hair of bounce, and a little more of it the faster he is moving. + // Board group Y = surface (bob); deckHeight is local on boardRoot — rider parented const bob = Math.sin(this._rideTime * c.bobRate * TAU) * c.bob * saturate(this.speed / Math.max(0.5, c.speed)); - this.character.root.position.set(_p.x, this.seatHeight + bob, _p.z); + this._anchor.set(_p.x, bob, _p.z); - /* ---- bank into the turn ---- */ - // Positive `turn` is a left-hand turn, and banking into it drops his left - // shoulder — which is a *negative* roll about the rig's forward axis. + // Character lean = board bank feel (extra visual; parent already banks) const rate = Math.max(0.05, c.leanRate); - const target = -clamp(turn / rate, -1, 1) * c.lean * MathUtils.DEG2RAD; + const target = -clamp(this._turnRate / rate, -1, 1) * c.lean * MathUtils.DEG2RAD * 0.35; this._lean = damp(this._lean, target, c.leanDamping, dt); - this.character.setLean(this._lean); + // While mounted, lean is mostly board rotation — keep body lean mild + if (!this._mounted) this.character.setLean(this._lean); + + if (!this._mounted && this.scooter.ready) this._mountRider(); + + if (this.distance >= this.length - 1e-4) { + if (c.freerideAfterPath !== false && c.freeride !== false) { + // Path done → freeride boat (tslda) with residual velocity + this.curve = null; + this._vel.set(Math.sin(this._yaw) * this.speed, 0, Math.cos(this._yaw) * this.speed); + this._setPhase(Phase.FREERIDE); + } else { + this._startDismount(); + } + } + } + + /* ------------------------------------------------------------------ */ + /* freeride — WASD boat (tslda / Wind Waker) */ + /* ------------------------------------------------------------------ */ + + /** + * Sample water elevation (CPU stand-in for StageWater waves). + * @param {number} x + * @param {number} z + */ + _sampleWaterY(x, z) { + const base = settings.walk.freerideWaterY ?? 0; + const t = this._rideTime || 0; + // Match StageWater feel: low amp multi-sine (SI metres) + const a = + Math.sin(x * 0.35 + t * 1.4) * 0.045 + + Math.sin(z * 0.28 + t * 1.1) * 0.035 + + Math.sin((x + z) * 0.5 + t * 2.0) * 0.02; + const water = this.ctx.water; + if (water?.sampleHeight) { + try { + return water.sampleHeight(x, z, t); + } catch { + /* fall through */ + } + } + return base + a; + } + + _updateFreeride(dt) { + const c = settings.walk; + this._rideTime += dt; + if (!this._mounted && this.scooter.ready) this._mountRider(); + + const keys = this._keys; + let ix = 0; + let iz = 0; + if (keys) { + if (keys.has('KeyW') || keys.has('ArrowUp')) iz -= 1; + if (keys.has('KeyS') || keys.has('ArrowDown')) iz += 1; + // A/D turn (boat): A = port (left), D = starboard (right) + // Sign flipped so +ix is left-yaw in freerideTurnRate (was inverted) + if (keys.has('KeyA') || keys.has('ArrowLeft')) ix += 1; + if (keys.has('KeyD') || keys.has('ArrowRight')) ix -= 1; + } + + // Turn boat (tslda joystick X → yaw) + const turn = (c.freerideTurnRate ?? 1.85) * ix; + this._yaw += turn * dt; + this._turnRate = turn; + + // Thrust along board forward (W) / reverse (S) + // Water physics: release W → gentle coast (not hard stop) + // Nautical speed: fishing skill tree + SWG blue meals + rod sea_legs + const nautical = Math.max(0.5, Number(this.ctx.getNauticalSpeedMul?.() ?? 1) || 1); + const fwdX = Math.sin(this._yaw); + const fwdZ = Math.cos(this._yaw); + const maxSp = (c.freerideSpeed ?? 7.2) * nautical; + const accel = (c.freerideAccel ?? 4.5) * Math.sqrt(nautical); + const coastDrag = c.freerideDrag ?? 0.55; + const brakeDrag = c.freerideBrakeDrag ?? 2.4; + + if (iz < 0) { + // forward thrust + this._vel.x += fwdX * accel * dt; + this._vel.z += fwdZ * accel * dt; + } else if (iz > 0) { + // reverse + stronger drag while braking + this._vel.x -= fwdX * accel * 0.45 * dt; + this._vel.z -= fwdZ * accel * 0.45 * dt; + const spB = Math.hypot(this._vel.x, this._vel.z); + if (spB > 1e-4) { + const d = Math.min(spB, brakeDrag * dt); + this._vel.x -= (this._vel.x / spB) * d; + this._vel.z -= (this._vel.z / spB) * d; + } + } else { + // water coast — keep glide when player releases W + const sp = Math.hypot(this._vel.x, this._vel.z); + if (sp > 1e-4) { + const d = Math.min(sp, coastDrag * dt); + this._vel.x -= (this._vel.x / sp) * d; + this._vel.z -= (this._vel.z / sp) * d; + } + } - if (this.distance >= this.length - 1e-4) this._startDismount(); + // Clamp speed + let sp = Math.hypot(this._vel.x, this._vel.z); + if (sp > maxSp) { + this._vel.x = (this._vel.x / sp) * maxSp; + this._vel.z = (this._vel.z / sp) * maxSp; + sp = maxSp; + } + this.speed = sp; + + // Space jump (edge) — hop off wave + const jumpDown = !!(keys && keys.has('Space')); + if (jumpDown && !this._wasJump && Math.abs(this._vy) < 0.05) { + this._vy = c.freerideJumpVy ?? 5.8; + this.character.playJump?.(0.06); + } + this._wasJump = jumpDown; + + // Integrate XZ + this._anchor.x += this._vel.x * dt; + this._anchor.z += this._vel.z * dt; + + // Wave follow + hop: group Y is water surface; deckHeight is on boardRoot local + const waterY = this._sampleWaterY(this._anchor.x, this._anchor.z); + const surface = waterY * (c.freerideWaveFollow ?? 0.85); + this._vy -= (c.freerideGravity ?? 14) * dt; + let y = (Number.isFinite(this._anchor.y) ? this._anchor.y : surface) + this._vy * dt; + if (y <= surface) { + y = surface; + this._vy = 0; + } + this._anchor.y = y; + + // Soft-body ragdoll-lite: hip drop + lean from bank + wave slope + if (c.softBody !== false) { + const waveDelta = + this._sampleWaterY(this._anchor.x + fwdX, this._anchor.z + fwdZ) - + this._sampleWaterY(this._anchor.x - fwdX, this._anchor.z - fwdZ); + const softHip = (c.softBodyHip ?? 0.06) * MathUtils.clamp(Math.abs(waveDelta) * 8, 0, 1); + this._softHip = softHip; + const rate = Math.max(0.05, c.leanRate); + const target = + -clamp(this._turnRate / rate, -1, 1) * c.lean * MathUtils.DEG2RAD * 0.45 + + waveDelta * (c.softBodyLean ?? 0.12); + this._lean = damp(this._lean, target, c.leanDamping, dt); + } + + if (!this._mounted) this.character.setLean(this._lean); } /* ------------------------------------------------------------------ */ @@ -305,11 +849,25 @@ export class WalkController { /* ------------------------------------------------------------------ */ _startDismount() { - this.phase = Phase.DISMOUNT; + this._setPhase(Phase.DISMOUNT); this._dismountTime = 0; - this._exit.copy(this.character.position); - this.scooter.release(); + + // Capture world exit before unparent + this.character.root.updateWorldMatrix(true, false); + this.character.root.getWorldPosition(this._exit); + this._exit.y = Math.max(0, this._exit.y); + + // Unparent first so character is free in scene + this._dismountRider(false); + this.character.setRideActive?.(false); + this.character.setRideParented?.(false); + + // Vehicle leaves scene (fade then retire; cancel if already gone) + if (this.scooter.active) this.scooter.release(); + else this.scooter.cancel(); + this.character.setPose('idle', settings.walk.poseBlend); + this.character.clearFlip?.(); } _updateDismount(dt) { @@ -318,21 +876,39 @@ export class WalkController { const t = saturate(this._dismountTime / Math.max(0.05, c.dismountTime)); const e = Easing.outCubic(t); - // He steps off forward as the ball drops away under him. + // Guarantee not parented mid-step + if (this.character.root.parent !== this.ctx.scene) { + this.ctx.scene.attach(this.character.root); + this.character.setRideParented?.(false); + } + _t.set(Math.sin(this._yaw), 0, Math.cos(this._yaw)); - _p.copy(this._exit).addScaledVector(_t, e * 0.45); - _p.y = MathUtils.lerp(this._exit.y, 0, e); + _p.copy(this._exit).addScaledVector(_t, e * 0.55); + _p.y = MathUtils.lerp(Math.max(0, this._exit.y), 0, e); this.character.root.position.copy(_p); + this.character.root.rotation.set(0, this._yaw, 0); + this.character.root.scale.set(1, 1, 1); this._lean = damp(this._lean, 0, 0.0005, dt); this.character.setLean(this._lean); + this._syncPhysicsToCharacter(); + + // Board fade anchor (vehicle no longer carries player) + this._anchor.set(this._exit.x, this._exit.y, this._exit.z); if (t < 1) return; - this.character.resetPlacement(); + // Land normal: vehicle must be gone; controller fully land + this.scooter.cancel(); + this.character.restoreFromRide?.({ x: _p.x, y: 0, z: _p.z, yaw: this._yaw }); + this.character.setFacing(this._yaw); + this._mounted = false; + this._vel.set(0, 0, 0); + this._vy = 0; + this._softHip = 0; + this._syncPhysicsToCharacter(); this._land(0.7); - // Optionally hop back to where the whole thing started. if (settings.walk.returnHome && this._home.distanceTo(this.character.position) > 0.5) { this._from.copy(this.character.position); this._target.copy(this._home).setY(0); @@ -341,25 +917,20 @@ export class WalkController { return; } - this.phase = Phase.IDLE; + this._setPhase(Phase.IDLE); this.curve = null; } - /* ------------------------------------------------------------------ */ - - /** - * Swing the heading toward `target`. - * @returns {number} the angular velocity actually used, radians/second - */ _turnTo(target, dt, rate) { const delta = wrapAngle(target - this._yaw); const step = delta * (1 - Math.pow(rate, dt)); this._yaw += step; - this.character.setFacing(this._yaw); + if (!this._mounted) this.character.setFacing(this._yaw); return step / Math.max(dt, 1e-4); } dispose() { + this._dismountRider(true); this.scooter.dispose(); this.ctx.scene.remove(this.scooter.group); } diff --git a/src/animation/bakeClip.js b/src/animation/bakeClip.js new file mode 100644 index 0000000..f7890dc --- /dev/null +++ b/src/animation/bakeClip.js @@ -0,0 +1,221 @@ +import { AnimationClip, PropertyBinding } from 'three'; + +/** + * Baked-clip helpers for grudge6 Bip001 JSON packs. + * Fleet rules: rotation-only, bone-name rematch (space/underscore), no Mixamo. + * Port of gameopen grudge/skeleton.ts rematch + normalize. + */ + +/** Strip hip/root position & scale tracks so a grounded kit does not float. */ +export function toRotationOnlyClip(clip) { + const tracks = clip.tracks.filter((t) => !/\.position$|\.scale$/.test(t.name)); + const out = new AnimationClip(clip.name, clip.duration, tracks); + out.uuid = clip.uuid; + return out; +} + +/** Alnum-only bone key: "Bip001 L UpperArm" / "Bip001_L_UpperArm" → bip001lupperarm */ +export function normalizeBoneKey(name) { + return String(name || '') + .trim() + .toLowerCase() + .replace(/^mixamorig\d*:/i, '') + .replace(/[^a-z0-9]/g, ''); +} + +/** + * Alias map: exact name + normalized key + space/underscore variants. + * HARD: only real Bone nodes. Never SkinnedMesh (WK_Units_head_* would steal Head tracks → head at feet). + */ +export function buildBoneNameLookup(root) { + const lookup = new Map(); + const actualByKey = new Map(); + + root.traverse((node) => { + // Bones only — containers are Object3D and must not receive bone quaternion tracks + if (node.isBone !== true) return; + const name = node.name || ''; + if (!name) return; + // Never treat mesh-like names as bones (defense if mis-typed) + if (/units_|weapon_|xtra_|shield_/i.test(name)) return; + + lookup.set(name, name); + const key = normalizeBoneKey(name); + lookup.set(key, name); + actualByKey.set(key, name); + + if (name.includes('_')) { + const spaced = name.replace(/^Bip001_/, 'Bip001 ').replace(/_/g, ' '); + lookup.set(spaced, name); + lookup.set(normalizeBoneKey(spaced), name); + } + if (name.includes(' ')) { + const underscored = name.replace(/ /g, '_'); + lookup.set(underscored, name); + lookup.set(normalizeBoneKey(underscored), name); + } + }); + + // Role aliases (clip may say Hips / LeftArm when kit is Bip001) + // Mixamo short names → Bip001 (combo bakes from Documents Mixamo FBX) + const aliases = [ + ['bip001pelvis', 'hips'], + ['bip001lupperarm', 'leftarm'], + ['bip001rupperarm', 'rightarm'], + ['bip001lforearm', 'leftforearm'], + ['bip001rforearm', 'rightforearm'], + ['bip001lhand', 'lefthand'], + ['bip001rhand', 'righthand'], + ['bip001lthigh', 'leftupleg'], + ['bip001rthigh', 'rightupleg'], + ['bip001lcalf', 'leftleg'], + ['bip001rcalf', 'rightleg'], + ['bip001lfoot', 'leftfoot'], + ['bip001rfoot', 'rightfoot'], + ['bip001lclavicle', 'leftshoulder'], + ['bip001rclavicle', 'rightshoulder'], + // Spine / head (Mixamo Spine / Spine01 / Spine02 / neck) + ['bip001spine', 'spine'], + ['bip001spine1', 'spine01'], + ['bip001spine1', 'spine1'], + ['bip001spine2', 'spine02'], + ['bip001spine2', 'spine2'], + ['bip001neck', 'neck'], + ['bip001head', 'head'] + ]; + for (const [a, b] of aliases) { + const boneA = actualByKey.get(a); + const boneB = actualByKey.get(b); + if (boneA) lookup.set(b, boneA); + if (boneB) lookup.set(a, boneB); + } + // Prefer mapping Mixamo Spine02 → highest available Bip001 spine + const spine2 = actualByKey.get('bip001spine2') || actualByKey.get('bip001spine1') || actualByKey.get('bip001spine'); + if (spine2) { + lookup.set('spine02', spine2); + lookup.set('spine2', spine2); + // Ghost Rider / Toon: clip may author Spine2 when kit only has Spine1/Spine + lookup.set('bip001spine2', spine2); + } + const spine1 = actualByKey.get('bip001spine1') || actualByKey.get('bip001spine'); + if (spine1) { + lookup.set('spine01', spine1); + lookup.set('spine1', spine1); + if (!actualByKey.get('bip001spine1')) lookup.set('bip001spine1', spine1); + } + // Toe0 often missing on grudge6 kits — fold into Foot to avoid dropped foot pose + const lFoot = actualByKey.get('bip001lfoot'); + const rFoot = actualByKey.get('bip001rfoot'); + if (lFoot) { + lookup.set('bip001ltoe0', lFoot); + lookup.set('bip001ltoe', lFoot); + lookup.set('lefttoeBase', lFoot); + lookup.set('lefttoe', lFoot); + } + if (rFoot) { + lookup.set('bip001rtoe0', rFoot); + lookup.set('bip001rtoe', rFoot); + lookup.set('righttoeBase', rFoot); + lookup.set('righttoe', rFoot); + } + + return lookup; +} + +/** + * Make quaternion keyframes continuous (no 180° flips) — reduces limb “pop” on rolls. + * @param {import('three').KeyframeTrack} track + */ +export function ensureQuaternionContinuity(track) { + if (!track || !/\.quaternion$/.test(track.name)) return track; + const v = track.values; + const n = Math.floor(v.length / 4); + for (let i = 1; i < n; i++) { + const o = (i - 1) * 4; + const c = i * 4; + const dot = v[o] * v[c] + v[o + 1] * v[c + 1] + v[o + 2] * v[c + 2] + v[o + 3] * v[c + 3]; + if (dot < 0) { + v[c] = -v[c]; + v[c + 1] = -v[c + 1]; + v[c + 2] = -v[c + 2]; + v[c + 3] = -v[c + 3]; + } + } + return track; +} + +/** + * Rewrite track node names so they bind to bones present on `root`. + * Drops position/scale by default (grounded kits). + */ +export function rematchClipToSkeleton(root, clip, { stripPositions = true } = {}) { + const lookup = buildBoneNameLookup(root); + const tracks = []; + let rewritten = 0; + let dropped = 0; + + for (const track of clip.tracks) { + if (stripPositions && /\.position$|\.scale$/.test(track.name)) continue; + + const parsed = PropertyBinding.parseTrackName(track.name); + const nodeName = parsed.nodeName; + if (!nodeName) { + tracks.push(track); + continue; + } + + // Never bind anim tracks to equip meshes (Units_head / Body / weapon) + if (/units_|weapon_|xtra_|shield_/i.test(nodeName)) { + dropped++; + continue; + } + // Drop Spine1/2 etc. when kit only has Bip001 Spine (common Toon RTS) + // Prefer lookup; do NOT use PropertyBinding.findNode (can hit non-bones) + + let resolved = + lookup.get(nodeName) || lookup.get(normalizeBoneKey(nodeName)) || null; + + // Exact bone only via findNode if it is really a Bone + if (!resolved) { + const found = PropertyBinding.findNode(root, nodeName); + if (found && found.isBone === true && !/units_|weapon_|xtra_/i.test(found.name || '')) { + resolved = found.name; + } + } + + if (!resolved) { + dropped++; + continue; + } + + const dot = track.name.indexOf('.'); + const propSuffix = dot >= 0 ? track.name.slice(dot) : `.${parsed.propertyName || 'quaternion'}`; + const Ctor = track.constructor; + const times = track.times.slice ? track.times.slice() : Array.from(track.times); + const values = track.values.slice ? track.values.slice() : Array.from(track.values); + const nextName = resolved === nodeName ? track.name : `${resolved}${propSuffix}`; + if (resolved !== nodeName) rewritten++; + const nt = new Ctor(nextName, times, values); + if (/\.quaternion$/.test(nextName)) ensureQuaternionContinuity(nt); + tracks.push(nt); + } + + if (rewritten || dropped) { + console.info( + `[bakeClip] rematch "${clip.name}": keep=${tracks.length} rewrote=${rewritten} dropped=${dropped}` + ); + } + + return new AnimationClip(clip.name, clip.duration, tracks); +} + +/** Fetch a fleet-baked JSON clip and return a rotation-only AnimationClip. */ +export async function loadBakedClipJson(url) { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) throw new Error(`[bakeClip] HTTP ${res.status} for ${url}`); + const ct = res.headers.get('content-type') || ''; + if (ct.includes('text/html')) throw new Error(`[bakeClip] HTML response for ${url}`); + const json = await res.json(); + const clip = AnimationClip.parse(json); + return toRotationOnlyClip(clip); +} diff --git a/src/animation/fbxClip.js b/src/animation/fbxClip.js new file mode 100644 index 0000000..f5be66d --- /dev/null +++ b/src/animation/fbxClip.js @@ -0,0 +1,64 @@ +/** + * Load real FBX one-shot clips (flips, etc.) from Open CDN and rematch to Bip001. + * Prefer baked JSON when available; FBX is the author source for striker/extra flips. + */ +import { FBXLoader } from 'three/addons/loaders/FBXLoader.js'; +import { rematchClipToSkeleton, toRotationOnlyClip } from './bakeClip.js'; + +let _loader = null; +function getLoader() { + if (!_loader) _loader = new FBXLoader(); + return _loader; +} + +/** + * @param {string} url absolute FBX url + * @param {import('three').Object3D} skeletonRoot grudge6 kit + * @param {string} [name] clip name + * @returns {Promise} + */ +export async function loadFbxClipRematched(url, skeletonRoot, name = 'clip') { + if (!url || !skeletonRoot) return null; + try { + const fbx = await getLoader().loadAsync(url); + const raw = fbx.animations?.[0]; + if (!raw) return null; + raw.name = name; + // Rotation-only + bone rematch (Mixamo/striker → Bip001) + const rot = toRotationOnlyClip(raw); + const matched = rematchClipToSkeleton(skeletonRoot, rot, { stripPositions: true }); + if (!matched.tracks.length) { + console.warn('[fbxClip] empty after rematch', url); + return null; + } + matched.name = name; + return matched; + } catch (e) { + console.warn('[fbxClip] load fail', url, e?.message || e); + return null; + } +} + +/** Open host absolute paths for production flip clips (real author FBX). */ +export const FLIP_FBX_URLS = Object.freeze({ + frontflip: [ + 'https://open.grudge-studio.com/anim/animations/extra/front-flip.fbx', + 'https://open.grudge-studio.com/anim/extra/front-flip.fbx' + ], + backflip: [ + 'https://open.grudge-studio.com/anim/striker/backflip.fbx', + 'https://open.grudge-studio.com/anim/animations/striker/Back_Flip_To_Uppercut.fbx' + ] +}); + +/** + * Jump / fall locomotion FBX (author on D:\Games\Models · lab under public/anim/locomotion/fall). + * @see config/fallAnimSsot.js + */ +export { FALL_FBX_URLS } from '../config/fallAnimSsot.js'; + +/** + * 2H melee idle FBX (author D:\Games\Models\2hand Idle.fbx → public/anim/melee/). + * @see config/meleeAnimSsot.js + */ +export { MELEE_FBX_URLS, MELEE_2H_IDLE_FBX_URLS } from '../config/meleeAnimSsot.js'; diff --git a/src/animation/pistolReloadPose.js b/src/animation/pistolReloadPose.js new file mode 100644 index 0000000..f31bc6d --- /dev/null +++ b/src/animation/pistolReloadPose.js @@ -0,0 +1,296 @@ +/** + * Procedural flintlock reload pose — post-mixer bone/weapon overlay. + * + * Not a second AnimationMixer. Layers on R_hand WeaponAttach + L_hand: + * 1) Rotate gun toward body midline (chest) + * 2) Off-hand reaches to barrel mouth + * 3) Tilt barrel up slightly (powder / power-shot refill feel) + * + * @see config/pistolAnimSsot.js · FLINTLOCK_RELOAD + */ + +import { MathUtils, Quaternion, Vector3 } from 'three'; +import { FLINTLOCK_RELOAD } from '../config/pistolAnimSsot.js'; + +const _chest = new Vector3(); +const _handW = new Vector3(); +const _muzzleW = new Vector3(); +const _toMid = new Vector3(); +const _fwd = new Vector3(); +const _right = new Vector3(); +const _qa = new Quaternion(); +const _qb = new Quaternion(); +const _qc = new Quaternion(); +const _bindAttachQ = new Quaternion(); +const _bindLHandQ = new Quaternion(); +const _tmp = new Vector3(); +const _up = new Vector3(0, 1, 0); + +/** + * @typedef {object} ReloadOpts + * @property {number} [durationSec] + * @property {number} [barrelTiltDeg] muzzle up during pour + * @property {number} [gunInWeight] how hard gun pulls to chest (0..1) + * @property {boolean} [power] longer power-shot refill + */ + +export class PistolReloadPose { + constructor() { + this.active = false; + this.t = 0; + this.duration = FLINTLOCK_RELOAD.durationSec; + this.barrelTiltDeg = FLINTLOCK_RELOAD.barrelTiltDeg; + this.gunInWeight = FLINTLOCK_RELOAD.gunInWeight; + this.power = false; + /** @type {import('./CharacterController.js').CharacterController|null} */ + this.character = null; + this._hasBind = false; + } + + /** + * @param {import('./CharacterController.js').CharacterController} character + * @param {ReloadOpts} [opts] + */ + start(character, opts = {}) { + if (!character) return false; + this.character = character; + this.power = !!opts.power; + this.duration = Number( + opts.durationSec ?? + (this.power ? FLINTLOCK_RELOAD.powerDurationSec : FLINTLOCK_RELOAD.durationSec) + ); + this.barrelTiltDeg = Number(opts.barrelTiltDeg ?? FLINTLOCK_RELOAD.barrelTiltDeg); + this.gunInWeight = Number(opts.gunInWeight ?? FLINTLOCK_RELOAD.gunInWeight); + this.t = 0; + this.active = true; + + const attach = character.weaponAttach || findWeaponAttach(character); + const lHand = character.bones?.lHand; + if (attach) { + _bindAttachQ.copy(attach.quaternion); + attach.userData._reloadBindQ = _bindAttachQ.clone(); + } + if (lHand) { + _bindLHandQ.copy(lHand.quaternion); + lHand.userData._reloadBindQ = _bindLHandQ.clone(); + } + this._hasBind = !!(attach || lHand); + character._gaitLocked = true; + character._oneShotTimer = Math.max(character._oneShotTimer || 0, this.duration * 0.95); + character.animState = 'reload'; + // Prefer draw/cast clip under reload for arm motion while procedural layers gun + character.requestOneShot?.('reload') || + character.requestOneShot?.('draw') || + character.requestOneShot?.('cast') || + character.playLibraryClip?.('draw'); + return true; + } + + stop() { + if (!this.active) return; + this._restoreBind(); + this.active = false; + this.t = 0; + if (this.character) { + this.character._gaitLocked = false; + if (this.character.animState === 'reload') this.character.animState = 'idle'; + } + } + + _restoreBind() { + const ch = this.character; + if (!ch) return; + const attach = ch.weaponAttach || findWeaponAttach(ch); + const lHand = ch.bones?.lHand; + if (attach?.userData?._reloadBindQ) { + attach.quaternion.copy(attach.userData._reloadBindQ); + } + if (lHand?.userData?._reloadBindQ) { + lHand.quaternion.copy(lHand.userData._reloadBindQ); + } + } + + /** + * Post-mixer. Call after AnimationMixer.update. + * @param {number} dt + */ + update(dt) { + if (!this.active || !this.character) return; + this.t += dt; + const u = MathUtils.clamp(this.t / Math.max(0.05, this.duration), 0, 1); + + // Envelope: ease in → hold pour → ease out + let w; + if (u < 0.22) w = easeOutCubic(u / 0.22); + else if (u < 0.62) w = 1; + else w = 1 - easeInCubic((u - 0.62) / 0.38); + + const ch = this.character; + const attach = ch.weaponAttach || findWeaponAttach(ch); + const rHand = ch.bones?.rHand; + const lHand = ch.bones?.lHand; + const root = ch.root || ch.model; + + // Chest midline world target + if (root) { + root.getWorldPosition(_chest); + _chest.y += (ch.height || 1.8) * 0.58; + // Slightly in front of torso + const yaw = ch.facing || 0; + _chest.x += Math.sin(yaw) * 0.12; + _chest.z += Math.cos(yaw) * 0.12; + } + + // ── Gun: rotate toward body middle + barrel tilt up ────────────── + if (attach && rHand) { + const bindQ = attach.userData._reloadBindQ || attach.quaternion; + attach.getWorldPosition(_handW); + + // Direction from grip to chest midline (bring gun in) + _toMid.copy(_chest).sub(_handW); + if (_toMid.lengthSq() > 1e-8) { + _toMid.normalize(); + // Prefer current attach "up/long" as barrel axis proxy + const muzzle = attach.userData?.muzzle; + if (muzzle) { + muzzle.getWorldPosition(_muzzleW); + _fwd.copy(_muzzleW).sub(_handW); + if (_fwd.lengthSq() < 1e-8) _fwd.set(0, 0, 1); + else _fwd.normalize(); + } else { + _fwd.set(0, 1, 0).applyQuaternion(attach.getWorldQuaternion(_qa)); + if (_fwd.lengthSq() < 1e-8) _fwd.set(0, 0, 1); + else _fwd.normalize(); + } + + // Blend barrel direction toward chest for "gun into body middle" + _tmp.copy(_fwd).lerp(_toMid, this.gunInWeight * w); + if (_tmp.lengthSq() > 1e-8) _tmp.normalize(); + _qb.setFromUnitVectors(_fwd, _tmp); + + // Barrel tilt up (powder pour) — pitch around hand local right + const tilt = MathUtils.degToRad(this.barrelTiltDeg) * w; + rHand.getWorldQuaternion(_qa); + _right.set(1, 0, 0).applyQuaternion(_qa); + if (_right.lengthSq() < 1e-8) _right.set(1, 0, 0); + else _right.normalize(); + // Prefer axis perpendicular to barrel × up so muzzle lifts + _right.crossVectors(_tmp, _up); + if (_right.lengthSq() < 1e-8) _right.set(1, 0, 0); + else _right.normalize(); + _qc.setFromAxisAngle(_right, tilt); + + attach.getWorldQuaternion(_qa); + _qa.premultiply(_qb).premultiply(_qc); + if (attach.parent) { + attach.parent.getWorldQuaternion(_qb).invert(); + _qa.premultiply(_qb); + } + // Blend from bind + attach.quaternion.copy(bindQ).slerp(_qa, Math.min(1, w * 0.92)); + } + } + + // ── Off-hand: move toward barrel muzzle ────────────────────────── + if (lHand && attach) { + const bindL = lHand.userData._reloadBindQ || lHand.quaternion; + const muzzle = attach.userData?.muzzle || attach; + muzzle.getWorldPosition(_muzzleW); + // Reach slightly short of muzzle (fingers at pan / barrel mouth) + _muzzleW.y += 0.02; + lHand.getWorldPosition(_handW); + _toMid.copy(_muzzleW).sub(_handW); + if (_toMid.lengthSq() > 1e-8) { + _toMid.normalize(); + const child = lHand.children.find((c) => c.isBone) || lHand.children[0]; + if (child) { + child.getWorldPosition(_fwd).sub(_handW); + if (_fwd.lengthSq() < 1e-8) _fwd.set(0, 0, 1); + else _fwd.normalize(); + } else { + _fwd.set(0, 0, 1).applyQuaternion(lHand.getWorldQuaternion(_qa)); + } + _qb.setFromUnitVectors(_fwd, _toMid); + lHand.getWorldQuaternion(_qa); + _qa.premultiply(_qb); + if (lHand.parent) { + lHand.parent.getWorldQuaternion(_qb).invert(); + _qa.premultiply(_qb); + } + lHand.quaternion.copy(bindL).slerp(_qa, Math.min(1, w * 0.85)); + } + } + + if (u >= 1) { + this._restoreBind(); + this.active = false; + this.t = 0; + if (ch.animState === 'reload') ch.animState = 'idle'; + ch._gaitLocked = false; + } + } +} + +function easeOutCubic(t) { + return 1 - Math.pow(1 - t, 3); +} +function easeInCubic(t) { + return t * t * t; +} + +/** + * @param {import('./CharacterController.js').CharacterController} character + * @returns {import('three').Object3D|null} + */ +export function findWeaponAttach(character) { + if (character?.weaponAttach) return character.weaponAttach; + const hand = character?.bones?.rHand; + if (!hand) return null; + for (const c of hand.children) { + if (c.userData?.weaponAttach || c.name === 'WeaponAttach') return c; + } + let found = null; + hand.traverse((o) => { + if (!found && (o.userData?.weaponAttach || o.name === 'WeaponAttach')) found = o; + }); + return found; +} + +/** + * World-space muzzle (barrel tip) from WeaponAttach marker or long-axis tip. + * @param {import('three').Object3D|null} attach + * @param {import('three').Vector3} [out] + */ +export function getMuzzleWorld(attach, out = new Vector3()) { + if (!attach) return out.set(0, 0, 0); + const m = attach.userData?.muzzle; + if (m) { + m.getWorldPosition(out); + return out; + } + attach.getWorldPosition(out); + return out; +} + +/** + * Barrel forward unit vector (grip → muzzle). + * @param {import('three').Object3D|null} attach + * @param {import('three').Vector3} [out] + */ +export function getBarrelForward(attach, out = new Vector3()) { + if (!attach) return out.set(0, 0, 1); + const m = attach.userData?.muzzle; + if (m) { + attach.getWorldPosition(_handW); + m.getWorldPosition(_muzzleW); + out.copy(_muzzleW).sub(_handW); + if (out.lengthSq() < 1e-8) out.set(0, 0, 1); + else out.normalize(); + return out; + } + out.set(0, 0, 1).applyQuaternion(attach.getWorldQuaternion(_qa)); + if (out.lengthSq() < 1e-8) out.set(0, 0, 1); + else out.normalize(); + return out; +} + diff --git a/src/api/deployableContract.js b/src/api/deployableContract.js new file mode 100644 index 0000000..a6cbe1d --- /dev/null +++ b/src/api/deployableContract.js @@ -0,0 +1,444 @@ +/** + * Deployable prefab contract — everything we can author / save / deploy. + * + * Extends weaponPrefabContract pattern to: + * weapons · buildables (scripts + purpose) · enemy Grudge characters · assets + * + * Never mint ITEM-/SKIL-/NPC- UUIDs here. Drafts are local until ObjectStore pipelines. + * + * @see docs/ADMIN_HUB_F1_F5_SSOT.md + * @see docs/WEAPON_PREFAB_UUID_SSOT.md + * @see docs/PREFAB_SCAFFOLD_CONTROL_SSOT.md + */ + +import { GRUDGE_UUID_PREFIX, isGrudgeUuid, WEAPON_PREFAB_REQUIRED_LAYERS } from './weaponPrefabContract.js'; +import { PREFAB_CATEGORIES, INFO_API, INFO_MIRROR } from './gameItemCatalog.js'; +import { CDN } from '../loot/prefabAssets.js'; + +/** + * Admin tab map (SSOT). + * F1–F4 · ] World (not F5) · Esc close · ? help · ` auto run/sail + */ +export const ADMIN_TABS = Object.freeze([ + { + id: 'player', + key: 'F1', + label: 'Player', + blurb: 'Hero session · race · equip · controller · fleet identity' + }, + { + id: 'assets', + key: 'F2', + label: 'Assets', + blurb: 'Buildables · harvest · vehicles · props · scripts + purpose' + }, + { + id: 'creatures', + key: 'F3', + label: 'Creatures', + blurb: 'Enemy · ally · boss · NPC · Grudge race kits · AI brain' + }, + { + id: 'prefabs', + key: 'F4', + label: 'Prefabs', + blurb: 'Weapons · armour · tools · scaffold · craft · export' + }, + { + id: 'world', + key: ']', + keyCode: 'BracketRight', + label: 'World', + blurb: 'Stage · water · loot · SI map · placement notes' + } +]); + +/** + * Deployable kinds the lab can create / save drafts for. + * @type {readonly { id: string, label: string, uuidPrefix: string, adminTab: string, authority: string, layers: string[], jobs: string[] }[]} + */ +export const DEPLOYABLE_KINDS = Object.freeze([ + { + id: 'weapon', + label: 'Weapon', + uuidPrefix: 'ITEM-', + adminTab: 'prefabs', + authority: 'master-weapon-prefabs.json · t0-weapons.json', + layers: [...WEAPON_PREFAB_REQUIRED_LAYERS], + jobs: ['bag', 'equip', 'controller', 'hotbar', 'combat', 'craft', 'export'] + }, + { + id: 'armour', + label: 'Armour', + uuidPrefix: 'ITEM-', + adminTab: 'prefabs', + authority: 'master-armor.json', + layers: ['identity', 'stats', 'assets', 'runtime', 'slots'], + jobs: ['bag', 'equip_mesh_ids', 'stats', 'export'] + }, + { + id: 'buildable', + label: 'Buildable', + uuidPrefix: 'PREFAB-', + adminTab: 'assets', + authority: 'locations / kenney-build / ObjectStore buildables', + layers: ['identity', 'purpose', 'script', 'assets', 'placement', 'runtime'], + jobs: ['place', 'snap', 'interact', 'harvest_or_use', 'save_instance', 'export'] + }, + { + id: 'harvestable', + label: 'Harvestable', + uuidPrefix: 'PREFAB-', + adminTab: 'assets', + authority: 'Mine-Loader harvest + nature props CDN', + layers: ['identity', 'purpose', 'script', 'assets', 'drops', 'runtime'], + jobs: ['spawn', 'interact_E', 'drop_loot', 'export'] + }, + { + id: 'map_layout', + label: 'Map layout (Training Room)', + uuidPrefix: 'MAP-', + adminTab: 'world', + authority: + 'trainingRoomMap + trainingRoomDeploy · ObjectStore maps/training_room · R2 lab/casting/training-room', + layers: ['identity', 'terrain', 'nodes', 'physics', 'publish'], + jobs: ['author_devnode', 'export_layout', 'promote_r2_d1', 'play_boot', 'forge_handoff'] + }, + { + id: 'vehicle', + label: 'Vehicle / ship', + uuidPrefix: 'PREFAB-', + adminTab: 'assets', + authority: 'Kenney vehicles · fleet ride', + layers: ['identity', 'purpose', 'script', 'assets', 'seats', 'runtime'], + jobs: ['spawn', 'mount', 'drive', 'export'] + }, + { + id: 'enemy', + label: 'Enemy / monster', + uuidPrefix: 'NPC-', + adminTab: 'creatures', + authority: 'grudge6 kits + training dummy + boss pins', + layers: ['identity', 'kit', 'anims', 'ai', 'combat', 'assets', 'runtime'], + jobs: ['spawn', 'aggro', 'anim_pack', 'skills', 'loot', 'export'] + }, + { + id: 'ally', + label: 'Ally / commander', + uuidPrefix: 'NPC-', + adminTab: 'creatures', + authority: 'grudge6 · party units', + layers: ['identity', 'kit', 'anims', 'ai', 'combat', 'assets', 'runtime'], + jobs: ['spawn', 'follow', 'command', 'export'] + }, + { + id: 'npc', + label: 'NPC / vendor', + uuidPrefix: 'NPC-', + adminTab: 'creatures', + authority: 'vendor catalogs · dialogue hooks', + layers: ['identity', 'kit', 'role', 'assets', 'runtime'], + jobs: ['spawn', 'interact', 'shop', 'export'] + }, + { + id: 'character_kit', + label: 'Grudge character kit', + uuidPrefix: 'KIT-', + adminTab: 'creatures', + authority: 'grudge6 CDN · race GLB · mesh_ids', + layers: ['identity', 'race', 'mesh_ids', 'anims', 'assets', 'runtime'], + jobs: ['load_kit', 'equip', 'anim_pack', 'export'] + } +]); + +export const DEPLOYABLE_BY_ID = Object.freeze( + Object.fromEntries(DEPLOYABLE_KINDS.map((k) => [k.id, k])) +); + +/** Buildable purpose tags (what the prop does in-world). */ +export const BUILDABLE_PURPOSES = Object.freeze([ + { id: 'structure', label: 'Structure', note: 'Wall / floor / roof snap piece' }, + { id: 'camp', label: 'Camp', note: 'Claim / storage / fire' }, + { id: 'crafting', label: 'Crafting station', note: 'Workbench · forge · cook' }, + { id: 'storage', label: 'Storage', note: 'Chest · depot · bag deposit' }, + { id: 'defense', label: 'Defense', note: 'Tower · spike · gate' }, + { id: 'decoration', label: 'Decoration', note: 'Prop only' }, + { id: 'resource_node', label: 'Resource node', note: 'Tree · rock · ore (harvest)' }, + { id: 'spawn_pad', label: 'Spawn pad', note: 'Unit / vehicle spawn' }, + { id: 'transport', label: 'Transport', note: 'Dock · pad · teleporter' } +]); + +/** Enemy / creature roles for F3 deploy catalog. */ +export const CREATURE_ROLES = Object.freeze([ + { id: 'enemy', label: 'Enemy' }, + { id: 'monster', label: 'Monster' }, + { id: 'boss', label: 'Boss' }, + { id: 'ally', label: 'Ally' }, + { id: 'commander', label: 'Commander' }, + { id: 'npc', label: 'NPC' }, + { id: 'vendor', label: 'Vendor' }, + { id: 'training_dummy', label: 'Training dummy' } +]); + +/** AI brain stubs (wire to Yuka / fleet AI later — names only). */ +export const AI_BRAINS = Object.freeze([ + { id: 'passive', label: 'Passive', note: 'No aggro' }, + { id: 'guard', label: 'Guard', note: 'Aggro in radius' }, + { id: 'patrol', label: 'Patrol', note: 'Path + return' }, + { id: 'hunter', label: 'Hunter', note: 'Chase + attack' }, + { id: 'boss_phase', label: 'Boss phases', note: 'Telegraph → skill cycles' }, + { id: 'ally_follow', label: 'Ally follow', note: 'Follow player + assist' }, + { id: 'vendor_idle', label: 'Vendor idle', note: 'Stand + interact shop' } +]); + +export const DEPLOYABLE_ENDPOINTS = Object.freeze({ + weapons: `${INFO_API}/master-weapon-prefabs.json`, + t0Weapons: `${INFO_API}/t0-weapons.json`, + armor: `${INFO_API}/master-armor.json`, + mounts: `${INFO_API}/master-mounts.json`, + registry: `${INFO_API}/master-registry.json`, + grudge6: 'https://assets.grudge-studio.com/asset-packs/toon-rts-characters/glb/characters/', + cdn: CDN, + info: INFO_API, + mirror: INFO_MIRROR, + itemBrowser: 'https://info.grudge-studio.com/GRUDGE_Item_Database.html', + pipelines: { + weapons: 'npm run build:weapon-pipeline', + master: 'npm run generate:master && npm run consolidate:game-data', + assets: 'grudge-asset-convert → R2 → D1 index' + } +}); + +/** + * Create a local draft deployable (no UUID mint). + * @param {string} kindId + * @param {Partial} [fields] + */ +export function createDeployableDraft(kindId, fields = {}) { + const kind = DEPLOYABLE_BY_ID[kindId] || DEPLOYABLE_BY_ID.weapon; + const slug = + fields.slug || + String(fields.name || 'untitled') + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/^-|-$/g, '') || + 'untitled'; + const now = new Date().toISOString(); + const base = { + _schema: 'grudge.deployable.draft.v1', + _local: true, + _savedAt: null, + _createdAt: now, + kind: kind.id, + kindLabel: kind.label, + adminTab: kind.adminTab, + id: fields.id || `draft-${kind.id}-${slug}`, + /** Placeholder only — ObjectStore assigns real uuid */ + uuid: fields.uuid || null, + name: fields.name || `New ${kind.label}`, + tier: fields.tier ?? 0, + description: fields.description || '', + layers: Object.fromEntries(kind.layers.map((L) => [L, null])), + jobs: kind.jobs, + authority: kind.authority, + assets: { + iconUrl: fields.iconUrl || null, + modelUrl: fields.modelUrl || null, + r2Key: fields.r2Key || null + }, + notes: fields.notes || '' + }; + + if (kind.id === 'weapon' || kind.id === 'armour') { + base.layers.identity = { + id: base.id, + uuid: null, + name: base.name, + tier: base.tier, + weaponType: fields.weaponType || 'SWORD', + slot: fields.slot || 'mainHand' + }; + base.layers.stats = fields.stats || { damage: 10, attackSpeed: 1, crit: 0.05 }; + base.layers.skills = fields.skills || { slots: [], skillUuids: [], note: 'Link SKIL-* via ObjectStore' }; + base.layers.assets = { ...base.assets }; + base.layers.runtime = { + meshSlot: fields.meshSlot || 'sword', + animPack: fields.animPack || 'sword_shield', + labStyle: fields.labStyle || 'starter_t0' + }; + base.layers.loadout = { pattern: base.tier === 0 ? 'three-slot-starter' : 'five-slot' }; + } + + if (kind.id === 'buildable' || kind.id === 'harvestable' || kind.id === 'vehicle') { + const purposeId = fields.purpose || (kind.id === 'harvestable' ? 'resource_node' : 'structure'); + const purpose = BUILDABLE_PURPOSES.find((p) => p.id === purposeId) || BUILDABLE_PURPOSES[0]; + base.layers.identity = { id: base.id, uuid: null, name: base.name, kind: kind.id }; + base.layers.purpose = { + id: purpose.id, + label: purpose.label, + note: purpose.note, + tags: fields.tags || [purpose.id] + }; + base.layers.script = buildBuildableScriptStub(base, purpose, fields); + base.layers.assets = { ...base.assets }; + base.layers.placement = { + snapGridM: fields.snapGridM ?? 1, + footprintM: fields.footprintM || [1, 1], + rotateY: fields.rotateY ?? true, + collision: fields.collision || 'box' + }; + base.layers.runtime = { + interactKey: 'E', + health: fields.health ?? 100, + team: fields.team || 'neutral' + }; + if (kind.id === 'harvestable') { + base.layers.drops = fields.drops || [{ materialId: 'wood', qty: 1 }]; + } + if (kind.id === 'vehicle') { + base.layers.seats = fields.seats || [{ id: 'driver', bone: null }]; + } + } + + if (kind.id === 'enemy' || kind.id === 'ally' || kind.id === 'npc' || kind.id === 'character_kit') { + const role = fields.role || (kind.id === 'ally' ? 'ally' : kind.id === 'npc' ? 'npc' : 'enemy'); + base.layers.identity = { + id: base.id, + uuid: null, + name: base.name, + role, + faction: fields.faction || (role === 'ally' ? 'player' : 'hostile') + }; + // raceId may be WK/ELF/… or short human/elf — CDN kits use short names + const raceId = fields.raceId || 'WK'; + const shortRace = + fields.shortRace || + ({ WK: 'human', ELF: 'elf', ORC: 'orc', DWF: 'dwarf', UD: 'undead', BRB: 'barbarian' }[raceId] || + String(raceId).toLowerCase()); + base.layers.kit = { + raceId, + shortRace, + kitUrl: fields.kitUrl || `${DEPLOYABLE_ENDPOINTS.grudge6}${shortRace}.glb`, + heightM: fields.heightM ?? (role === 'boss' ? 4.3 : 1.8), + mesh_ids: fields.mesh_ids || null + }; + base.layers.anims = { + pack: fields.animPack || 'sword_shield', + roles: fields.animRoles || ['idle', 'walk', 'run', 'attack', 'death'] + }; + base.layers.ai = { + brain: fields.brain || (role === 'ally' ? 'ally_follow' : role === 'vendor' ? 'vendor_idle' : 'guard'), + aggroRangeM: fields.aggroRangeM ?? 8, + attackRangeM: fields.attackRangeM ?? 1.6 + }; + base.layers.combat = { + hp: fields.hp ?? (role === 'boss' ? 900 : 150), + damage: fields.damage ?? 12, + skills: fields.skills || [] + }; + base.layers.assets = { ...base.assets, modelUrl: base.layers.kit.kitUrl }; + base.layers.runtime = { + targetable: true, + selectable: true, + siUnits: true + }; + if (kind.id === 'character_kit') { + base.layers.race = { id: fields.raceId || 'human' }; + base.layers.mesh_ids = fields.mesh_ids || {}; + } + } + + return base; +} + +/** + * Buildable / asset item script stub (purpose + hooks). + */ +export function buildBuildableScriptStub(draft, purpose, fields = {}) { + return { + kind: 'buildable_script', + language: 'javascript-module', + note: 'Draft only — fleet uses place/snap/interact systems, not eval', + module: { + id: draft.id, + type: draft.kind, + purpose: purpose?.id || fields.purpose || 'structure', + onPlace: 'snap to grid · register collider · save instance pos', + onInteract: purpose?.id === 'crafting' ? 'open craft UI' : purpose?.id === 'storage' ? 'open depot' : purpose?.id === 'resource_node' ? 'start harvest swing' : 'inspect / use', + onDestroy: 'despawn · optional scrap drops', + tick: fields.tick || null, + stats: fields.scriptStats || {}, + purposeNote: purpose?.note || '' + } + }; +} + +/** + * Validate a deployable draft for completeness (soft score). + * @param {object} draft + */ +export function validateDeployableDraft(draft) { + const kind = DEPLOYABLE_BY_ID[draft?.kind] || DEPLOYABLE_BY_ID.weapon; + const missing = []; + const warnings = []; + for (const L of kind.layers) { + if (draft.layers?.[L] == null || draft.layers[L] === '') missing.push(L); + } + if (!draft.name) missing.push('name'); + if (!draft.assets?.modelUrl && !draft.layers?.assets?.modelUrl && !draft.layers?.kit?.kitUrl) { + warnings.push('no modelUrl — generate 3D brief before deploy'); + } + if (draft.uuid && !isGrudgeUuid(draft.uuid)) { + warnings.push('uuid present but not a GRUDGE prefix'); + } + if (!draft.uuid) warnings.push('no uuid yet — ObjectStore pipeline mints on register'); + if (draft.kind === 'weapon' && !draft.layers?.skills?.skillUuids?.length) { + warnings.push('weapon has no SKIL-* yet — scaffold from T0 or master'); + } + const total = kind.layers.length + 1; + const ok = total - missing.length; + return { + ok: missing.length === 0, + score: `${ok}/${total}`, + missing, + warnings, + kind: kind.id, + jobs: kind.jobs + }; +} + +/** + * Export snapshot for download / ObjectStore handoff. + * @param {object} draft + */ +export function exportDeployableSnapshot(draft) { + const val = validateDeployableDraft(draft); + return { + schema: 'grudge.deployable.export.v1', + exportedAt: new Date().toISOString(), + draft: { + ...draft, + _local: undefined + }, + validation: val, + register: { + note: 'Do not mint UUIDs in lab. Push JSON to ObjectStore pipeline.', + pipelines: DEPLOYABLE_ENDPOINTS.pipelines, + next: draft.kind === 'weapon' || draft.kind === 'armour' + ? 'build:weapon-pipeline / generate:master' + : draft.adminTab === 'creatures' + ? 'register kit + AI row + D1 CDN paths' + : 'buildable catalog + R2 mesh + purpose script' + }, + categories: PREFAB_CATEGORIES, + uuidPrefixes: GRUDGE_UUID_PREFIX + }; +} + +/** + * Summarize kinds for a given admin tab. + * @param {string} tabId + */ +export function kindsForAdminTab(tabId) { + return DEPLOYABLE_KINDS.filter((k) => k.adminTab === tabId); +} diff --git a/src/api/fleetApi.js b/src/api/fleetApi.js new file mode 100644 index 0000000..2e1d3e2 --- /dev/null +++ b/src/api/fleetApi.js @@ -0,0 +1,524 @@ +/** + * Thin client for Grudge deployable game API (Railway Postgres SSOT). + * SSOT: grudge-production-wiring — one account, Railway characters/bag. + * + * Production casting host prefers **same-origin** `/api/*` (vercel.json rewrite → Railway) + * so the browser never needs CORS for player data. Absolute Railway URL is fallback + * (local vite without proxy, or VITE_FLEET_API override). + * + * Database: never connect from the SPA. Characters/bag live on Railway Postgres + * via grudge-api. D1 is asset index only (ObjectStore/info), not player SSOT. + */ + +/** Absolute Railway game API (player DB). */ +export const RAILWAY_API = + (typeof import.meta !== 'undefined' && import.meta.env?.VITE_RAILWAY_API) || + 'https://grudge-api-production-0d46.up.railway.app'; + +/** + * Resolve API base for browser: + * - VITE_FLEET_API if set ("" or "same-origin" → relative /api) + * - on casting / vercel.app hosts → same-origin (proxy) + * - else Railway absolute + */ +export function resolveFleetApiBase() { + const env = + typeof import.meta !== 'undefined' ? import.meta.env?.VITE_FLEET_API : undefined; + if (env === '' || env === 'same-origin' || env === '/') return ''; + if (typeof env === 'string' && env.trim()) return env.replace(/\/+$/, ''); + + if (typeof window !== 'undefined' && window.location?.hostname) { + const h = window.location.hostname; + // Control plane: casting.grudge.studio (primary) · legacy casting.grudge-studio.com + if ( + h === 'casting.grudge.studio' || + h.endsWith('.casting.grudge.studio') || + h === 'casting.grudge-studio.com' || + h.endsWith('.casting.grudge-studio.com') || + h.includes('casting-abilities-threejs') || + h === 'localhost' || + h === '127.0.0.1' + ) { + // localhost: vite has no /api proxy unless configured — use Railway + if (h === 'localhost' || h === '127.0.0.1') return RAILWAY_API; + return ''; + } + } + return RAILWAY_API; +} + +export const FLEET_API_DEFAULT = resolveFleetApiBase(); + +/** Same keys as GrudgeBuilder shared/fleet/authConnect.ts — do not invent a second store. */ +export const FLEET_AUTH_TOKEN_KEYS = [ + 'grudge.open.token', + 'grudge_auth_token', + 'grudge_session_token', + 'grudge.token', + 'sso_token', + 'grudge_token', + 'grudge_jwt', + 'grudgeIdToken', + 'grudge_id_token', + 'grudge.sessionToken', + 'token' +]; + +export function readFleetToken() { + try { + for (const k of FLEET_AUTH_TOKEN_KEYS) { + const v = localStorage.getItem(k) || sessionStorage.getItem(k); + if (v) return v; + } + } catch { + /* ignore */ + } + return null; +} + +export function storeFleetToken(token) { + if (!token) return; + try { + localStorage.setItem('grudge.open.token', token); + localStorage.setItem('grudge_token', token); + localStorage.setItem('sso_token', token); + } catch { + /* ignore */ + } +} + +/** Pull ?grudge_token= / #sso_token= from Grudge ID return and persist. */ +export function consumeFleetAuthReturn() { + if (typeof window === 'undefined') return null; + try { + const url = new URL(window.location.href); + const hash = new URLSearchParams(String(url.hash || '').replace(/^#/, '')); + const token = + url.searchParams.get('grudge_token') || + url.searchParams.get('sso_token') || + url.searchParams.get('token') || + hash.get('grudge_token') || + hash.get('sso_token') || + hash.get('token'); + if (token) { + storeFleetToken(token); + url.searchParams.delete('grudge_token'); + url.searchParams.delete('sso_token'); + url.searchParams.delete('token'); + const clean = url.pathname + (url.searchParams.toString() ? `?${url.searchParams}` : '') + url.hash; + window.history.replaceState({}, '', clean); + } + const characterId = + url.searchParams.get('characterId') || url.searchParams.get('char'); + if (characterId) { + localStorage.setItem('grudge_active_character', characterId); + } + return token || readFleetToken(); + } catch { + return readFleetToken(); + } +} + +/** Production Main Panel / Open (do not fork UI here). */ +export const MAIN_PANEL_URL = + 'https://ui.grudge-studio.com/main-panel.html?era=warlords'; +export const OPEN_LIBRARY_URL = 'https://open.grudge-studio.com'; +export const CHARACTER_FOUNDRY_URL = 'https://character.grudge-studio.com/foundry'; +export const GRUDGE_ID_URL = 'https://id.grudge-studio.com'; +/** Inventory / crafting / char select product SSOT (Warlords craft suite) */ +export const CRAFT_SSOT_URL = 'https://grudgewarlords.com/craft/'; +export const WARLORDS_ENGINE_URL = 'https://threeflow-grudgenexus.vercel.app'; + +export function fleetLoginUrl(returnTo) { + const dest = + returnTo || + (typeof window !== 'undefined' + ? window.location.href.split('#')[0] + : 'https://casting-abilities-threejs.vercel.app/'); + return `${GRUDGE_ID_URL}/login?redirect_uri=${encodeURIComponent(dest)}`; +} + +/** + * @typedef {object} FleetApiStatus + * @property {boolean} ok + * @property {number} [latencyMs] + * @property {string} [message] + * @property {object} [body] + */ + +export class FleetApi { + /** + * @param {{ baseUrl?: string, getToken?: () => string|null }} [opts] + */ + constructor(opts = {}) { + const base = opts.baseUrl !== undefined ? opts.baseUrl : resolveFleetApiBase(); + // '' = same-origin (vercel /api rewrites) + this.baseUrl = String(base ?? '').replace(/\/+$/, ''); + this.getToken = opts.getToken || readFleetToken; + /** @type {FleetApiStatus|null} */ + this.lastHealth = null; + /** @type {object[]|null} */ + this.lastCharacters = null; + } + + /** + * @param {string} path + * @param {RequestInit} [init] + */ + async fetch(path, init = {}) { + let url; + if (path.startsWith('http')) { + url = path; + } else { + const p = path.startsWith('/') ? path : `/${path}`; + url = this.baseUrl ? `${this.baseUrl}${p}` : p; + } + const headers = new Headers(init.headers || {}); + if (!headers.has('Accept')) headers.set('Accept', 'application/json'); + const token = this.getToken(); + if (token && !headers.has('Authorization')) { + headers.set('Authorization', `Bearer ${token}`); + } + // Same-origin /api can use credentials; cross-origin Railway uses bearer only + const sameOrigin = !this.baseUrl || url.startsWith('/') || (typeof window !== 'undefined' && url.startsWith(window.location.origin)); + const res = await fetch(url, { + ...init, + headers, + mode: 'cors', + credentials: sameOrigin ? 'include' : 'omit' + }); + const text = await res.text(); + let body = null; + try { + body = text ? JSON.parse(text) : null; + } catch { + body = { raw: text?.slice(0, 200) }; + } + return { res, body, url }; + } + + /** GET /api/health or /health */ + async health() { + const t0 = performance.now(); + const tryPaths = ['/api/health', '/health', '/api/status']; + for (const p of tryPaths) { + try { + const { res, body } = await this.fetch(p); + if (res.ok) { + this.lastHealth = { + ok: true, + latencyMs: Math.round(performance.now() - t0), + message: `OK ${p}`, + body + }; + return this.lastHealth; + } + } catch (err) { + this.lastHealth = { + ok: false, + latencyMs: Math.round(performance.now() - t0), + message: err?.message || String(err) + }; + } + } + if (!this.lastHealth?.ok) { + this.lastHealth = { + ok: false, + latencyMs: Math.round(performance.now() - t0), + message: 'No health endpoint reachable (CORS or down)' + }; + } + return this.lastHealth; + } + + /** + * GET /api/characters — requires auth; lab shows honest fail if no token/CORS. + * @returns {Promise<{ ok: boolean, characters: object[], message: string }>} + */ + async listCharacters() { + try { + // Warlords era only on this host (player frontend path) + const paths = [ + '/api/characters?era=warlords', + '/api/characters' + ]; + let lastStatus = 0; + let lastBody = null; + for (const p of paths) { + const { res, body } = await this.fetch(p); + lastStatus = res.status; + lastBody = body; + if (res.status === 401 || res.status === 403) { + this.lastCharacters = []; + return { + ok: false, + characters: [], + message: + 'Not signed in — open Grudge ID, then return with a session token (grudge_token)' + }; + } + if (!res.ok) continue; + let list = Array.isArray(body) + ? body + : body?.characters || body?.items || body?.data || []; + // Prefer Warlords-era rows when API returns mixed eras + const warlords = list.filter((c) => { + const era = String(c.gameEra || c.era || '').toLowerCase(); + return !era || era === 'warlords' || era === 'warlord'; + }); + if (warlords.length) list = warlords; + this.lastCharacters = list; + return { + ok: true, + characters: list, + message: list.length + ? `${list.length} Warlords character(s)` + : 'Signed in — no Warlords characters on this account yet (create in Foundry)' + }; + } + return { + ok: false, + characters: [], + message: `HTTP ${lastStatus}` + (lastBody?.error ? `: ${lastBody.error}` : '') + }; + } catch (err) { + return { + ok: false, + characters: [], + message: err?.message || 'fetch failed (CORS — use Grudge ID on a fleet host)' + }; + } + } + + /** + * GET account bag summary if authorized. + */ + async accountBag() { + try { + const { res, body } = await this.fetch('/api/account'); + if (!res.ok) return { ok: false, message: `HTTP ${res.status}`, body: null }; + return { ok: true, message: 'account ok', body }; + } catch (err) { + return { ok: false, message: err?.message || 'fail', body: null }; + } + } + + /** + * GET inventory / materials bag — tries fleet paths used by Open / craft. + * @returns {Promise<{ ok: boolean, items: object[], message: string, path?: string }>} + */ + async listInventory() { + // UUID law: account bag is /api/account/inventory (same Railway Postgres). + // Do not invent /bag /materials forks — those 404 and look like a second DB. + const paths = [ + '/api/account/inventory', + '/api/inventory' + ]; + for (const p of paths) { + try { + const { res, body } = await this.fetch(p); + if (res.status === 401 || res.status === 403) { + return { + ok: false, + items: [], + message: 'Not signed in — Grudge ID token required for account bag' + }; + } + if (!res.ok) continue; + const items = + body?.items || + body?.materials || + body?.bag || + body?.slots || + (Array.isArray(body) ? body : body?.data) || + []; + const list = Array.isArray(items) ? items : []; + return { + ok: true, + items: list, + message: list.length ? `${list.length} inventory row(s) via ${p}` : `Empty bag (${p})`, + path: p, + body + }; + } catch { + /* next path */ + } + } + return { + ok: false, + items: [], + message: 'No inventory endpoint reachable (sign-in on fleet host or CORS)' + }; + } + + /** + * Best-effort deposit one stack to account bag (Railway). + * Fail closed with honest message — never fake success. + * @param {object} item + * @param {{ characterId?: string }} [opts] + */ + async depositItem(item, opts = {}) { + if (!item?.id) { + return { ok: false, message: 'No item id' }; + } + const payload = { + id: item.id, + itemId: item.id, + name: item.name, + qty: item.qty || 1, + tier: item.tier ?? 0, + category: item.category || item.kind || 'materials', + iconUrl: item.iconUrl || item.icon, + characterId: opts.characterId || null, + source: 'casting-main-panel' + }; + const tries = [ + { path: '/api/account/inventory', method: 'POST' }, + { path: '/api/inventory/deposit', method: 'POST' } + ]; + for (const t of tries) { + try { + const { res, body } = await this.fetch(t.path, { + method: t.method, + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload) + }); + if (res.status === 401 || res.status === 403) { + return { + ok: false, + message: 'Not signed in — cannot deposit to Railway bag', + authRequired: true + }; + } + if (res.ok) { + return { + ok: true, + message: `Deposited ${payload.name || payload.id} ×${payload.qty}`, + body, + path: t.path + }; + } + } catch { + /* next */ + } + } + return { + ok: false, + message: + 'Deposit API not available — use Craft SSOT (grudgewarlords.com/craft/) for account bag', + openCraft: true + }; + } + + async authMe() { + try { + const { res, body } = await this.fetch('/api/auth/me'); + if (!res.ok) { + return { ok: false, status: res.status, message: `auth/me ${res.status}`, body }; + } + return { ok: true, message: 'signed in', body }; + } catch (err) { + return { ok: false, message: err?.message || 'auth/me fail', body: null }; + } + } + + /** On-chain mirror of ownership (cNFT). Railway remains game truth. */ + async listNfts() { + try { + const { res, body } = await this.fetch('/api/nfts'); + if (res.status === 401 || res.status === 403) { + return { ok: false, nfts: [], message: 'Not signed in — cannot list cNFTs' }; + } + if (!res.ok) return { ok: false, nfts: [], message: `HTTP ${res.status}` }; + const nfts = body?.nfts || body?.items || (Array.isArray(body) ? body : []); + return { + ok: true, + nfts: Array.isArray(nfts) ? nfts : [], + message: `${(nfts || []).length} cNFT row(s) (chain mirrors Railway)` + }; + } catch (err) { + return { ok: false, nfts: [], message: err?.message || 'nfts fail' }; + } + } + + async getCharacter(characterId) { + if (!characterId) return { ok: false, character: null, message: 'No characterId' }; + try { + const { res, body } = await this.fetch(`/api/characters/${encodeURIComponent(characterId)}`); + if (!res.ok) return { ok: false, character: null, message: `HTTP ${res.status}`, body }; + return { ok: true, character: body, message: 'character ok' }; + } catch (err) { + return { ok: false, character: null, message: err?.message || 'fail' }; + } + } + + /** + * POST /api/characters/:id/progress — character-scoped save only. + * Never send bag/inventory here. + */ + async saveCharacterProgress(characterId, payload) { + if (!characterId) return { ok: false, message: 'No character UUID — open Foundry' }; + try { + const { res, body } = await this.fetch( + `/api/characters/${encodeURIComponent(characterId)}/progress`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload || {}) + } + ); + if (res.status === 401 || res.status === 403) { + return { ok: false, message: 'Not signed in — cannot save progress', authRequired: true }; + } + if (!res.ok) { + return { + ok: false, + message: body?.error || `HTTP ${res.status}`, + body + }; + } + return { + ok: true, + message: `Saved progress r${body?.progressRevision ?? '?'}`, + body + }; + } catch (err) { + return { ok: false, message: err?.message || 'progress save fail' }; + } + } + + /** + * Parallel health + roster + bag + cNFT ownership. + */ + async fleetStatusBundle() { + const [health, me, chars, inv, account, nfts] = await Promise.all([ + this.health(), + this.authMe(), + this.listCharacters(), + this.listInventory(), + this.accountBag(), + this.listNfts() + ]); + const grudgeId = me.body?.grudgeId || me.body?.grudge_id || me.body?.user?.grudge_id; + return { + health, + me, + characters: chars, + inventory: inv, + account, + nfts, + hasToken: !!this.getToken(), + grudgeId: grudgeId || null, + engine: WARLORDS_ENGINE_URL, + baseUrl: this.baseUrl || '(same-origin)', + law: { + player: 'Railway Postgres', + chain: 'cNFT mirrors via /api/nfts — not a second bag', + saves: 'POST /api/characters/:id/progress', + deploy: 'Warlords Engine (ThreeFlow) → R2 sector keys' + } + }; + } +} + +export const fleetApi = new FleetApi(); diff --git a/src/api/gameItemCatalog.js b/src/api/gameItemCatalog.js new file mode 100644 index 0000000..14bbdd4 --- /dev/null +++ b/src/api/gameItemCatalog.js @@ -0,0 +1,515 @@ +/** + * Unified game item catalog import for production prefab work. + * + * SSOT (do not fork): + * https://info.grudge-studio.com/api/v1/game-library.json + * https://info.grudge-studio.com/api/v1/canonical-items-manifest.json + * master-weapon-prefabs · master-armor · master-relics · master-mounts · … + * Browse: GRUDGE_Item_Database.html · WEAPON_SKILLS.html + * + * @see docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md + */ + +import { CDN, presentPrefab, loadPrefabCatalog, cdnUrl, tierPresent } from '../loot/prefabAssets.js'; +import { loadEquippableWeapons, T0_STARTER_WEAPON_IDS } from './t0WeaponCatalog.js'; +import { BACK_MOBILITY_CATALOG } from '../config/backSlotMobilitySsot.js'; +import { catalogJsonUrls } from '../config/fleetEnv.js'; + +export const INFO_API = 'https://info.grudge-studio.com/api/v1'; +/** Live Pages — objectstore.grudge-studio.com/api/v1 catalogs 404 */ +export const INFO_MIRROR = 'https://grudge-objectstore.pages.dev/api/v1'; +/** Prefer manifest — game-library.json is currently 404 on info/objectstore (2026-08). */ +export const GAME_LIBRARY_URL = `${INFO_API}/game-library.json`; +export const ITEMS_MANIFEST_URL = `${INFO_API}/canonical-items-manifest.json`; +export const ITEM_BROWSER_URL = 'https://info.grudge-studio.com/GRUDGE_Item_Database.html'; +export const WEAPON_SKILLS_HTML = 'https://info.grudge-studio.com/WEAPON_SKILLS.html'; + +/** Prefab product categories for lab / production surfaces */ +export const PREFAB_CATEGORIES = Object.freeze([ + { id: 'weapons', label: 'Weapons', authority: 'master-weapon-prefabs.json', equip: true }, + { id: 'tools', label: 'Tools', authority: 'master-weapon-prefabs.json (TOOL)', equip: true }, + { id: 'offhand', label: 'Off-hands', authority: 'SHIELD · TOME prefabs', equip: true }, + { id: 't0', label: 'T0 starters', authority: 't0-weapons.json', equip: true }, + { id: 'armor', label: 'Armour', authority: 'master-armor.json', equip: true }, + { id: 'back', label: 'Back / mobility', authority: 'backSlotMobilitySsot + armor back', equip: true }, + { id: 'relics', label: 'Relics', authority: 'master-relics.json', equip: true }, + { id: 'class', label: 'Class items', authority: 'master-classRelics.json · classes.json', equip: true }, + { id: 'mounts', label: 'Mounts', authority: 'master-mounts.json', equip: true }, + { id: 'special', label: 'Specials', authority: 'artifacts · enchants · infusions', equip: false } +]); + +/** + * @typedef {object} GameItemRow + * @property {string} id + * @property {string} [uuid] + * @property {string} name + * @property {string} category weapons|armor|relics|mounts|class|offhand|t0|tools|special + * @property {number} [tier] + * @property {string} [slot] equip slot hint + * @property {string} [iconUrl] + * @property {string|null} [modelUrl] + * @property {object} [stats] + * @property {string} [description] + * @property {string} [weaponType] + * @property {boolean} equippable + * @property {object} [raw] + * @property {string} source catalog file + */ + +async function fetchJson(urls) { + for (const url of urls) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) continue; + return await res.json(); + } catch { + /* next */ + } + } + return null; +} + +function iconOf(row) { + return ( + row?.iconUrl || + row?.assets?.iconUrl || + cdnUrl(row?.assets?.iconR2Key) || + cdnUrl(row?.icon?.path) || + cdnUrl(row?.icon) || + null + ); +} + +function modelOf(row) { + return ( + row?.modelUrl || + row?.prodGltfUrl || + row?.assets?.modelUrl || + cdnUrl(row?.assets?.modelR2Key) || + cdnUrl(row?.modelPath) || + null + ); +} + +/** + * Normalize any catalog row into a HUD/equip-safe present item. + * @param {object} row + * @param {string} category + * @param {string} source + * @returns {GameItemRow|null} + */ +export function presentItem(row, category, source) { + if (!row) return null; + const id = row.id || row.uuid || row.slug; + if (!id) return null; + const tier = Number(row.tier) || 0; + const t = tierPresent(tier); + const equippable = + category === 'weapons' || + category === 'tools' || + category === 'offhand' || + category === 't0' || + category === 'armor' || + category === 'back' || + category === 'relics' || + category === 'mounts' || + !!row.equippable; + + const slot = + row.slot || row.equipSlot || row.subCategory || row.weaponType || null; + + // Bag / paperdoll kind hints (itemFitsSlot) + let kind = category; + if (category === 't0' || category === 'weapons') kind = 'weapon'; + else if (category === 'tools') kind = 'tool'; + else if (category === 'offhand') kind = /shield/i.test(String(row.weaponType || '')) + ? 'shield' + : 'tome'; + else if (category === 'armor') kind = 'armour'; + else if (category === 'back') kind = 'back'; + else if (category === 'relics' || category === 'class') kind = 'relic'; + else if (category === 'mounts') kind = 'mount'; + + return { + id: String(id), + uuid: row.uuid || null, + name: row.name || row.baseName || String(id), + category, + kind, + tier, + slot, + slotHint: row.slotHint || slot || null, + equipSlot: slot, + iconUrl: iconOf(row) || cdnUrl('icons/pack/weapons/staff_1.png'), + modelUrl: modelOf(row), + stats: row.stats || null, + description: row.description || row.lore || row.note || '', + weaponType: row.weaponType || null, + equippable, + borderColor: t.border, + glowColor: t.glow, + tierLabel: t.label, + source, + raw: row + }; +} + +let _cache = null; +let _loading = null; + +/** + * Load all prefab-relevant catalogs for the lab dev environment. + */ +export async function loadGameItemCatalog() { + if (_cache) return _cache; + if (_loading) return _loading; + + _loading = (async () => { + const [ + library, + manifest, + weaponsCat, + armor, + relics, + mounts, + classRelics, + equippable + ] = await Promise.all([ + fetchJson(catalogJsonUrls('game-library.json')), + fetchJson(catalogJsonUrls('canonical-items-manifest.json')), + loadPrefabCatalog().catch(() => null), + fetchJson(catalogJsonUrls('master-armor.json')), + fetchJson(catalogJsonUrls('master-relics.json')), + fetchJson(catalogJsonUrls('master-mounts.json')), + fetchJson(catalogJsonUrls('master-classRelics.json')), + loadEquippableWeapons().catch(() => null) + ]); + + /** @type {Record} */ + const byCategory = { + weapons: [], + tools: [], + offhand: [], + t0: [], + armor: [], + back: [], + relics: [], + class: [], + mounts: [], + special: [] + }; + + // Weapons / tools / offhand from master-weapon-prefabs + for (const raw of weaponsCat?._rawPrefabs || []) { + const wt = String(raw.weaponType || '').toUpperCase(); + const cat = String(raw.category || ''); + let bucket = 'weapons'; + if (wt === 'TOOL' || /tool/i.test(cat)) bucket = 'tools'; + else if (wt === 'SHIELD' || wt === 'TOME' || /offhand|shield|tome/i.test(cat)) bucket = 'offhand'; + const row = presentItem(raw, bucket, 'master-weapon-prefabs.json'); + if (row) byCategory[bucket].push(row); + } + + // T0 starters (skills live here) + for (const w of equippable?.weapons || []) { + const row = presentItem( + { + id: w.id, + name: w.name, + tier: w.tier, + weaponType: w.weaponType, + stats: w.stats, + description: w.description, + iconUrl: w.iconUrl, + modelUrl: w.modelUrl, + equippable: true + }, + 't0', + 't0-weapons.json' + ); + if (row) { + row.slot1 = w.slot1; + row.slot2 = w.slot2; + row.slot3Options = w.slot3Options; + row.defaultSlot3Id = w.defaultSlot3Id; + row.animPack = w.animPack; + row.meshSlot = w.meshSlot; + byCategory.t0.push(row); + } + } + + // Armor (body/head/arms/legs/back sets) + for (const a of armor?.items || armor?.armor || []) { + const slot = a.slot || a.equipSlot || a.subCategory || ''; + const isBack = /back|cape|cloak|pack|wings/i.test(String(slot)); + const bucket = isBack ? 'back' : 'armor'; + const row = presentItem( + { + ...a, + equipSlot: slot, + slotHint: isBack + ? 'back' + : /head|helm/i.test(String(slot)) + ? 'head' + : /leg|boot|pant/i.test(String(slot)) + ? 'legs' + : /arm|glove|hand/i.test(String(slot)) + ? 'arms' + : /shoulder/i.test(String(slot)) + ? 'shoulders' + : 'body', + icon: a.icon || a.iconPath, + equippable: true + }, + bucket, + 'master-armor.json' + ); + if (row) byCategory[bucket].push(row); + } + + // Lab back mobility (windsurf · wings) — always available for casting develop + for (const m of Object.values(BACK_MOBILITY_CATALOG)) { + if (m.id === 'none') continue; + const row = presentItem( + { + id: m.id, + name: m.label, + tier: 0, + slot: 'back', + slotHint: 'back', + equipSlot: 'back', + description: m.notes || m.domain, + modelUrl: m.modelUrl, + equippable: true, + domain: m.domain, + deployKind: m.deployKind, + flight: m.flight || null + }, + 'back', + 'backSlotMobilitySsot' + ); + if (row) { + row.kind = 'back'; + row.domain = m.domain; + byCategory.back.push(row); + } + } + + // Relics + const relicList = relics?.relics || relics?.items || []; + for (const r of relicList) { + const row = presentItem(r, 'relics', 'master-relics.json'); + if (row) byCategory.relics.push(row); + } + + // Class relics / class items + const cr = classRelics?.classRelics || classRelics?.items || classRelics?.relics || []; + const crArr = Array.isArray(cr) ? cr : Object.values(cr).flat(); + for (const r of crArr) { + const row = presentItem(r, 'class', 'master-classRelics.json'); + if (row) byCategory.class.push(row); + } + + // Mounts + for (const m of mounts?.mounts || mounts?.items || []) { + const row = presentItem(m, 'mounts', 'master-mounts.json'); + if (row) byCategory.mounts.push(row); + } + + const counts = Object.fromEntries( + Object.entries(byCategory).map(([k, v]) => [k, v.length]) + ); + + _cache = { + library, + manifest, + byCategory, + counts, + equippable, + weaponsCat, + starters: { + apprenticeWand: equippable?.byId?.get?.(T0_STARTER_WEAPON_IDS.apprenticeWand) || null, + saplingStaff: equippable?.byId?.get?.(T0_STARTER_WEAPON_IDS.saplingStaff) || null + }, + urls: { + library: GAME_LIBRARY_URL, + manifest: ITEMS_MANIFEST_URL, + browser: ITEM_BROWSER_URL, + skills: WEAPON_SKILLS_HTML, + cdn: CDN + }, + loadedAt: Date.now() + }; + _loading = null; + return _cache; + })(); + + return _loading; +} + +/** + * T0–T1 items that fit a paperdoll slot (for RMB catalog browse). + * @param {string} slotId mainHand|body|back|relic|… + * @param {{ maxTier?: number, q?: string }} [opts] + * @returns {Promise} + */ +export async function listT0T1ForSlot(slotId, opts = {}) { + const cat = await loadGameItemCatalog(); + const maxTier = opts.maxTier ?? 1; + const q = String(opts.q || '').toLowerCase().trim(); + const sid = String(slotId || '').toLowerCase(); + + /** @type {GameItemRow[]} */ + const out = []; + const push = (row) => { + if (!row || !row.equippable) return; + if ((row.tier ?? 0) > maxTier) return; + if (q && !`${row.name} ${row.id}`.toLowerCase().includes(q)) return; + out.push(row); + }; + + // Weapon set A (mainHand) + set B (weapon2) share the same main-weapon catalog + if (sid === 'mainhand' || sid === 'weapon2') { + for (const r of [...cat.byCategory.t0, ...cat.byCategory.weapons, ...cat.byCategory.tools]) { + push(r); + } + } else if (sid === 'offhand' || sid === 'offhand2') { + for (const r of cat.byCategory.offhand) push(r); + // Dual-wield: also list light weapons for Off 1 / Off 2 + for (const r of [...cat.byCategory.t0, ...cat.byCategory.weapons]) { + const wt = String(r.weaponType || r.id || '').toLowerCase(); + if (/dagger|pistol|handgun|off|shield|tome|wand/.test(wt)) push(r); + } + } else if (['head', 'body', 'arms', 'legs', 'shoulders'].includes(sid)) { + for (const r of cat.byCategory.armor) { + const h = String(r.slotHint || r.slot || '').toLowerCase(); + if (!h || h === sid || h.includes(sid) || (sid === 'body' && /chest|torso|armor|body/i.test(h))) { + push(r); + } + } + } else if (sid === 'back') { + for (const r of cat.byCategory.back) push(r); + } else if (sid === 'relic') { + for (const r of [...cat.byCategory.relics, ...cat.byCategory.class]) push(r); + } else if (sid === 'mount') { + for (const r of cat.byCategory.mounts) push(r); + } else { + // Generic: all equippable T0–T1 + for (const list of Object.values(cat.byCategory)) { + for (const r of list) push(r); + } + } + + // Dedupe by id + const seen = new Set(); + return out.filter((r) => { + if (seen.has(r.id)) return false; + seen.add(r.id); + return true; + }); +} + +/** + * Flat list T0–T1 for inventory seed / admin. + * @param {{ maxTier?: number }} [opts] + */ +export async function listAllT0T1(opts = {}) { + const cat = await loadGameItemCatalog(); + const maxTier = opts.maxTier ?? 1; + const out = []; + for (const list of Object.values(cat.byCategory)) { + for (const r of list) { + if (r.equippable && (r.tier ?? 0) <= maxTier) out.push(r); + } + } + return out; +} + +export function getGameItemCatalogCache() { + return _cache; +} + +/** + * Filter rows for Prefabs UI. + * @param {Awaited>} cat + * @param {{ category?: string, q?: string, limit?: number }} opts + */ +export function queryGameItems(cat, opts = {}) { + if (!cat) return []; + const limit = opts.limit ?? 80; + const q = String(opts.q || '') + .trim() + .toLowerCase(); + let pool = []; + if (opts.category && cat.byCategory[opts.category]) { + pool = cat.byCategory[opts.category]; + } else { + pool = Object.values(cat.byCategory).flat(); + } + if (q) { + pool = pool.filter( + (r) => + r.name.toLowerCase().includes(q) || + r.id.toLowerCase().includes(q) || + String(r.weaponType || '') + .toLowerCase() + .includes(q) || + String(r.slot || '') + .toLowerCase() + .includes(q) + ); + } + return pool.slice(0, limit); +} + +/** + * Export a production-oriented prefab snapshot for any row. + * @param {GameItemRow} row + */ +export function exportItemPrefabSnapshot(row) { + if (!row) return null; + return { + version: '1.0.0', + generated: new Date().toISOString(), + kind: 'grudge-game-item-prefab', + id: row.id, + uuid: row.uuid, + name: row.name, + category: row.category, + tier: row.tier, + slot: row.slot, + weaponType: row.weaponType, + equippable: row.equippable, + stats: row.stats, + description: row.description, + assets: { + iconUrl: row.iconUrl, + modelUrl: row.modelUrl + }, + presentation: { + borderColor: row.borderColor, + glowColor: row.glowColor, + tierLabel: row.tierLabel + }, + skills: row.slot1 + ? { + slot1: row.slot1, + slot2: row.slot2, + slot3Options: row.slot3Options, + defaultSlot3Id: row.defaultSlot3Id + } + : row.raw?.skills || null, + lab: { + meshSlot: row.meshSlot || null, + animPack: row.animPack || null, + liveLab: 'https://casting.grudge.studio/', + source: row.source + }, + production: { + itemBrowser: ITEM_BROWSER_URL, + weaponSkills: WEAPON_SKILLS_HTML, + gameLibrary: GAME_LIBRARY_URL, + consumers: ['items', 'character-hud', 'ui', 'controller', 'combat', 'dev-lab'] + } + }; +} diff --git a/src/api/prefabDraftStore.js b/src/api/prefabDraftStore.js new file mode 100644 index 0000000..53da0f8 --- /dev/null +++ b/src/api/prefabDraftStore.js @@ -0,0 +1,170 @@ +/** + * Local draft save/load for deployable prefabs (weapons, buildables, enemies…). + * + * Lab storage only — not player SSOT (Railway) and not catalog mint (ObjectStore). + * Export JSON → hand off to ObjectStore pipelines for real UUIDs. + * + * @see docs/ADMIN_HUB_F1_F5_SSOT.md + */ + +import { createDeployableDraft, validateDeployableDraft, exportDeployableSnapshot } from './deployableContract.js'; +import { downloadJson } from './prefabScaffold.js'; + +const STORAGE_KEY = 'grudge.casting.deployableDrafts.v1'; +const MAX_DRAFTS = 80; + +function safeParse(raw) { + try { + return JSON.parse(raw); + } catch { + return null; + } +} + +/** + * @returns {{ version: number, drafts: object[] }} + */ +export function loadDraftLibrary() { + if (typeof localStorage === 'undefined') return { version: 1, drafts: [] }; + const data = safeParse(localStorage.getItem(STORAGE_KEY)); + if (!data || !Array.isArray(data.drafts)) return { version: 1, drafts: [] }; + return { version: data.version || 1, drafts: data.drafts }; +} + +function persist(lib) { + if (typeof localStorage === 'undefined') return false; + const drafts = (lib.drafts || []).slice(0, MAX_DRAFTS); + localStorage.setItem(STORAGE_KEY, JSON.stringify({ version: 1, drafts })); + return true; +} + +/** + * List drafts, optional filter by kind or adminTab. + * @param {{ kind?: string, adminTab?: string }} [filter] + */ +export function listDrafts(filter = {}) { + const { drafts } = loadDraftLibrary(); + return drafts + .filter((d) => { + if (filter.kind && d.kind !== filter.kind) return false; + if (filter.adminTab && d.adminTab !== filter.adminTab) return false; + return true; + }) + .sort((a, b) => String(b._savedAt || b._createdAt || '').localeCompare(String(a._savedAt || a._createdAt || ''))); +} + +/** + * @param {string} id + */ +export function getDraft(id) { + return loadDraftLibrary().drafts.find((d) => d.id === id) || null; +} + +/** + * Save or update a draft. Returns saved draft. + * @param {object} draft + */ +export function saveDraft(draft) { + if (!draft?.id) throw new Error('draft.id required'); + const lib = loadDraftLibrary(); + const now = new Date().toISOString(); + const next = { + ...draft, + _local: true, + _savedAt: now, + _createdAt: draft._createdAt || now + }; + const idx = lib.drafts.findIndex((d) => d.id === next.id); + if (idx >= 0) lib.drafts[idx] = next; + else lib.drafts.unshift(next); + persist(lib); + return next; +} + +/** + * @param {string} id + */ +export function deleteDraft(id) { + const lib = loadDraftLibrary(); + lib.drafts = lib.drafts.filter((d) => d.id !== id); + persist(lib); + return true; +} + +/** + * Create + save a new draft of a kind. + * @param {string} kindId + * @param {object} [fields] + */ +export function createAndSaveDraft(kindId, fields = {}) { + const draft = createDeployableDraft(kindId, fields); + return saveDraft(draft); +} + +/** + * Duplicate draft under new id. + * @param {string} id + */ +export function duplicateDraft(id) { + const src = getDraft(id); + if (!src) return null; + const copy = createDeployableDraft(src.kind, { + ...src, + name: `${src.name} (copy)`, + id: undefined, + uuid: null + }); + // preserve layers where useful + if (src.layers) copy.layers = JSON.parse(JSON.stringify(src.layers)); + if (src.assets) copy.assets = { ...src.assets }; + copy.id = `draft-${src.kind}-${Date.now().toString(36)}`; + copy.uuid = null; + return saveDraft(copy); +} + +/** + * Download full export pack for one draft. + * @param {string|object} idOrDraft + */ +export function downloadDraftExport(idOrDraft) { + const draft = typeof idOrDraft === 'string' ? getDraft(idOrDraft) : idOrDraft; + if (!draft) return false; + const snap = exportDeployableSnapshot(draft); + const name = String(draft.id || 'draft').replace(/[^a-z0-9_-]+/gi, '-'); + return downloadJson(snap, `deployable-${name}.json`); +} + +/** + * Import a draft JSON (from file or paste). Saves locally. + * @param {object} data export snapshot or raw draft + */ +export function importDraftJson(data) { + const draft = data?.draft || data; + if (!draft?.kind || !draft?.id) throw new Error('Invalid draft JSON (need kind + id)'); + const cleaned = { + ...draft, + uuid: draft.uuid || null, + _local: true + }; + // avoid clobbering: new id if already exists + if (getDraft(cleaned.id)) { + cleaned.id = `${cleaned.id}-import-${Date.now().toString(36)}`; + } + return saveDraft(cleaned); +} + +/** + * Stats for admin hub header. + */ +export function draftStats() { + const drafts = listDrafts(); + const byKind = {}; + const byTab = {}; + for (const d of drafts) { + byKind[d.kind] = (byKind[d.kind] || 0) + 1; + byTab[d.adminTab] = (byTab[d.adminTab] || 0) + 1; + } + return { total: drafts.length, byKind, byTab, max: MAX_DRAFTS }; +} + +export { validateDeployableDraft, exportDeployableSnapshot, createDeployableDraft }; diff --git a/src/api/prefabScaffold.js b/src/api/prefabScaffold.js new file mode 100644 index 0000000..d134abc --- /dev/null +++ b/src/api/prefabScaffold.js @@ -0,0 +1,438 @@ +/** + * Prefab Scaffold Control — correct access + generation briefs for Grudge items. + * + * Access (read SSOT only): + * canonical-items-manifest · equipment-pattern · master-weapon-prefabs · + * t0-weapons · master-weaponSkills · master-recipes · master-registry + * + * Generation (local drafts — do not mint ITEM- or SKIL- UUIDs here): + * style icon prompt · 3D sprite brief · item script stub · craft formula pack + * + * Minting UUIDs / writing ObjectStore = ObjectStore pipelines only + * (npm run build:weapon-pipeline / generate:master). + * + * @see docs/PREFAB_SCAFFOLD_CONTROL_SSOT.md + * @see docs/WEAPON_PREFAB_UUID_SSOT.md + */ + +import { + INFO_API, + INFO_MIRROR, + ITEMS_MANIFEST_URL, + PREFAB_CATEGORIES +} from './gameItemCatalog.js'; +import { catalogJsonUrls } from '../config/fleetEnv.js'; +import { + GRUDGE_UUID_PREFIX, + WEAPON_PREFAB_RUNTIME_JOBS, + WEAPON_PREFAB_REQUIRED_LAYERS, + validateWeaponPrefab, + normalizeWeaponPrefabContract, + isGrudgeUuid +} from './weaponPrefabContract.js'; +import { CDN, presentPrefab, loadPrefabCatalog } from '../loot/prefabAssets.js'; +import { + loadEquippableWeapons, + exportWarlordsWeaponPrefab, + T0_ALL_WEAPON_IDS +} from './t0WeaponCatalog.js'; + +export const SCAFFOLD_ENDPOINTS = Object.freeze({ + manifest: `${INFO_API}/canonical-items-manifest.json`, + equipmentPattern: `${INFO_API}/_meta/canonical-equipment-pattern.json`, + weaponPrefabs: `${INFO_API}/master-weapon-prefabs.json`, + t0Weapons: `${INFO_API}/t0-weapons.json`, + weaponSkills: `${INFO_API}/master-weaponSkills.json`, + recipes: `${INFO_API}/master-recipes.json`, + materials: `${INFO_API}/master-materials.json`, + registry: `${INFO_API}/master-registry.json`, + iconRegistry: `${INFO_API}/icon-registry.json`, + docs: 'https://info.grudge-studio.com/docs', + hub: 'https://info.grudge-studio.com/hub.html', + itemBrowser: 'https://info.grudge-studio.com/GRUDGE_Item_Database.html', + weaponSkillsHtml: 'https://info.grudge-studio.com/WEAPON_SKILLS.html', + iconBrowser: 'https://info.grudge-studio.com/ICON_BROWSER.html', + cdn: CDN, + workerMirror: INFO_MIRROR, + fetchOrder: [ + 'same-origin /api/info/v1/*', + 'info.grudge-studio.com/api/v1/*', + 'grudge-objectstore.pages.dev/api/v1/*', + 'assets.grudge-studio.com via /api/assets (binaries only)' + ] +}); + +/** Style families for icon / 3D sprite generation (house art direction). */ +export const ITEM_STYLE_FAMILIES = Object.freeze({ + warlords_dark: { + id: 'warlords_dark', + label: 'Warlords dark fantasy', + palette: ['#1a1410', '#3d2b1f', '#8b6914', '#c4a35a', '#e8dcc2', '#5c1a1a'], + notes: 'Grudge house style — worn metal, muted gold, no plastic MMO gloss' + }, + toon_rts: { + id: 'toon_rts', + label: 'Toon RTS polyart', + palette: ['#2a2a32', '#6b7c8f', '#c9a227', '#e8eef6'], + notes: 'Matches grudge6 / Toon RTS polyart weapons' + }, + starter_t0: { + id: 'starter_t0', + label: 'T0 training / blunted', + palette: ['#6b7280', '#9aa3ad', '#c5ccd4', '#4b5563'], + notes: 'Grey training gear — blunted edges, wood + scrap' + } +}); + +async function fetchJson(urls) { + for (const url of urls) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) continue; + return await res.json(); + } catch { + /* next */ + } + } + return null; +} + +let _scaffoldCache = null; + +/** + * Load control-plane catalogs (manifest + pattern + optional recipes index). + */ +export async function loadPrefabScaffold() { + if (_scaffoldCache) return _scaffoldCache; + const [manifest, pattern, recipes] = await Promise.all([ + fetchJson(catalogJsonUrls('canonical-items-manifest.json')), + fetchJson(catalogJsonUrls('_meta/canonical-equipment-pattern.json')), + fetchJson(catalogJsonUrls('master-recipes.json')) + ]); + + _scaffoldCache = { + loadedAt: Date.now(), + endpoints: SCAFFOLD_ENDPOINTS, + categories: PREFAB_CATEGORIES, + uuidPrefixes: { + ...GRUDGE_UUID_PREFIX, + ...(manifest?.uuidPrefixes || {}) + }, + manifest, + pattern, + recipesTotal: recipes?.totalRecipes || recipes?.total || recipes?.recipes?.length || 0, + recipes: recipes?.recipes || [], + layers: WEAPON_PREFAB_REQUIRED_LAYERS, + jobs: WEAPON_PREFAB_RUNTIME_JOBS, + styleFamilies: ITEM_STYLE_FAMILIES, + t0Ids: T0_ALL_WEAPON_IDS, + pipelines: pattern?.pipelines || { + weaponsAndTools: 'npm run build:weapon-pipeline', + allMasterData: 'npm run generate:master && npm run consolidate:game-data' + } + }; + return _scaffoldCache; +} + +/** + * Resolve craft formula for an item (T0 inline recipe or RECP-* by result name/id). + * @param {object} item equippable or prefab row + * @param {object} [scaffold] loadPrefabScaffold() + */ +export function resolveCraftFormula(item, scaffold) { + if (!item) return null; + const t0Craft = item.rawT0?.craftingRecipe || item.craftingRecipe || item.raw?.craftingRecipe; + if (t0Craft?.materials?.length) { + return { + source: 't0-weapons.craftingRecipe', + uuid: null, + station: t0Craft.station || 'Anywhere', + profession: t0Craft.profession || null, + craftTime: t0Craft.craftTime ?? 10, + gold: t0Craft.gold ?? 0, + materials: t0Craft.materials.map((m) => ({ + id: m.id, + uuid: m.uuid || null, + name: m.name || m.id, + quantity: m.quantity || 1 + })), + resultId: item.id, + resultUuid: item.uuid || null, + note: 'T0 starter craft (Anywhere). T1 unlocks five-slot via craftsInto.' + }; + } + + const recipes = scaffold?.recipes || []; + const id = String(item.id || ''); + const name = String(item.name || ''); + const uuid = item.uuid || item.uuids?.item; + const hit = + recipes.find((r) => r.resultItemId === id || r.resultItemId === uuid) || + recipes.find((r) => r.resultUuid === uuid) || + recipes.find( + (r) => + r.resultName && + name && + String(r.resultName).toLowerCase() === name.toLowerCase() + ) || + null; + + if (!hit) { + return { + source: 'none', + uuid: item.rawPrefab?.recipeUuid || null, + materials: [], + note: 'No RECP row linked — fill recipeUuid on prefab or craft in ObjectStore' + }; + } + + return { + source: 'master-recipes.json', + uuid: hit.uuid, + id: hit.id, + name: hit.name, + station: hit.station, + profession: hit.profession, + craftTime: hit.craftTime, + gold: hit.goldCost ?? hit.gold, + tier: hit.tier, + materials: (hit.materials || []).map((m) => ({ + id: m.id, + uuid: m.uuid, + name: m.name || m.id, + quantity: m.quantity || 1 + })), + resultId: hit.resultItemId, + resultName: hit.resultName, + successRate: hit.successRate, + requiredLevel: hit.requiredLevel + }; +} + +/** + * Full control pack for one weapon/item (access + validation + gen briefs). + * @param {object} weapon equippable from loadEquippableWeapons or raw prefab + */ +export async function buildItemScaffoldPack(weapon) { + const scaffold = await loadPrefabScaffold(); + const eqCat = await loadEquippableWeapons().catch(() => null); + const w = + weapon || + eqCat?.byId?.get?.('t0-sword') || + null; + if (!w) return { error: 'no weapon' }; + + const raw = w.rawPrefab || w.raw || w; + const t0 = w.rawT0 || (eqCat?.t0Data?.weapons || []).find((x) => x.id === w.id) || null; + const contract = normalizeWeaponPrefabContract(raw, t0); + // merge equippable skill bodies into contract for gen + if (w.slot1) { + contract.skills.slot1Body = w.slot1; + contract.skills.slot2Body = w.slot2; + contract.skills.slot3Options = w.slot3Options; + } + const validation = validateWeaponPrefab(raw?.uuid ? raw : { ...raw, ...w, stats: w.stats || raw.stats }); + // if raw thin, re-validate with merged identity + const mergedForVal = { + uuid: w.uuid || raw.uuid, + id: w.id, + name: w.name, + tier: w.tier, + weaponType: w.weaponType, + stats: w.stats || raw.stats || {}, + assets: { + iconUrl: w.iconUrl, + ...(raw.assets || {}) + }, + modelUrl: w.modelUrl, + skills: raw.skills || { + slots: [ + { + type: 'primary', + skillIds: w.slot1?.id ? [w.slot1.id] : [], + skillUuids: w.slot1?.uuid ? [w.slot1.uuid] : [], + fixed: true + }, + { + type: 'secondary', + skillIds: w.slot2?.id ? [w.slot2.id] : [], + skillUuids: w.slot2?.uuid ? [w.slot2.uuid] : [], + fixed: true + }, + { + type: 'ability', + skillIds: (w.slot3Options || []).map((s) => s.id), + skillUuids: (w.slot3Options || []).map((s) => s.uuid).filter(Boolean), + choice: true + } + ], + skillUuids: [ + w.slot1?.uuid, + w.slot2?.uuid, + ...(w.slot3Options || []).map((s) => s.uuid) + ].filter(Boolean), + slotPattern: w.tier === 0 ? 'three-slot-starter' : 'five-slot' + }, + loadout: raw.loadout || { pattern: w.tier === 0 ? 'three-slot-starter' : 'five-slot' } + }; + const val = validateWeaponPrefab(mergedForVal); + const craft = resolveCraftFormula( + { ...w, rawT0: t0, craftingRecipe: t0?.craftingRecipe }, + scaffold + ); + const styleKey = + w.tier === 0 ? 'starter_t0' : /toon|rts/i.test(w.weaponType || '') ? 'toon_rts' : 'warlords_dark'; + const style = ITEM_STYLE_FAMILIES[styleKey]; + + const gen = { + icon: buildIconGenerationBrief(w, style), + sprite3d: build3dSpriteBrief(w, style), + itemScript: buildItemScriptStub(w, craft, contract), + statsBlock: w.stats || {}, + craft + }; + + return { + access: { + endpoints: SCAFFOLD_ENDPOINTS, + resolveOrder: scaffold.manifest?.resolutionOrder || scaffold.pattern?.fetchOrder, + pipelines: scaffold.pipelines + }, + identity: { + id: w.id, + uuid: w.uuid, + name: w.name, + tier: w.tier, + weaponType: w.weaponType + }, + validation: val, + contract, + exportPrefab: exportWarlordsWeaponPrefab(w), + generation: gen, + use: { + equip: true, + meshSlot: w.meshSlot, + animPack: w.animPack, + labStyle: w.labStyle, + hotbarKeys: '1–3 (T0) or 1–5 (T1+)', + jobs: WEAPON_PREFAB_RUNTIME_JOBS + } + }; +} + +/** + * Style-appropriate 2D icon generation brief (for Imagine / Meshy / artist). + */ +export function buildIconGenerationBrief(item, style = ITEM_STYLE_FAMILIES.warlords_dark) { + const wt = item.weaponType || 'WEAPON'; + const name = item.name || item.id; + const tier = item.tier ?? 0; + return { + kind: 'icon_2d', + targetPath: `game-assets/icons/weapons/${String(item.id || name) + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-')}.png`, + targetUuidPrefix: GRUDGE_UUID_PREFIX.icon, + size: '512×512 PNG, transparent, centered', + style: style.id, + palette: style.palette, + prompt: [ + `${style.label} game item icon of ${name}`, + `${wt} weapon, tier T${tier}`, + 'single object, three-quarter view, readable silhouette at 64px', + 'dark fantasy, grounded materials, no text, no UI chrome', + style.notes, + item.tier === 0 ? 'training/blunted look, grey-brown, recruit gear' : 'production combat gear' + ].join('. '), + negative: 'text, watermark, busy background, neon cyberpunk, plastic toy, multiple objects', + register: 'Upload to R2 → set assets.iconUrl + assets.iconUuid ICON-* on prefab via ObjectStore pipeline' + }; +} + +/** + * 3D “sprite” / world-drop mesh brief (billboard or low-poly prop). + */ +export function build3dSpriteBrief(item, style = ITEM_STYLE_FAMILIES.warlords_dark) { + const name = item.name || item.id; + const wt = item.weaponType || 'SWORD'; + return { + kind: 'mesh_3d_world_sprite', + targetPath: `models/weapons/${String(item.category || 'misc').toLowerCase()}/${String(item.id || name).toLowerCase()}.glb`, + dropPrefabPath: `prefabs/items/weapons/${wt}/${item.tier ?? 0}/${String(name).toLowerCase()}.prefab.glb`, + si: { + maxLengthM: + /SPEAR|GREAT|2H/i.test(wt) ? 1.8 : /DAGGER|WAND/i.test(wt) ? 0.7 : /BOW|GUN/i.test(wt) ? 1.3 : 1.15, + units: '1 unit = 1 m' + }, + style: style.id, + prompt: [ + `Low-poly stylized ${name} (${wt}) for browser Three.js`, + 'single mesh or few meshes, PBR metal/roughness, no rig required for drop prop', + 'origin at grip for equip; drop prefab may be icon-plane + glow', + style.notes + ].join('. '), + collider: 'box or capsule along blade — bake .collider.json optional', + register: 'grudge-asset-convert → R2 → prefab modelUrl / prodGltfUrl / dropPrefabR2Key' + }; +} + +/** + * Item script stub — use hooks, stats, craft (runtime consumer shape). + */ +export function buildItemScriptStub(item, craft, contract) { + return { + kind: 'item_script', + language: 'javascript-module', + note: 'Draft only — production wiring uses fleet equip + DRC, not eval of this file', + module: { + id: item.id, + uuid: item.uuid || contract?.uuids?.item || null, + type: 'weapon', + weaponType: item.weaponType, + tier: item.tier, + stats: item.stats || {}, + skills: { + slot1: item.slot1?.id || item.slot1, + slot2: item.slot2?.id || item.slot2, + slot3Options: (item.slot3Options || []).map((s) => s.id || s), + skillUuids: contract?.uuids?.skills || [] + }, + use: { + equipSlot: item.meshSlot || 'sword', + animPack: item.animPack, + onEquip: 'setAnimPack + mesh_ids + attachWeaponModel', + onSkill: 'hotbar 1–3 → useSkill(slot) catalog only', + onUnequip: 'clear attach + restore pack' + }, + craft: craft || null, + validate: 'validateWeaponPrefab / normalizeWeaponPrefabContract' + } + }; +} + +/** + * Download helper payload for UI. + * @param {object} pack buildItemScaffoldPack result + * @param {'full'|'icon'|'sprite3d'|'script'|'craft'} [part] + */ +export function serializeScaffoldPart(pack, part = 'full') { + if (!pack) return null; + if (part === 'full') return pack; + if (part === 'icon') return pack.generation?.icon; + if (part === 'sprite3d') return pack.generation?.sprite3d; + if (part === 'script') return pack.generation?.itemScript; + if (part === 'craft') return pack.generation?.craft; + if (part === 'export') return pack.exportPrefab; + return pack; +} + +export function downloadJson(obj, filename) { + if (!obj || typeof document === 'undefined') return false; + const blob = new Blob([JSON.stringify(obj, null, 2)], { type: 'application/json' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = filename; + a.click(); + URL.revokeObjectURL(a.href); + return true; +} diff --git a/src/api/t0WeaponCatalog.js b/src/api/t0WeaponCatalog.js new file mode 100644 index 0000000..e5f42e0 --- /dev/null +++ b/src/api/t0WeaponCatalog.js @@ -0,0 +1,1117 @@ +/** + * T0 + equippable weapon loadouts for Warlords prefab authoring. + * + * Sources (SSOT — do not fork): + * - master-weapon-prefabs.json → icon, 3D model, tier, weaponType, skill slot ids + * - t0-weapons.json → three-slot starter skill bodies + * - master-weaponSkills.json → full skill defs / starters by weapon type + * + * Lab: equip weapon → anim pack + skill bar + hand mesh + icon sprite + */ + +import { + presentPrefab, + loadPrefabCatalog, + cdnUrl, + CDN +} from '../loot/prefabAssets.js'; +import { catalogJsonUrls } from '../config/fleetEnv.js'; +import { + loadWeaponSkillsCatalog, + labMapForWeaponType, + normalizeWeaponTypeId, + animRoleForSkill, + vfxIdForSkill, + iconCdnUrl +} from './weaponSkillsCatalog.js'; +import { WEAPON_SLOT_TO_PACK } from '../config/weaponAnimPack.js'; +import { bindFromCatalogSkill } from '../combat/staffWeaponSkillsBind.js'; +import { + compileProductionWeaponSkill, + getCachedProductionOverride, + productionToDrcSkill, + warmProductionOverrides +} from '../combat/weaponSkillProduction.js'; +import { labMaxTier, isLabAdmin } from '../config/labAdmin.js'; + +export { warmProductionOverrides }; + +export const T0_WEAPONS_URL = 'https://info.grudge-studio.com/api/v1/t0-weapons.json'; +/** Live ObjectStore Pages — objectstore.grudge-studio.com/api/v1 is 404 */ +export const T0_WEAPONS_MIRROR = + 'https://grudge-objectstore.pages.dev/api/v1/t0-weapons.json'; +/** Product browse (same skills as JSON) */ +export const WEAPON_SKILLS_HTML = 'https://info.grudge-studio.com/WEAPON_SKILLS.html'; + +/** Catalog ids for the two T0 magic starters (Mage Wand = class item, later) */ +export const T0_STARTER_WEAPON_IDS = Object.freeze({ + apprenticeWand: 't0-wand', + saplingStaff: 't0-nature-staff' +}); + +/** + * All catalog T0 starter weapon ids (api/v1/t0-weapons.json). + * Lab equip + `?t0=` · do not invent extra starters. + */ +export const T0_ALL_WEAPON_IDS = Object.freeze([ + 't0-sword', + 't0-axe1h', + 't0-dagger', + 't0-hammer1h', + 't0-spear', + 't0-greatsword', + 't0-greataxe', + 't0-hammer2h', + 't0-bow', + 't0-crossbow', + 't0-gun', + 't0-wand', + 't0-nature-staff', + 't0-tool', + 't0-fishing-pole', + 't0-offhand-tome' +]); + +/** + * Production mesh overrides (R2 prod/gltf) when catalog still points at family staff.glb. + * Uploaded 2026-08: arcane resonance → t0-wand · mushroom → t0-nature-staff · wand family. + */ +export const T0_MODEL_CDN = Object.freeze({ + 't0-wand': `${CDN}/prod/gltf/weapons/t0-wand.glb`, + 't0-nature-staff': `${CDN}/prod/gltf/weapons/t0-nature-staff.glb`, + /** Flintlock Pistol — local lab mesh until R2 prod upload */ + 't0-gun': './models/weapons/t0-flintlock.glb', + /** Fishing poles — Animated Fish Bundle (profession TOOL · Grudge Angler tiers) */ + 't0-fishing-pole': './models/fish/poles/fishing_rod.glb', + 'fishing-pole-t1': './models/fish/poles/fishing_rod-0yar0lg58p.glb', + 'fishing-pole-t2': './models/fish/poles/fishing_rod-9aohhrphe7.glb', + 'fishing-pole-t3': './models/fish/poles/fishing_rod-aoabqwh68m.glb', + 'fishing-pole-t4': './models/fish/poles/fishing_rod-ldlwqjn9zg.glb', + 'fishing-pole-t5': './models/fish/poles/fishing_rod-ldlwqjn9zg.glb' +}); + +/** category / id → kit mesh slot */ +export const WEAPON_TYPE_TO_MESH_SLOT = Object.freeze({ + SWORD: 'sword', + AXE: 'axe', + AXE_1H: 'axe', + DAGGER: 'sword', + MACE: 'hammer', + HAMMER: 'hammer', + HAMMER_1H: 'hammer', + HAMMER_2H: 'hammer', + SPEAR: 'spear', + GREATSWORD: 'sword', + GREATAXE: 'axe', + SCYTHE: 'axe', + BOW: 'bow', + LONGBOW: 'bow', + CROSSBOW: 'bow', + /** Handgun uses dedicated pistol anim pack (gunplay spin · draw · whip) */ + GUN: 'pistol', + RIFLE: 'bow', + PISTOL: 'pistol', + HANDGUN: 'pistol', + STAFF: 'staff', + WAND: 'staff', + TOME: 'staff', + NATURE_STAFF: 'staff', + TOOL: 'axe', + SHIELD: 'shield' +}); + +/** + * @typedef {object} WeaponSkillDef + * @property {string} id + * @property {string} name + * @property {string} [description] + * @property {number} [damage] + * @property {number} [cooldown] + * @property {number} [castTime] + * @property {string} [damageType] + * @property {string} [icon] + * @property {string[]} [effects] + * @property {object} [resourceCost] + * @property {'primary'|'secondary'|'ability'|'ultimate'|string} [slotType] + * @property {boolean} [fixed] + * @property {boolean} [choice] + */ + +/** + * @typedef {object} EquippableWeapon + * @property {string} id + * @property {string} name + * @property {number} tier + * @property {string} weaponType SWORD | WAND | … + * @property {string} meshSlot sword|axe|staff|bow|… + * @property {string} animPack sword_shield|magic|longbow + * @property {string} iconUrl + * @property {string|null} modelUrl + * @property {string|null} dropPrefabUrl + * @property {object} present presentPrefab() + * @property {WeaponSkillDef} slot1 + * @property {WeaponSkillDef} slot2 + * @property {WeaponSkillDef[]} slot3Options + * @property {string} defaultSlot3Id + * @property {object} [stats] + * @property {object} [rawPrefab] + * @property {object} [rawT0] + */ + +let _cache = null; +let _loading = null; + +/** Clear in-memory equippable catalog (e.g. after DO promote / Multiverse push). */ +export function clearEquippableWeaponsCache() { + _cache = null; + _loading = null; + if (typeof globalThis !== 'undefined') { + globalThis.__castingT0WeaponsCache = null; + } +} + +async function fetchJson(urls) { + for (const url of urls) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) continue; + return await res.json(); + } catch { + /* next */ + } + } + return null; +} + +/** + * Infer weaponType from t0 id / category. + * @param {object} w + */ +export function inferWeaponType(w) { + if (w.weaponType) return normalizeWeaponTypeId(w.weaponType); + const id = String(w.id || ''); + const cat = String(w.category || w.subCategory || ''); + if (/wand/i.test(id + cat)) return 'WAND'; + if (/nature.?staff|staff/i.test(id + cat)) return 'STAFF'; + if (/tome|offhand/i.test(id + cat)) return 'TOME'; + if (/greatsword/i.test(id + cat)) return 'GREATSWORD'; + if (/greataxe/i.test(id + cat)) return 'GREATAXE'; + if (/crossbow/i.test(id + cat)) return 'CROSSBOW'; + if (/bow/i.test(id + cat)) return 'BOW'; + if (/gun|rifle|pistol/i.test(id + cat)) return 'GUN'; + if (/dagger/i.test(id + cat)) return 'DAGGER'; + if (/spear/i.test(id + cat)) return 'SPEAR'; + if (/hammer|mace/i.test(id + cat)) return 'HAMMER'; + if (/axe/i.test(id + cat)) return 'AXE'; + if (/sword/i.test(id + cat)) return 'SWORD'; + if (/tool/i.test(id + cat)) return 'TOOL'; + return 'SWORD'; +} + +/** + * Mesh slot + anim pack for a weapon type. + * @param {string} weaponType + */ +export function labSlotsForWeaponType(weaponType) { + const wt = normalizeWeaponTypeId(weaponType); + const lab = labMapForWeaponType(wt); + const meshSlot = WEAPON_TYPE_TO_MESH_SLOT[wt] || lab.slot || 'sword'; + const animPack = WEAPON_SLOT_TO_PACK[meshSlot] || lab.pack || 'sword_shield'; + return { meshSlot, animPack, labStyle: lab.style, labElement: lab.element || null }; +} + +/** + * Normalize one skill blob from t0-weapons / master. + * @param {object} sk + * @param {string} slotType + * @param {object} [opts] + */ +export function normalizeSkillDef(sk, slotType = 'ability', opts = {}) { + if (!sk) return null; + // Catalog Instant = castTime null/undefined → 0. Do not invent 0.4/0.5. + const castRaw = sk.castTime; + const castTime = + castRaw == null || castRaw === '' || castRaw === 'Instant' + ? 0 + : Number(castRaw) || 0; + const cdRaw = sk.cooldown; + const cooldown = + cdRaw == null || cdRaw === '' || cdRaw === '—' ? 0 : Number(cdRaw) || 0; + return { + id: sk.id, + uuid: sk.uuid || null, + name: sk.name || sk.id, + description: sk.description || '', + damage: Number(sk.damage) || 0, + cooldown, + castTime, + range: sk.range == null || sk.range === '' || sk.range === '—' ? null : sk.range, + damageType: sk.damageType || 'physical', + /** Catalog projectile token: bullet | null — keep for gun / bow delivery */ + projectile: + sk.projectile == null || sk.projectile === '' || sk.projectile === '—' + ? null + : String(sk.projectile), + icon: sk.icon || opts.fallbackIcon || null, + iconUrl: iconCdnUrl(sk.icon || opts.fallbackIcon) || opts.fallbackIconUrl || null, + effects: Array.isArray(sk.effects) ? sk.effects.slice() : [], + resourceCost: sk.resourceCost || { mana: 0, stamina: 0 }, + slotType, + fixed: opts.fixed ?? false, + choice: opts.choice ?? false, + tier: sk.tier ?? 0, + animation: sk.animation || sk.prefab?.animationClip || null, + prefab: sk.prefab || null, + castingSpellId: sk.castingSpellId || null, + /** Provenance — never invent skill rows; only catalog or explicit stub */ + source: opts.source || 'catalog' + }; +} + +/** + * Build equippable from prefab + optional t0 body. + * @param {object} prefab + * @param {object|null} t0 + * @param {import('./weaponSkillsCatalog.js').WeaponSkillsCatalog|null} skillsCat + */ +export function buildEquippable(prefab, t0 = null, skillsCat = null) { + if (!prefab && !t0) return null; + const base = prefab || t0; + const id = base.id; + const weaponType = inferWeaponType({ ...t0, ...prefab, weaponType: prefab?.weaponType || t0?.weaponType }); + const lab = labSlotsForWeaponType(weaponType); + const present = presentPrefab( + prefab || { + id, + name: t0?.name || id, + tier: t0?.tier ?? 0, + weaponType, + assets: { iconUrl: t0?.iconUrl }, + modelUrl: null + } + ); + + const iconUrl = + present?.iconUrl || + t0?.iconUrl || + cdnUrl('icons/pack/weapons/Sword_01.png'); + + // Prefer authored T0 meshes on CDN over generic family staff.glb + const modelOverride = T0_MODEL_CDN[id] || null; + const modelUrl = + modelOverride || + t0?.modelUrl || + t0?.assets?.modelUrl || + present?.modelUrl || + null; + + let slot1 = null; + let slot2 = null; + /** @type {WeaponSkillDef[]} */ + let slot3Options = []; + + if (t0?.weaponSkills) { + const ws = t0.weaponSkills; + slot1 = normalizeSkillDef(ws.slot1, 'primary', { + fixed: true, + fallbackIconUrl: iconUrl + }); + slot2 = normalizeSkillDef(ws.slot2, 'secondary', { + fixed: true, + fallbackIconUrl: iconUrl + }); + slot3Options = (ws.slot3Options || []).map((s) => + normalizeSkillDef(s, 'ability', { choice: true, fallbackIconUrl: iconUrl }) + ); + } + + // Prefab skill ids → resolve from master catalog + if ((!slot1 || !slot2) && skillsCat && prefab?.skills?.slots) { + const byId = skillsCat.byId; + for (const block of prefab.skills.slots) { + const ids = block.skillIds || []; + const defs = ids.map((sid) => byId.get(sid)).filter(Boolean); + if (block.type === 'primary' && defs[0] && !slot1) { + slot1 = normalizeSkillDef(defs[0], 'primary', { fixed: true, fallbackIconUrl: iconUrl }); + } + if (block.type === 'secondary' && defs[0] && !slot2) { + slot2 = normalizeSkillDef(defs[0], 'secondary', { fixed: true, fallbackIconUrl: iconUrl }); + } + if (block.type === 'ability' && !slot3Options.length) { + slot3Options = defs.map((d) => + normalizeSkillDef(d, 'ability', { choice: true, fallbackIconUrl: iconUrl }) + ); + } + } + } + + // Fallback: master weapon type starters + if ((!slot1 || !slot2) && skillsCat) { + const list = (skillsCat.allSkills || []).filter( + (s) => s.weaponTypeId === weaponType && (s.tier === 0 || String(s.id).startsWith('t0_')) + ); + const prim = list.find((s) => s.slotType === 'primary') || list[0]; + const sec = list.find((s) => s.slotType === 'secondary') || list[1]; + const abs = list.filter((s) => s.slotType === 'ability'); + if (!slot1 && prim) + slot1 = normalizeSkillDef(prim, 'primary', { fixed: true, fallbackIconUrl: iconUrl }); + if (!slot2 && sec) + slot2 = normalizeSkillDef(sec, 'secondary', { fixed: true, fallbackIconUrl: iconUrl }); + if (!slot3Options.length && abs.length) { + slot3Options = abs.map((s) => + normalizeSkillDef(s, 'ability', { choice: true, fallbackIconUrl: iconUrl }) + ); + } + } + + // Absolute last resort stub — only if catalog fetch failed; marked source stub + if (!slot1) { + console.warn(`[t0WeaponCatalog] missing catalog slot1 for ${id} — stub only`); + slot1 = normalizeSkillDef( + { + id: `${id}_basic`, + name: 'Basic Attack', + damage: 10, + cooldown: 0.5, + castTime: 0, + damageType: lab.labStyle === 'spell' ? 'arcane' : 'physical' + }, + 'primary', + { fixed: true, fallbackIconUrl: iconUrl, source: 'stub-fallback' } + ); + } + if (!slot2) { + console.warn(`[t0WeaponCatalog] missing catalog slot2 for ${id} — stub only`); + slot2 = normalizeSkillDef( + { + id: `${id}_style`, + name: 'Stance', + damage: 0, + cooldown: 5, + castTime: 0, + damageType: slot1.damageType + }, + 'secondary', + { fixed: true, fallbackIconUrl: iconUrl, source: 'stub-fallback' } + ); + } + if (!slot3Options.length) { + console.warn(`[t0WeaponCatalog] missing catalog slot3 for ${id} — stub only`); + slot3Options = [ + normalizeSkillDef( + { + id: `${id}_ability`, + name: 'Ability', + damage: 12, + cooldown: 4, + castTime: 0, + damageType: slot1.damageType + }, + 'ability', + { choice: true, fallbackIconUrl: iconUrl, source: 'stub-fallback' } + ) + ]; + } + + const defaultSlot3Id = + t0?.weaponSkills?.defaultSlot3 || + t0?.defaultSlot3 || + slot3Options[0]?.id; + + return { + id, + /** GRUDGE ITEM-* identity (prefab or t0 row) */ + uuid: prefab?.uuid || t0?.uuid || present?.uuid || null, + baseUuid: prefab?.baseUuid || prefab?.uuid || t0?.uuid || null, + name: present?.name || t0?.name || prefab?.name || id, + tier: present?.tier ?? t0?.tier ?? 0, + weaponType, + meshSlot: lab.meshSlot, + animPack: lab.animPack, + labStyle: lab.labStyle, + labElement: lab.labElement, + iconUrl, + modelUrl, + dropPrefabUrl: present?.dropPrefabUrl || null, + present, + slot1, + slot2, + slot3Options, + defaultSlot3Id, + stats: prefab?.stats || t0?.stats || null, + description: t0?.description || prefab?.lore || prefab?.description || '', + rawPrefab: prefab || null, + rawT0: t0 || null, + assetCdn: CDN + }; +} + +/** + * Load all equippable T0 weapons from live catalog. + * **SSOT:** https://info.grudge-studio.com/api/v1/t0-weapons.json + * (+ master-weapon-prefabs for icons/models when present) + * Browse: WEAPON_SKILLS.html + * + * @returns {Promise<{ weapons: EquippableWeapon[], byId: Map, prefabCat: object, skillsCat: object }>} + */ +export async function loadEquippableWeapons() { + if (_cache) return _cache; + if (_loading) return _loading; + + _loading = (async () => { + const [prefabCat, skillsCat, t0Data] = await Promise.all([ + loadPrefabCatalog().catch(() => null), + loadWeaponSkillsCatalog().catch(() => null), + fetchJson(catalogJsonUrls('t0-weapons.json')) + ]); + + const t0List = t0Data?.weapons || []; + if (!t0List.length) { + console.warn('[t0WeaponCatalog] t0-weapons.json empty/unreachable — no starters'); + } + const t0ById = new Map(t0List.map((w) => [w.id, w])); + const prefabList = prefabCat?.list || []; + const prefabRawById = new Map(); + if (prefabCat?._rawPrefabs) { + for (const p of prefabCat._rawPrefabs) prefabRawById.set(p.id, p); + } + + // t0-weapons is skill SSOT; prefabs enrich icon/model. + // Lab admin (L20 · max T8): also load higher-tier prefabs that resolve skills + // so production push can smoke every icon / tree / equip path. + const maxTier = isLabAdmin() ? labMaxTier() : 0; + const ids = new Set([ + ...t0List.map((w) => w.id), + ...prefabList + .filter( + (p) => + p.tier === 0 || + String(p.id).startsWith('t0-') || + (maxTier > 0 && Number(p.tier) <= maxTier && p.weaponType) + ) + .map((p) => p.id) + ]); + + /** @type {EquippableWeapon[]} */ + const weapons = []; + let stubCount = 0; + let missingIcon = 0; + for (const id of ids) { + const present = prefabList.find((p) => p.id === id) || null; + const raw = prefabRawById.get(id) || present?.raw || null; + const t0 = t0ById.get(id) || null; + // Prefer t0 body (skills) + prefab mesh when available + const eq = buildEquippable(raw || present?.raw || present, t0, skillsCat); + if (eq) { + // Every skill must produce an icon for Main Panel tree + HUD + ensureWeaponSkillIcons(eq); + if ( + eq.slot1?.source === 'stub-fallback' || + eq.slot2?.source === 'stub-fallback' + ) { + stubCount += 1; + } + if (!eq.iconUrl) missingIcon += 1; + eq.catalogSource = { + t0: T0_WEAPONS_URL, + skillsHtml: WEAPON_SKILLS_HTML, + weaponId: id + }; + weapons.push(eq); + } + } + if (stubCount || missingIcon) { + console.warn( + `[t0WeaponCatalog] ${weapons.length} weapons · ${stubCount} with stub skills · ${missingIcon} missing weapon icon · maxTier=${maxTier}` + ); + } else { + console.info( + `[t0WeaponCatalog] ${weapons.length} weapons loaded · all skill icons resolved · maxTier=${maxTier}` + ); + } + + // Ensure the two magic starters always surface first when present + const priority = [T0_STARTER_WEAPON_IDS.apprenticeWand, T0_STARTER_WEAPON_IDS.saplingStaff]; + weapons.sort((a, b) => { + const pa = priority.indexOf(a.id); + const pb = priority.indexOf(b.id); + if (pa >= 0 || pb >= 0) return (pa < 0 ? 99 : pa) - (pb < 0 ? 99 : pb); + return a.name.localeCompare(b.name); + }); + let byId = new Map(weapons.map((w) => [w.id, w])); + let doMerge = { merged: 0, doMeta: null }; + + // Merge Cloudflare DO equip mirror (lab drafts + Multiverse push) — t0 wins on id + try { + const { mergeDoEquipCatalog } = await import('./weaponSkillDoApi.js'); + const merged = await mergeDoEquipCatalog({ weapons, byId }, { preferRemote: false }); + if (merged.merged > 0) { + weapons.length = 0; + weapons.push(...merged.weapons); + byId = merged.byId; + } + doMerge = { merged: merged.merged, doMeta: merged.doMeta, remoteCount: merged.remoteCount }; + } catch (e) { + console.warn('[t0WeaponCatalog] DO equip merge skipped', e?.message || e); + } + + _cache = { + weapons, + byId, + prefabCat, + skillsCat, + t0Data, + doMerge, + starters: { + apprenticeWand: byId.get(T0_STARTER_WEAPON_IDS.apprenticeWand) || null, + saplingStaff: byId.get(T0_STARTER_WEAPON_IDS.saplingStaff) || null + }, + loadedAt: Date.now() + }; + // Sync access for skill trees (wand / sapling) without re-fetch + if (typeof globalThis !== 'undefined') { + globalThis.__castingT0WeaponsCache = _cache; + } + _loading = null; + return _cache; + })(); + + return _loading; +} + +/** Sync read of last loaded equippable catalog (null until first load). */ +export function getEquippableWeaponsCache() { + return _cache || (typeof globalThis !== 'undefined' ? globalThis.__castingT0WeaponsCache : null) || null; +} + +/** + * Hotbar for a live catalog T0 id (e.g. t0-wand, t0-nature-staff). + * @param {string} weaponId + * @param {string} [slot3Id] + */ +export async function hotbarForCatalogWeaponId(weaponId, slot3Id) { + const cat = await loadEquippableWeapons(); + const w = cat.byId.get(weaponId); + if (!w) return []; + return hotbarForWeapon(w, slot3Id || w.defaultSlot3Id); +} + +/** + * Convert a skill def to DRC hotbar skill (combat runtime). + * @param {WeaponSkillDef} sk + * @param {number} barSlot + * @param {EquippableWeapon} weapon + */ +export function skillDefToDrc(sk, barSlot, weapon) { + if (!sk) return null; + + // Production compiler — anim + VFX + physics + statuses + delivery (catalog-first) + // Overrides: warmProductionOverrides([ids]) on equip, then cache is available here. + try { + const cachedOv = getCachedProductionOverride(sk.id); + const prod = compileProductionWeaponSkill(sk, { + weaponTypeId: weapon?.weaponType || weapon?.weaponTypeId, + animPack: weapon?.animPack, + labStyle: weapon?.labStyle, + barSlot, + overrides: cachedOv || sk.production || undefined + }); + if (prod) { + const drc = productionToDrcSkill(prod); + if (!drc.iconUrl && weapon?.iconUrl) drc.iconUrl = weapon.iconUrl; + drc.weaponId = weapon?.id; + drc.attachToHand = true; + drc.fixed = !!sk.fixed; + drc.choice = !!sk.choice; + drc.tier = sk.tier ?? 0; + drc.catalogUuid = sk.uuid || null; + drc.source = sk.source || 'catalog'; + drc.hint = `${sk.name} · ${weapon?.name || weapon?.id || ''} · production`; + return drc; + } + } catch (e) { + console.warn('[skillDefToDrc] production compile failed, legacy path', sk.id, e); + } + + // ── Legacy fallback (should rarely run) ───────────────────────────── + const labStyle = weapon?.labStyle || 'melee'; + const dmg = String(sk.damageType || '').toLowerCase(); + const nameId = `${sk.id} ${sk.name}`; + const isHeal = sk.damage < 0 || /heal|sprout|radiant|minor.?heal/i.test(nameId); + // Zero-damage utility / stance / aim — catalog buffs (not invented) + const isBuff = + !isHeal && + Number(sk.damage) === 0 && + /focus|stance|guard|ward|buff|shield|brace|aim|reload|cover|evade|wind.?up|lumber|pole.?guard|read.?page|take.?aim|take.?cover/i.test( + nameId + ); + /** Focus / Take Aim / Wind-Up / Power Stance = next-hit damage mul */ + const isFocus = + isBuff && /focus|take.?aim|wind.?up|power.?stance|read.?page/i.test(nameId); + /** Ward / guard / brace / cover / lumber = defense */ + const isWard = + isBuff && /ward|shield|guard|brace|cover|lumber|pole.?guard/i.test(nameId); + + let element = 'arcane'; + let abilityElement = null; + let style = 'melee'; + + if (labStyle === 'ranged') { + style = isBuff ? 'spell' : 'ranged'; + element = 'physical'; + abilityElement = null; + } else if (labStyle === 'spell' || /arcane|fire|frost|ice|holy|nature|lightning|water|earth|wind/.test(dmg)) { + style = 'spell'; + if (dmg === 'fire') { + element = 'fire'; + abilityElement = 'fire'; + } else if (dmg === 'frost' || dmg === 'ice' || dmg === 'water') { + element = 'ice'; + abilityElement = 'water'; + } else if (dmg === 'nature' || dmg === 'earth') { + element = 'nature'; + abilityElement = 'earth'; + } else if (dmg === 'lightning' || dmg === 'wind' || dmg === 'storm') { + element = 'storm'; + abilityElement = 'wind'; + } else if (dmg === 'holy') { + element = 'holy'; + abilityElement = 'wind'; + } else { + element = 'arcane'; + abilityElement = 'wind'; + } + } else { + // Melee / tool — buffs stay non-damage utility + style = isBuff || isHeal ? 'spell' : 'melee'; + element = isHeal ? 'nature' : 'physical'; + abilityElement = isHeal ? 'earth' : null; + } + + // Fill cast/travel/impact from WEAPON_SKILLS / staff school bind (catalog id only) + const staffB = bindFromCatalogSkill({ + id: sk.id, + name: sk.name, + description: sk.description, + damageType: sk.damageType, + effects: sk.effects, + cooldown: sk.cooldown, + castTime: sk.castTime, + range: sk.range, + damage: sk.damage, + slotType: sk.slotType, + resourceCost: sk.resourceCost + }); + + const catalogLike = { + ...sk, + labStyle, + labPack: weapon?.animPack, + slotType: sk.slotType, + castEffectId: staffB?.castEffectId, + travelEffectId: staffB?.travelEffectId, + impactEffectId: staffB?.impactEffectId + }; + + const vfx = vfxIdForSkill(catalogLike); + + return { + id: sk.id, + label: sk.name, + slot: barSlot, + style, + skillKind: isBuff ? 'buff' : isHeal ? 'heal' : style, + element: staffB?.element || element, + abilityElement: staffB ? staffB.element : abilityElement, + pathMode: isBuff || isHeal ? null : staffB?.pathMode || (style === 'spell' ? 'stream' : null), + presentation: staffB?.presentation || null, + animRole: isBuff || isHeal ? (labStyle === 'melee' || labStyle === 'ranged' ? 'block' : 'cast') : animRoleForSkill(catalogLike), + animPack: + weapon?.animPack || + (labStyle === 'spell' ? 'magic' : labStyle === 'ranged' ? 'longbow' : 'sword_shield'), + castClip: sk.animation || staffB?.castClip || 'magic/standing 1h cast spell 01', + rangeM: + sk.range || + staffB?.rangeM || + (style === 'spell' ? 12 : style === 'ranged' ? 18 : 3.2), + // Prefer catalog numbers; Instant castTime = 0 (do not invent cast windows) + cooldown: sk.cooldown ?? staffB?.cooldown ?? 0, + castDuration: + sk.castTime != null && sk.castTime !== '' + ? Number(sk.castTime) || 0 + : staffB?.castDuration ?? 0, + staminaCost: sk.resourceCost?.stamina ?? 0, + manaCost: sk.resourceCost?.mana ?? staffB?.manaCost ?? 0, + damage: sk.damage ?? 0, + castEffectId: staffB?.castEffectId || vfx, + travelEffectId: isBuff || isHeal ? null : staffB?.travelEffectId || vfx, + impactEffectId: staffB?.impactEffectId || vfx, + attachToHand: true, + weaponId: weapon?.id, + catalogSkillId: sk.id, + isFocus, + isWard, + focusDurationSec: isFocus ? 3 : isWard ? 2 : 0, + focusDamageMul: isFocus ? 1.35 : 1, + effects: sk.effects || [], + description: sk.description || '', + iconUrl: sk.iconUrl || weapon?.iconUrl, + tier: sk.tier ?? 0, + fixed: !!sk.fixed, + choice: !!sk.choice, + source: sk.source || 'info.grudge-studio.com/api/v1/t0-weapons.json', + catalogUuid: sk.uuid || null, + hint: `${sk.name} · ${weapon?.name || weapon?.id || ''} · catalog` + }; +} + +/** + * Guarantee every skill on a weapon has iconUrl (CDN or weapon fallback). + * Product: no weapon ships without skill icons for Main Panel + HUD. + * @param {EquippableWeapon} weapon + */ +export function ensureWeaponSkillIcons(weapon) { + if (!weapon) return weapon; + const fallback = + weapon.iconUrl || + cdnUrl('icons/pack/weapons/Sword_01.png'); + if (!weapon.iconUrl) weapon.iconUrl = fallback; + const patch = (sk) => { + if (!sk) return; + if (!sk.iconUrl) { + sk.iconUrl = iconCdnUrl(sk.icon) || fallback; + } + if (!sk.icon && sk.iconUrl) { + // keep path-ish for export when only URL present + sk.icon = sk.icon || null; + } + }; + patch(weapon.slot1); + patch(weapon.slot2); + for (const s of weapon.slot3Options || []) patch(s); + return weapon; +} + +/** + * Full skill list for Main Panel weapon skill tree (icons + tooltips). + * Slot1 · Slot2 · all Slot3 options — catalog only. + * @param {EquippableWeapon} weapon + * @param {string} [activeSlot3Id] + */ +export function weaponSkillTreeRows(weapon, activeSlot3Id) { + if (!weapon) return []; + ensureWeaponSkillIcons(weapon); + const active = + activeSlot3Id || weapon.defaultSlot3Id || weapon.slot3Options?.[0]?.id; + /** @type {object[]} */ + const rows = []; + if (weapon.slot1) { + rows.push({ + ...weapon.slot1, + barSlot: 0, + keyLabel: '1', + active: true, + treeRole: 'primary' + }); + } + if (weapon.slot2) { + rows.push({ + ...weapon.slot2, + barSlot: 1, + keyLabel: '2', + active: true, + treeRole: 'secondary' + }); + } + for (const s of weapon.slot3Options || []) { + rows.push({ + ...s, + barSlot: 2, + keyLabel: '3', + active: s.id === active, + treeRole: 'ability', + choice: true + }); + } + return rows; +} + +/** + * Tooltip body for a weapon skill (Main Panel + HUD title). + * @param {object} sk + * @param {EquippableWeapon} [weapon] + */ +export function weaponSkillTooltip(sk, weapon) { + if (!sk) return ''; + const parts = [ + sk.name || sk.label || sk.id, + sk.description || '', + [ + sk.damageType || '', + sk.damage != null ? `${sk.damage} dmg` : '', + sk.cooldown != null ? `CD ${sk.cooldown}s` : '', + sk.castTime != null && sk.castTime > 0 ? `cast ${sk.castTime}s` : 'Instant', + sk.range != null ? `rng ${sk.range}` : '' + ] + .filter(Boolean) + .join(' · '), + weapon ? `${weapon.name} · T${weapon.tier} · ${weapon.weaponType}` : '', + sk.slotType || sk.treeRole || '', + sk.source === 'stub-fallback' ? '⚠ stub — catalog missing skill body' : '' + ].filter(Boolean); + return parts.join('\n'); +} + +/** + * Hotbar for equipped weapon — **catalog skills only** (no invented rows). + * T0: slots 1–2 fixed, slot 3 = chosen option from slot3Options. + * Anim roles map presentation only; DMG/CD/cast/effects stay from JSON. + * @param {EquippableWeapon} weapon + * @param {string} [slot3Id] + */ +export function hotbarForWeapon(weapon, slot3Id) { + if (!weapon) return []; + ensureWeaponSkillIcons(weapon); + const s3 = + weapon.slot3Options.find((s) => s.id === slot3Id) || + weapon.slot3Options.find((s) => s.id === weapon.defaultSlot3Id) || + weapon.slot3Options[0]; + const bar = [ + skillDefToDrc(weapon.slot1, 0, weapon), + skillDefToDrc(weapon.slot2, 1, weapon), + skillDefToDrc(s3, 2, weapon) + ].filter(Boolean); + + // Always surface icon + tooltip fields on hotbar skills + for (const sk of bar) { + if (!sk.iconUrl) sk.iconUrl = weapon.iconUrl; + if (!sk.description && sk.id === weapon.slot1?.id) sk.description = weapon.slot1?.description; + if (!sk.description && sk.id === weapon.slot2?.id) sk.description = weapon.slot2?.description; + if (!sk.description && s3?.id === sk.id) sk.description = s3?.description; + sk.tooltip = weaponSkillTooltip( + { + name: sk.label, + description: sk.description, + damage: sk.damage, + damageType: sk.damageType, + cooldown: sk.cooldown, + castTime: sk.castDuration, + range: sk.rangeM, + slotType: sk.slot === 0 ? 'primary' : sk.slot === 1 ? 'secondary' : 'ability', + id: sk.id, + source: sk.source + }, + weapon + ); + } + + // Presentation only: anim role + effect-string hints (catalog numbers untouched) + applyT0Presentation(bar, weapon); + return bar; +} + +/** + * Map all T0 catalog skill names → anim roles / residual hints. + * Does not invent skills — only presentation for the 15 starters. + * @param {object[]} bar + * @param {EquippableWeapon} [weapon] + */ +export function applyT0Presentation(bar, weapon) { + const pack = weapon?.animPack || 'sword_shield'; + const isRangedPack = + pack === 'longbow' || pack === 'pistol' || weapon?.labStyle === 'ranged'; + for (const sk of bar) { + if (!sk) continue; + const idn = `${sk.id} ${sk.label}`.toLowerCase(); + const desc = String(sk.description || '').toLowerCase(); + const fx = (sk.effects || []).join(' ').toLowerCase(); + const blob = `${idn} ${fx} ${desc}`; + + // --- Slot 2 style / utility (dmg 0) --- + if (sk.damage === 0 || sk.isBuff || sk.isWard || sk.isFocus) { + if (/evade/.test(idn)) { + sk.animRole = 'dodgeB'; + sk.skillKind = 'buff'; + } else if (pack === 'magic') { + sk.animRole = sk.isFocus || /aim|reload|focus|read/.test(idn) ? 'cast' : 'block'; + } else if (isRangedPack) { + // Take Cover / Reload / Take Aim — block or cast (pistol kneel-ready later) + sk.animRole = + sk.isFocus || /aim|reload|focus|read/.test(idn) ? 'cast' : 'block'; + } else { + sk.animRole = 'block'; + } + const m = fx.match(/(\d+(?:\.\d+)?)\s*s/); + if (m) sk.focusDurationSec = Number(m[1]); + // Take Cover: default 2s ward if catalog effect has no number (still "-dmg taken 2s") + if (sk.isWard && !(sk.focusDurationSec > 0)) sk.focusDurationSec = 2; + continue; + } + + // --- Heals --- + if (sk.damage < 0 || sk.skillKind === 'heal') { + sk.animRole = 'cast'; + sk.style = 'spell'; + continue; + } + + // --- Ranged T0 (bow / xbow / flintlock pistol) --- + if ( + isRangedPack || + sk.style === 'ranged' || + sk.projectile === 'bullet' || + sk.projectileKind === 'bullet' || + sk.useBulletProjectile || + /shot|bolt|arrow|fire|pinning|rapid|piercing|knockback.?shot|burst|suppress/i.test(idn) + ) { + if (isRangedPack || sk.style === 'ranged' || sk.useBulletProjectile) { + sk.style = 'ranged'; + sk.animRole = 'attack'; + if (!sk.rangeM || sk.rangeM < 5) sk.rangeM = 18; + if (/knockback/i.test(fx + idn)) { + sk.knockbackMm = 280; + sk.knockupVy = 1.2; + } + // Multi-hit count from catalog text (Burst Fire = three-round → 3) + if (/double|rapid|burst|multi|three.?round|3.?round|triple/i.test(blob)) { + if (/three.?round|3.?round|triple/i.test(blob)) sk.multiHit = 3; + else if (/double|twin|2.?hit/i.test(blob)) sk.multiHit = 2; + else if (/burst|multi/i.test(blob)) sk.multiHit = sk.multiHit || 3; + else sk.multiHit = sk.multiHit || 2; + } + // Flintlock / gun: ensure bullet delivery flags survive presentation + if ( + pack === 'pistol' || + weapon?.id === 't0-gun' || + /t0_gun|flint|pistol|handgun/i.test(idn) || + sk.projectile === 'bullet' + ) { + sk.useBulletProjectile = true; + sk.projectileKind = 'bullet'; + if (sk.projectile == null) sk.projectile = 'bullet'; + } + continue; + } + } + + // --- Magic T0 (wand / staff / tome) --- + if (pack === 'magic' || sk.style === 'spell') { + sk.animRole = 'cast'; + sk.style = 'spell'; + continue; + } + + // --- Melee / tool starters (sword_shield pack) --- + // Order: specials before generic "practice/stab/thrust" so Backstab ≠ attack1 + if ( + /overhead|heavy.?blow|crushing|stagger|backstab|reach.?strike|quick.?thrust|high single|armor break/i.test( + idn + fx + ) + ) { + sk.animRole = 'attack3'; + } else if (/sweep|cleaving|spinning|wide.?arc|shockwave|cone|\baoe\b|poison/i.test(idn + fx)) { + sk.animRole = 'attack2'; + sk.residualAoe = sk.residualAoe || 1.3; + } else if ( + /practice|starter|^chop$|^mine$|^skin$|^pry$|practice.?chop|practice.?stab|practice.?smash|practice.?thrust|practice.?cleave|practice.?hew/i.test( + idn + ) + ) { + sk.animRole = 'attack1'; + } else { + sk.animRole = sk.animRole && sk.animRole !== 'attack' ? sk.animRole : 'attack1'; + } + + if (/extended reach|extended range|reach/i.test(fx) && (!sk.rangeM || sk.rangeM < 4)) { + sk.rangeM = 3.8; + } + if (/small aoe|aoe|cone/i.test(fx)) sk.residualAoe = sk.residualAoe || 1.2; + } +} + +/** + * Warlords / fleet weapon prefab export — full GRUDGE UUID contract + hotbar. + * @param {EquippableWeapon} weapon + * @param {{ slot3Id?: string }} [opts] + */ +export function exportWarlordsWeaponPrefab(weapon, opts = {}) { + if (!weapon) return null; + const slot3Id = opts.slot3Id || weapon.defaultSlot3Id; + const hotbar = hotbarForWeapon(weapon, slot3Id); + // Lazy import avoided circular issues — inline contract shape from raw rows + const raw = weapon.rawPrefab || weapon.rawT0 || {}; + const t0 = weapon.rawT0 || null; + const contract = { + version: '1.1.0', + kind: 'warlords-weapon-prefab', + generated: new Date().toISOString(), + uuids: { + item: weapon.uuid || raw.uuid || t0?.uuid || null, + baseItem: raw.baseUuid || weapon.uuid || t0?.uuid || null, + icon: raw.assets?.iconUuid || null, + recipe: raw.recipeUuid || null, + skills: [ + weapon.slot1?.uuid, + weapon.slot2?.uuid, + ...(weapon.slot3Options || []).map((s) => s.uuid) + ].filter(Boolean), + activeSlot3: (weapon.slot3Options || []).find((s) => s.id === slot3Id)?.uuid || null + }, + id: weapon.id, + name: weapon.name, + tier: weapon.tier, + weaponType: weapon.weaponType, + description: weapon.description, + stats: weapon.stats, + assets: { + iconUrl: weapon.iconUrl, + modelUrl: weapon.modelUrl, + dropPrefabUrl: weapon.dropPrefabUrl, + worldSprite: weapon.iconUrl, + meshSlot: weapon.meshSlot, + animPack: weapon.animPack, + dropPrefabR2Key: raw.assets?.dropPrefabR2Key || null, + worldDropVfxR2Key: raw.assets?.worldDropVfxR2Key || null + }, + presentation: { + iconUrl: weapon.iconUrl, + modelUrl: weapon.modelUrl, + borderColor: weapon.present?.borderColor, + glowColor: weapon.present?.glowColor, + tierLabel: weapon.present?.tierLabel + }, + slotPattern: weapon.tier === 0 ? 'three-slot-starter' : 'five-slot', + skills: { + slot1: weapon.slot1, + slot2: weapon.slot2, + slot3Options: weapon.slot3Options, + defaultSlot3Id: weapon.defaultSlot3Id, + activeSlot3Id: slot3Id, + hotbar, + skillUuids: [ + weapon.slot1?.uuid, + weapon.slot2?.uuid, + ...(weapon.slot3Options || []).map((s) => s.uuid) + ].filter(Boolean) + }, + runtime: { + meshSlot: weapon.meshSlot, + animPack: weapon.animPack, + labStyle: weapon.labStyle, + jobs: ['bag', 'equip', 'controller', 'hotbar', 'combat', 'craft', 'export'] + }, + lab: { + meshSlot: weapon.meshSlot, + animPack: weapon.animPack, + labStyle: weapon.labStyle, + liveLab: 'https://casting-abilities-threejs.vercel.app/' + }, + sources: { + prefabs: 'api/v1/master-weapon-prefabs.json', + t0: 'api/v1/t0-weapons.json', + skills: 'api/v1/master-weaponSkills.json', + registry: 'api/v1/master-registry.json' + } + }; + return contract; +} diff --git a/src/api/weaponPrefabContract.js b/src/api/weaponPrefabContract.js new file mode 100644 index 0000000..23df2be --- /dev/null +++ b/src/api/weaponPrefabContract.js @@ -0,0 +1,376 @@ +/** + * GRUDGE UUID + weapon prefab contract (Casting / Warlords). + * + * Strengthens pattern: every weapon prefab is a **linked UUID graph**, not just a name. + * Catalog SSOT: master-weapon-prefabs.json · t0-weapons.json · master-weaponSkills.json + * + * @see docs/WEAPON_PREFAB_UUID_SSOT.md + * @see docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md + */ + +import { cdnUrl, presentPrefab, tierPresent } from '../loot/prefabAssets.js'; +import { labMapForWeaponType, normalizeWeaponTypeId } from './weaponSkillsCatalog.js'; +import { WEAPON_SLOT_TO_PACK } from '../config/weaponAnimPack.js'; +import { WEAPON_TYPE_TO_MESH_SLOT } from './t0WeaponCatalog.js'; + +/** Official GRUDGE UUID prefixes (ObjectStore / registry). */ +export const GRUDGE_UUID_PREFIX = Object.freeze({ + item: 'ITEM-', + skill: 'SKIL-', + icon: 'ICON-', + recipe: 'RECP-', + attribute: 'ATTR-', + material: 'MATL-', + relic: 'RELC-', + mount: 'MNT-', + artifact: 'ARTF-', + enchant: 'ENCH-', + effect: 'VFX-' // when bound on skill.prefab.vfxRef +}); + +/** + * What a weapon prefab must **have** (data layers). + * @type {readonly string[]} + */ +export const WEAPON_PREFAB_REQUIRED_LAYERS = Object.freeze([ + 'identity', // uuid + id + name + tier + weaponType + 'stats', // damage / speed / crit / defense … + 'skills', // SKIL-* slots (T0 three-slot or T1+ five-slot) + 'assets', // icon (+ model when available) + 'runtime', // meshSlot · animPack · labStyle + 'loadout' // pattern three-slot-starter | five-slot +]); + +/** + * What a weapon prefab must **do** at runtime (consumers). + * @type {readonly { id: string, label: string, surface: string }[]} + */ +export const WEAPON_PREFAB_RUNTIME_JOBS = Object.freeze([ + { id: 'bag', label: 'Show in bag / world drop', surface: 'icon + tier presentation' }, + { id: 'equip', label: 'Equip main/off hand', surface: 'mesh_ids + hand GLB attach' }, + { id: 'controller', label: 'Drive controller pack', surface: 'animPack from weaponType' }, + { id: 'hotbar', label: 'Bind skill hotbar', surface: 'skillIds + skillUuids → DRC' }, + { id: 'combat', label: 'Fire skills in combat', surface: 'delivery · residual · cast' }, + { id: 'craft', label: 'Link craft / T0→T1', surface: 'recipeUuid · craftsInto' }, + { id: 'export', label: 'Export for Unity / Warlords', surface: 'full contract JSON' } +]); + +/** + * @param {string|null|undefined} u + * @param {string} [prefix] + */ +export function isGrudgeUuid(u, prefix) { + if (!u || typeof u !== 'string') return false; + if (prefix) return u.startsWith(prefix); + return /^(ITEM|SKIL|ICON|RECP|ATTR|MATL|RELC|MNT|ARTF|ENCH|VFX|PREFAB)-/i.test(u); +} + +/** + * Parse skill slot blocks from prefab.skills or prefab.loadout.bindings. + * @param {object} prefab + * @returns {{ type: string, label?: string, skillIds: string[], skillUuids: string[], fixed: boolean, choice: boolean }[]} + */ +export function prefabSkillSlots(prefab) { + const slots = + prefab?.skills?.slots || + prefab?.loadout?.bindings?.slots || + prefab?.loadout?.slots || + []; + return (slots || []).map((s) => ({ + type: s.type || 'ability', + label: s.label || s.type, + skillIds: [...(s.skillIds || [])], + skillUuids: [...(s.skillUuids || [])], + fixed: !!s.fixed, + choice: !!s.choice, + unlockTier: s.unlockTier ?? 0 + })); +} + +/** + * Flatten all SKIL-* on a prefab. + * @param {object} prefab + * @returns {string[]} + */ +export function prefabAllSkillUuids(prefab) { + const fromRoot = prefab?.skills?.skillUuids || []; + const fromSlots = prefabSkillSlots(prefab).flatMap((s) => s.skillUuids); + return [...new Set([...fromRoot, ...fromSlots].filter((u) => isGrudgeUuid(u, GRUDGE_UUID_PREFIX.skill)))]; +} + +/** + * Runtime bind surface from weaponType. + * @param {object} prefab + */ +export function runtimeBindFromPrefab(prefab) { + const wt = normalizeWeaponTypeId(prefab?.weaponType || prefab?.category || 'SWORD'); + const lab = labMapForWeaponType(wt); + const meshSlot = WEAPON_TYPE_TO_MESH_SLOT[wt] || lab.slot || 'sword'; + const animPack = WEAPON_SLOT_TO_PACK[meshSlot] || lab.pack || 'sword_shield'; + return { + weaponType: wt, + meshSlot, + animPack, + labStyle: lab.style || 'melee', + labElement: lab.element || null, + equipmentSlot: prefab?.ummorpg?.equipmentSlot || 'MainHand', + ummorpgClass: prefab?.ummorpg?.scriptableItemClass || 'WeaponItem' + }; +} + +/** + * Validate a master-weapon-prefabs (or merged T0) row. + * @param {object} prefab + * @returns {{ ok: boolean, score: number, max: number, missing: string[], warnings: string[], layers: Record }} + */ +export function validateWeaponPrefab(prefab) { + const missing = []; + const warnings = []; + const layers = { + identity: false, + stats: false, + skills: false, + assets: false, + runtime: false, + loadout: false + }; + + if (!prefab) { + return { ok: false, score: 0, max: 6, missing: ['prefab'], warnings, layers }; + } + + // identity + if (isGrudgeUuid(prefab.uuid, GRUDGE_UUID_PREFIX.item) && prefab.id && prefab.name != null) { + layers.identity = true; + } else { + if (!isGrudgeUuid(prefab.uuid, GRUDGE_UUID_PREFIX.item)) missing.push('uuid ITEM-*'); + if (!prefab.id) missing.push('id'); + if (prefab.name == null) missing.push('name'); + } + if (prefab.tier == null) warnings.push('tier missing (default 0)'); + if (!prefab.weaponType && !prefab.category) warnings.push('weaponType missing'); + + // stats + if (prefab.stats && typeof prefab.stats === 'object' && Object.keys(prefab.stats).length) { + layers.stats = true; + } else { + missing.push('stats'); + } + + // skills + const slots = prefabSkillSlots(prefab); + const skUuids = prefabAllSkillUuids(prefab); + if (slots.length >= 1 && (skUuids.length >= 1 || slots.some((s) => s.skillIds.length))) { + layers.skills = true; + if (skUuids.length < slots.filter((s) => s.skillIds.length).length) { + warnings.push('some skill slots lack skillUuids (ids only)'); + } + } else { + missing.push('skills.slots + SKIL-*'); + } + + // assets + const icon = + prefab.assets?.iconUrl || + prefab.assets?.iconR2Key || + prefab.icon?.cdnUrl || + prefab.icon?.path; + const model = + prefab.modelUrl || + prefab.prodGltfUrl || + prefab.assets?.modelUrl || + prefab.prodGltfKey || + prefab.assets?.modelR2Key; + if (icon) { + layers.assets = true; + if (!model) warnings.push('modelUrl/prodGltf missing (family fallback OK)'); + if (!prefab.assets?.iconUuid) warnings.push('iconUuid ICON-* not set'); + } else { + missing.push('assets.iconUrl'); + } + + // runtime (derivable) + const rt = runtimeBindFromPrefab(prefab); + if (rt.meshSlot && rt.animPack) layers.runtime = true; + else missing.push('runtime meshSlot/animPack'); + + // loadout pattern + const pattern = + prefab.skills?.slotPattern || + prefab.loadout?.pattern || + prefab.loadout?.slotPattern || + (Number(prefab.tier) === 0 ? 'three-slot-starter' : 'five-slot'); + if (pattern) layers.loadout = true; + else missing.push('loadout.pattern'); + + const score = Object.values(layers).filter(Boolean).length; + const max = WEAPON_PREFAB_REQUIRED_LAYERS.length; + return { + ok: score === max && missing.length === 0, + score, + max, + missing, + warnings, + layers, + pattern, + skillUuidCount: skUuids.length, + runtime: rt + }; +} + +/** + * Normalize catalog prefab → **full runtime contract** (UUID graph + jobs). + * @param {object} prefab raw master-weapon-prefabs entry + * @param {object} [t0] optional t0-weapons body (skills authority for starters) + * @returns {object} + */ +export function normalizeWeaponPrefabContract(prefab, t0 = null) { + const present = presentPrefab(prefab); + const report = validateWeaponPrefab(prefab || t0); + const rt = runtimeBindFromPrefab(prefab || t0 || {}); + const slots = prefabSkillSlots(prefab || {}); + // Prefer T0 skill bodies when provided (skill SSOT) + const t0Slots = t0?.weaponSkills + ? [ + { + type: 'primary', + label: 'Slot 1 · Starter Attack', + skillIds: t0.weaponSkills.slot1?.id ? [t0.weaponSkills.slot1.id] : [], + skillUuids: t0.weaponSkills.slot1?.uuid ? [t0.weaponSkills.slot1.uuid] : [], + fixed: true, + choice: false, + skill: t0.weaponSkills.slot1 || null + }, + { + type: 'secondary', + label: 'Slot 2 · Starter Style', + skillIds: t0.weaponSkills.slot2?.id ? [t0.weaponSkills.slot2.id] : [], + skillUuids: t0.weaponSkills.slot2?.uuid ? [t0.weaponSkills.slot2.uuid] : [], + fixed: true, + choice: false, + skill: t0.weaponSkills.slot2 || null + }, + { + type: 'ability', + label: 'Slot 3 · Choose One', + skillIds: (t0.weaponSkills.slot3Options || []).map((s) => s.id).filter(Boolean), + skillUuids: (t0.weaponSkills.slot3Options || []).map((s) => s.uuid).filter(Boolean), + fixed: false, + choice: true, + options: t0.weaponSkills.slot3Options || [] + } + ] + : null; + + const skillSlots = t0Slots || slots; + const allSkillUuids = [ + ...new Set( + [ + ...prefabAllSkillUuids(prefab || {}), + ...skillSlots.flatMap((s) => s.skillUuids || []), + t0?.weaponSkills?.slot1?.uuid, + t0?.weaponSkills?.slot2?.uuid, + ...((t0?.weaponSkills?.slot3Options || []).map((s) => s.uuid) || []) + ].filter(Boolean) + ) + ]; + + const tier = Number(prefab?.tier ?? t0?.tier ?? 0) || 0; + const pattern = + prefab?.skills?.slotPattern || + prefab?.loadout?.pattern || + (tier === 0 ? 'three-slot-starter' : 'five-slot'); + + return { + version: '1.1.0', + kind: 'grudge-weapon-prefab-contract', + generated: new Date().toISOString(), + /** UUID graph — resolve everything through these */ + uuids: { + item: prefab?.uuid || t0?.uuid || null, + baseItem: prefab?.baseUuid || prefab?.uuid || t0?.uuid || null, + icon: prefab?.assets?.iconUuid || null, + recipe: prefab?.recipeUuid || null, + skills: allSkillUuids, + attributes: prefab?.attributeUuids || prefab?.statConnections?.attributeUuids || [] + }, + identity: { + id: prefab?.id || t0?.id, + name: prefab?.name || t0?.name, + baseName: prefab?.baseName || t0?.baseName || prefab?.name, + tier, + tierLabel: prefab?.tierLabel || tierPresent(tier).label, + weaponType: rt.weaponType, + category: prefab?.category || t0?.category, + subCategory: prefab?.subCategory || t0?.subCategory, + lore: prefab?.lore || t0?.description || '', + source: prefab?.source || t0?.source || 'catalog' + }, + stats: prefab?.stats || t0?.stats || {}, + skills: { + slotPattern: pattern, + slots: skillSlots, + skillUuids: allSkillUuids, + craftsInto: prefab?.skills?.craftsInto || (tier === 0 ? 'T1' : null) + }, + assets: { + iconUrl: present?.iconUrl || t0?.iconUrl || null, + iconUuid: prefab?.assets?.iconUuid || null, + modelUrl: present?.modelUrl || null, + prodGltfUrl: prefab?.prodGltfUrl || prefab?.modelUrl || null, + dropPrefabR2Key: prefab?.assets?.dropPrefabR2Key || null, + worldDropVfxR2Key: prefab?.assets?.worldDropVfxR2Key || null, + cdn: 'https://assets.grudge-studio.com' + }, + runtime: { + ...rt, + jobs: WEAPON_PREFAB_RUNTIME_JOBS.map((j) => j.id) + }, + loadout: { + pattern, + role: prefab?.loadout?.role || (tier === 0 ? 'starter' : 'combat'), + noTierUpgrades: tier === 0 || !!prefab?.loadout?.noTierUpgrades, + ummorpg: prefab?.ummorpg || null + }, + validation: report, + presentation: present + ? { + borderColor: present.borderColor, + glowColor: present.glowColor, + tierLabel: present.tierLabel + } + : null, + sources: { + prefabs: 'api/v1/master-weapon-prefabs.json', + t0: 'api/v1/t0-weapons.json', + skills: 'api/v1/master-weaponSkills.json', + registry: 'api/v1/master-registry.json' + } + }; +} + +/** + * Completeness matrix for a list of prefabs (lab diagnostics). + * @param {object[]} prefabs + */ +export function summarizePrefabCompleteness(prefabs = []) { + const rows = prefabs.map((p) => { + const v = validateWeaponPrefab(p); + return { + id: p.id, + uuid: p.uuid, + tier: p.tier, + ok: v.ok, + score: `${v.score}/${v.max}`, + missing: v.missing, + warnings: v.warnings + }; + }); + const ok = rows.filter((r) => r.ok).length; + return { + total: rows.length, + complete: ok, + incomplete: rows.length - ok, + rows + }; +} diff --git a/src/api/weaponSkillDoApi.js b/src/api/weaponSkillDoApi.js new file mode 100644 index 0000000..3718cb8 --- /dev/null +++ b/src/api/weaponSkillDoApi.js @@ -0,0 +1,266 @@ +/** + * Cloudflare Durable Object client — weapon skill drafts + equipWeaponById mirror. + * + * Control plane (dev → production): https://casting.grudge.studio + * Public DO host (CNAME live): https://weapon-skills.grudge-studio.com + * Fallback: workers.dev + * + * Contracts: + * grudge.weaponSkillPrefabBundle/v1 (Multiverse exportDurableBundle) + * grudge.equipWeaponCatalog/v1 (mirrors Casting equipWeaponById) + * + * Override: VITE_WEAPON_SKILL_DO_URL + */ + +export const CASTING_LAB_ORIGIN = 'https://casting.grudge.studio'; +export const WEAPON_SKILL_DO_PUBLIC = 'https://weapon-skills.grudge-studio.com'; +export const WEAPON_SKILL_DO_WORKERS_DEV = + 'https://grudge-weapon-skill-drafts.grudge.workers.dev'; + +const DEFAULT_DO_BASE = + (typeof import.meta !== 'undefined' && import.meta.env?.VITE_WEAPON_SKILL_DO_URL) || + WEAPON_SKILL_DO_PUBLIC; + +/** @type {string} */ +let _base = String(DEFAULT_DO_BASE).replace(/\/+$/, ''); + +export function getWeaponSkillDoBase() { + return _base; +} + +export function setWeaponSkillDoBase(url) { + if (url) _base = String(url).replace(/\/+$/, ''); + return _base; +} + +/** + * Prefer public CNAME when healthy; fall back to workers.dev. + * @returns {Promise} + */ +export async function resolveWeaponSkillDoBase() { + const candidates = [ + (typeof import.meta !== 'undefined' && import.meta.env?.VITE_WEAPON_SKILL_DO_URL) || null, + WEAPON_SKILL_DO_PUBLIC, + WEAPON_SKILL_DO_WORKERS_DEV, + ].filter(Boolean); + + for (const base of candidates) { + try { + const r = await fetch(`${String(base).replace(/\/+$/, '')}/api/health`, { + mode: 'cors', + cache: 'no-store', + }); + if (r.ok) { + _base = String(base).replace(/\/+$/, ''); + return _base; + } + } catch { + /* try next */ + } + } + return _base; +} + +async function doFetch(path, opts = {}) { + const url = `${_base}${path.startsWith('/') ? path : `/${path}`}`; + const headers = { + Accept: 'application/json', + 'X-Grudge-Source': opts.source || 'casting.grudge.studio', + ...(opts.headers || {}), + }; + if (opts.body && !headers['Content-Type']) { + headers['Content-Type'] = 'application/json'; + } + const res = await fetch(url, { + method: opts.method || 'GET', + mode: 'cors', + cache: 'no-store', + headers, + body: opts.body ? JSON.stringify(opts.body) : undefined, + }); + const text = await res.text(); + let data = null; + try { + data = text ? JSON.parse(text) : null; + } catch { + data = { raw: text }; + } + if (!res.ok) { + const err = new Error(data?.message || data?.error || `DO ${res.status}`); + err.status = res.status; + err.data = data; + throw err; + } + return data; +} + +export async function healthCheck() { + return doFetch('/api/health'); +} + +export async function fetchBundle() { + return doFetch('/api/v1/bundle'); +} + +/** + * Push Multiverse exportDurableBundle (or partial) into the DO. + * @param {object} bundle + * @param {{ source?: string }} [opts] + */ +export async function putBundle(bundle, opts = {}) { + return doFetch('/api/v1/bundle', { + method: 'PUT', + body: bundle, + source: opts.source || 'casting.grudge.studio', + }); +} + +export async function fetchEquipCatalog() { + return doFetch('/api/v1/equip-catalog'); +} + +/** + * Replace equip catalog mirror (Casting equipWeaponById shape). + * @param {{ weapons: object[], version?: number }} catalog + */ +export async function putEquipCatalog(catalog, opts = {}) { + return doFetch('/api/v1/equip-catalog', { + method: 'PUT', + body: catalog, + source: opts.source || 'casting.grudge.studio', + }); +} + +/** Upsert one equippable weapon row. */ +export async function postEquipWeapon(weapon, opts = {}) { + return doFetch('/api/v1/equip-catalog/weapon', { + method: 'POST', + body: weapon, + source: opts.source || 'casting.grudge.studio', + }); +} + +/** + * Stamp production promote metadata on all skills + catalog. + * Called from Casting lab (dev → production handoff). + * @param {{ label?: string, note?: string }} [body] + */ +export async function promoteCatalog(body = {}, opts = {}) { + return doFetch('/api/v1/promote', { + method: 'POST', + body, + source: opts.source || 'casting.grudge.studio', + }); +} + +/** + * Merge DO equip catalog weapons into a live equippable list (byId Map + weapons[]). + * DO entries fill gaps; local t0 SSOT wins on id collision unless preferRemote. + * + * @param {{ weapons: object[], byId: Map }} catalog + * @param {{ preferRemote?: boolean }} [opts] + * @returns {Promise<{ weapons: object[], byId: Map, doMeta: object|null, merged: number }>} + */ +export async function mergeDoEquipCatalog(catalog, opts = {}) { + if (!catalog?.byId) { + return { weapons: catalog?.weapons || [], byId: catalog?.byId || new Map(), doMeta: null, merged: 0 }; + } + let remote = null; + try { + await resolveWeaponSkillDoBase(); + remote = await fetchEquipCatalog(); + } catch (e) { + console.warn('[weaponSkillDoApi] equip-catalog unavailable', e?.message || e); + return { ...catalog, doMeta: null, merged: 0 }; + } + + const preferRemote = !!opts.preferRemote; + let merged = 0; + const weapons = [...(catalog.weapons || [])]; + const byId = catalog.byId instanceof Map ? catalog.byId : new Map(Object.entries(catalog.byId || {})); + + for (const w of remote.weapons || []) { + if (!w?.id) continue; + const existing = byId.get(w.id); + if (existing && !preferRemote) continue; + const row = normalizeDoWeapon(w, existing); + if (existing && preferRemote) { + const i = weapons.findIndex((x) => x.id === w.id); + if (i >= 0) weapons[i] = row; + else weapons.push(row); + } else if (!existing) { + weapons.push(row); + } + byId.set(w.id, row); + merged++; + } + + if (typeof globalThis !== 'undefined') { + globalThis.__castingWeaponSkillDo = { + base: _base, + controlPlane: CASTING_LAB_ORIGIN, + equipCount: remote.count, + meta: remote.meta || null, + mergedAt: Date.now(), + }; + } + + return { + weapons, + byId, + doMeta: remote.meta || null, + merged, + remoteCount: remote.count, + contract: remote.contract, + }; +} + +function normalizeDoWeapon(w, existing) { + return { + ...(existing || {}), + id: w.id, + name: w.name || existing?.name || w.id, + weaponType: w.weaponType || existing?.weaponType || 'SWORD', + animPack: w.animPack || existing?.animPack || 'sword_shield', + meshSlot: w.meshSlot || existing?.meshSlot || 'sword', + modelUrl: w.modelUrl || existing?.modelUrl || null, + icon: w.icon || existing?.icon || null, + iconUrl: w.iconUrl || existing?.iconUrl || null, + defaultSlot3Id: w.defaultSlot3Id || existing?.defaultSlot3Id || null, + slot1: w.slot1 || existing?.slot1 || null, + slot2: w.slot2 || existing?.slot2 || null, + slot3Options: w.slot3Options || existing?.slot3Options || [], + skills: w.skills || existing?.skills || null, + source: w.source || 'do', + catalogSource: { + ...(existing?.catalogSource || {}), + durableObject: 'WeaponSkillDrafts', + doBase: _base, + controlPlane: CASTING_LAB_ORIGIN, + }, + }; +} + +/** + * Push local Casting equippable weapons into the DO mirror (lab → production handoff). + * @param {object[]} weapons + */ +export async function pushLocalEquipMirror(weapons, opts = {}) { + const list = (weapons || []).map((w) => ({ + id: w.id, + name: w.name, + weaponType: w.weaponType, + animPack: w.animPack, + meshSlot: w.meshSlot, + modelUrl: w.modelUrl, + icon: w.icon, + iconUrl: w.iconUrl, + defaultSlot3Id: w.defaultSlot3Id, + slot1: w.slot1, + slot2: w.slot2, + slot3Options: w.slot3Options, + skills: w.skills, + source: 'casting.grudge.studio', + })); + return putEquipCatalog({ weapons: list }, opts); +} diff --git a/src/api/weaponSkillsCatalog.js b/src/api/weaponSkillsCatalog.js new file mode 100644 index 0000000..c29ae66 --- /dev/null +++ b/src/api/weaponSkillsCatalog.js @@ -0,0 +1,280 @@ +/** + * Master weapon skills catalog — ObjectStore SSOT (not a fork). + * + * Browse: https://info.grudge-studio.com/WEAPON_SKILLS.html + * Docs: https://info.grudge-studio.com/docs + * JSON: https://info.grudge-studio.com/api/v1/master-weaponSkills.json (v3.1.0 · 268 skills) + * + * Icons: https://assets.grudge-studio.com{icon path} + * Staff runtime binds: combat/staffWeaponSkillsBind.js (fills empty prefab.vfxRef) + */ + +import { enrichStaffSkill } from '../combat/staffWeaponSkillsBind.js'; +import { catalogJsonUrls, sameOriginFleetUrl } from '../config/fleetEnv.js'; + +export const MASTER_WEAPON_SKILLS_URL = + 'https://info.grudge-studio.com/api/v1/master-weaponSkills.json'; +/** Live Pages — objectstore.grudge-studio.com/api/v1 is 404 */ +export const MASTER_WEAPON_SKILLS_MIRROR = + 'https://grudge-objectstore.pages.dev/api/v1/master-weaponSkills.json'; +export const WEAPON_SKILLS_HTML = 'https://info.grudge-studio.com/WEAPON_SKILLS.html'; +export const INFO_DOCS_URL = 'https://info.grudge-studio.com/docs'; +export const ICONS_CDN = 'https://assets.grudge-studio.com'; + +/** Weapon type id (catalog) → casting mesh slot + anim pack */ +export const WEAPON_TYPE_TO_LAB = Object.freeze({ + SWORD: { slot: 'sword', pack: 'sword_shield', style: 'melee' }, + AXE: { slot: 'axe', pack: 'sword_shield', style: 'melee' }, + AXE_1H: { slot: 'axe', pack: 'sword_shield', style: 'melee' }, + DAGGER: { slot: 'sword', pack: 'sword_shield', style: 'melee' }, + MACE: { slot: 'hammer', pack: 'sword_shield', style: 'melee' }, + HAMMER: { slot: 'hammer', pack: 'sword_shield', style: 'melee' }, + HAMMER_1H: { slot: 'hammer', pack: 'sword_shield', style: 'melee' }, + SPEAR: { slot: 'spear', pack: 'sword_shield', style: 'melee' }, + GREATSWORD: { slot: 'sword', pack: 'sword_shield', style: 'melee' }, + GREATAXE: { slot: 'axe', pack: 'sword_shield', style: 'melee' }, + HAMMER_2H: { slot: 'hammer', pack: 'sword_shield', style: 'melee' }, + SCYTHE: { slot: 'axe', pack: 'sword_shield', style: 'melee' }, + BOW: { slot: 'bow', pack: 'longbow', style: 'ranged' }, + LONGBOW: { slot: 'bow', pack: 'longbow', style: 'ranged' }, + CROSSBOW: { slot: 'bow', pack: 'longbow', style: 'ranged' }, + RIFLE: { slot: 'bow', pack: 'longbow', style: 'ranged' }, + PISTOL: { slot: 'pistol', pack: 'pistol', style: 'ranged' }, + GUN: { slot: 'pistol', pack: 'pistol', style: 'ranged' }, + HANDGUN: { slot: 'pistol', pack: 'pistol', style: 'ranged' }, + STAFF: { slot: 'staff', pack: 'magic', style: 'spell' }, + FIRE_STAFF: { slot: 'staff', pack: 'magic', style: 'spell', element: 'fire' }, + FROST_STAFF: { slot: 'staff', pack: 'magic', style: 'spell', element: 'ice' }, + HOLY_STAFF: { slot: 'staff', pack: 'magic', style: 'spell', element: 'holy' }, + LIGHTNING_STAFF: { slot: 'staff', pack: 'magic', style: 'spell', element: 'storm' }, + NATURE_STAFF: { slot: 'staff', pack: 'magic', style: 'spell', element: 'nature' }, + ARCANE_STAFF: { slot: 'staff', pack: 'magic', style: 'spell', element: 'arcane' }, + WAND: { slot: 'staff', pack: 'magic', style: 'spell', element: 'arcane' }, + TOME: { slot: 'staff', pack: 'magic', style: 'spell' }, + TOOL: { slot: 'axe', pack: 'sword_shield', style: 'melee' }, + SHIELD: { slot: 'shield', pack: 'sword_shield', style: 'melee' } +}); + +/** + * Normalize catalog weapon type id. + * @param {string} id + */ +export function normalizeWeaponTypeId(id) { + return String(id || '') + .trim() + .toUpperCase() + .replace(/[\s-]+/g, '_'); +} + +/** + * @param {string} iconPath + */ +export function iconCdnUrl(iconPath) { + if (!iconPath) return null; + if (/^https?:\/\//i.test(iconPath)) return sameOriginFleetUrl(iconPath); + const p = iconPath.startsWith('/') ? iconPath : `/${iconPath}`; + return sameOriginFleetUrl(`${ICONS_CDN}${p}`); +} + +/** + * @param {string} weaponTypeId + */ +export function labMapForWeaponType(weaponTypeId) { + const id = normalizeWeaponTypeId(weaponTypeId); + return ( + WEAPON_TYPE_TO_LAB[id] || + WEAPON_TYPE_TO_LAB[id.replace(/_STAFF$/, '_STAFF')] || + { slot: 'sword', pack: 'sword_shield', style: 'melee' } + ); +} + +/** + * Flatten one weapon type's slots into skill rows. + * @param {object} wt catalog weaponTypes[] entry + */ +export function flattenWeaponTypeSkills(wt) { + const typeId = normalizeWeaponTypeId(wt.id || wt.weaponType || wt.name); + const lab = labMapForWeaponType(typeId); + /** @type {object[]} */ + const out = []; + + const pushSkill = (sk, slotType, slotLabel) => { + if (!sk || !sk.id) return; + const casting = sk.prefab?.castingLab || sk.lab || null; + out.push({ + id: sk.id, + uuid: sk.uuid || null, + name: sk.name || sk.id, + description: sk.description || '', + icon: sk.icon || wt.icon || null, + iconUrl: iconCdnUrl(sk.icon || wt.icon), + tier: sk.tier ?? 1, + damage: sk.damage ?? 0, + cooldown: sk.cooldown ?? 0, + castTime: sk.castTime ?? 0, + range: sk.range ?? null, + damageType: sk.damageType || 'physical', + animation: sk.animation || sk.prefab?.animationClip || casting?.animationClip || null, + prefab: sk.prefab || null, + effects: sk.effects || [], + weaponTypeId: typeId, + weaponTypeName: wt.name || typeId, + slotType: slotType || 'ability', + slotLabel: slotLabel || slotType || '', + labPack: casting?.animPack || lab.pack, + labSlot: lab.slot, + labStyle: lab.style, + labElement: casting?.element || lab.element || null, + castingSpellId: sk.castingSpellId || casting?.castingSpellId || null, + castEffectId: sk.prefab?.castEffectId || casting?.castEffectId || null, + travelEffectId: sk.prefab?.travelEffectId || casting?.travelEffectId || null, + impactEffectId: sk.prefab?.impactEffectId || casting?.impactEffectId || null, + pathMode: casting?.pathMode || null + }); + }; + + for (const block of wt.slots || []) { + for (const sk of block.skills || []) pushSkill(sk, block.type, block.label); + } + for (const block of wt.starterSlots || []) { + for (const sk of block.skills || []) pushSkill(sk, block.type || 'starter', block.label || 'T0'); + } + // Lab: every STAFF skill gets pathMode / Ability / cast-travel-impact / anim + if (typeId === 'STAFF' || /_STAFF$/.test(typeId) || typeId === 'WAND' || typeId === 'TOME') { + for (const row of out) enrichStaffSkill(row); + } + return out; +} + +/** + * @typedef {object} WeaponSkillsCatalog + * @property {string} version + * @property {number} totalSkills + * @property {object[]} weaponTypes + * @property {object[]} allSkills + * @property {Map} byId + */ + +/** + * Load master catalog (cached). + * @returns {Promise} + */ +let _cache = null; +let _loading = null; + +export async function loadWeaponSkillsCatalog() { + if (_cache) return _cache; + if (_loading) return _loading; + + _loading = (async () => { + let data = null; + for (const url of catalogJsonUrls('master-weaponSkills.json')) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) continue; + data = await res.json(); + break; + } catch { + /* try next */ + } + } + if (!data) { + _loading = null; + throw new Error('master-weaponSkills.json unreachable'); + } + + const weaponTypes = data.weaponTypes || []; + /** @type {object[]} */ + const allSkills = []; + for (const wt of weaponTypes) { + allSkills.push(...flattenWeaponTypeSkills(wt)); + } + const byId = new Map(allSkills.map((s) => [s.id, s])); + + _cache = { + version: data.version || 'unknown', + totalSkills: data.totalSkills || allSkills.length, + weaponTypes, + allSkills, + byId, + loadedAt: Date.now() + }; + _loading = null; + return _cache; + })(); + + return _loading; +} + +/** + * Skills for a weapon type id (SWORD, BOW, …). + * @param {WeaponSkillsCatalog} cat + * @param {string} weaponTypeId + */ +export function skillsForWeaponType(cat, weaponTypeId) { + const id = normalizeWeaponTypeId(weaponTypeId); + return (cat?.allSkills || []).filter((s) => s.weaponTypeId === id); +} + +/** + * Pick a T0/starter 4-skill bar for a weapon type (primary→ultimate). + * @param {WeaponSkillsCatalog} cat + * @param {string} weaponTypeId + */ +export function defaultHotbarForWeaponType(cat, weaponTypeId) { + const list = skillsForWeaponType(cat, weaponTypeId); + const starters = list.filter((s) => String(s.id).startsWith('t0_') || s.tier === 0); + const pool = starters.length >= 4 ? starters : list; + const bySlot = { primary: [], secondary: [], ability: [], ultimate: [] }; + for (const s of pool) { + const k = s.slotType in bySlot ? s.slotType : 'ability'; + bySlot[k].push(s); + } + const pick = (arr, i) => arr[i] || pool[i] || null; + return [ + pick(bySlot.primary, 0) || pool[0] || null, + pick(bySlot.secondary, 0) || pool[1] || null, + pick(bySlot.ability, 0) || pool[2] || null, + pick(bySlot.ultimate, 0) || pool[3] || null + ].filter(Boolean); +} + +/** Infer anim role to play for a catalog skill. */ +export function animRoleForSkill(skill) { + if (!skill) return 'attack'; + if (skill.labStyle === 'spell') return 'cast'; + if (skill.animation && /cast|spell/i.test(skill.animation)) return 'cast'; + if (skill.slotType === 'defense' || /parry|guard|block/i.test(skill.id + skill.name)) return 'block'; + const blob = `${skill.id || ''} ${skill.name || ''} ${skill.weaponTypeId || ''}`.toLowerCase(); + // T0 pistol: primary = gunplay, empty = reload, secondary = cover/draw, power = charged / whip + if (/pistol|handgun|gun/.test(blob) || skill.labPack === 'pistol') { + if (skill.isReload || skill.skillKind === 'reload' || /reload/i.test(blob)) return 'reload'; + if (skill.slotType === 'secondary' || /draw|holster|cover/i.test(blob)) return 'draw'; + if (skill.slotType === 'ability' || /charge|power|special|suppress|fan/i.test(blob)) + return 'skill2'; + if (/whip|bash|melee/.test(blob)) return 'skill3'; + if (/spin|flourish|gunplay|burst|double|shot/i.test(blob)) return 'gunplay'; + return 'attack'; + } + return skill.labStyle === 'ranged' ? 'attack' : 'attack'; +} + +/** Infer VFX effect id from damage type / description / casting kit binds. */ +export function vfxIdForSkill(skill) { + if (!skill) return 'arcane_swirl'; + if (skill.impactEffectId) return skill.impactEffectId; + if (skill.travelEffectId) return skill.travelEffectId; + if (skill.castEffectId) return skill.castEffectId; + if (skill.prefab?.impactEffectId) return skill.prefab.impactEffectId; + if (skill.prefab?.vfxRef) return skill.prefab.vfxRef; + const t = String(skill.damageType || '').toLowerCase(); + const blob = `${skill.id} ${skill.name} ${skill.description}`.toLowerCase(); + if (t === 'fire' || /fire|ember|inferno/.test(blob)) return 'inferno'; + if (t === 'frost' || t === 'ice' || /frost|ice|freeze/.test(blob)) return 'frost_wave'; + if (t === 'lightning' || /storm|lightning|thunder/.test(blob)) return 'chain_lightning'; + if (t === 'holy' || /holy|light|moon/.test(blob)) return 'moon_beam'; + if (t === 'nature' || /nature|earth|quake/.test(blob)) return 'earth_surge'; + if (/slash|cleave|sweep/.test(blob)) return 'getsuga_slash'; + if (skill.labStyle === 'spell') return 'arcane_swirl'; + return 'getsuga_slash'; +} diff --git a/src/assets/generatedCatalog.js b/src/assets/generatedCatalog.js new file mode 100644 index 0000000..fe3258d --- /dev/null +++ b/src/assets/generatedCatalog.js @@ -0,0 +1,82 @@ +/** + * Consume three-generator baked props (CDN catalog + same-origin fallback). + * @see MolochDaGod/three-generator shared/fleetAssetClient.ts + */ + +import { sameOriginFleetUrl } from '../config/fleetEnv.js'; + +export const GENERATED_CATALOG_CDN = sameOriginFleetUrl( + 'https://assets.grudge-studio.com/catalogs/three-generator/fleet-catalog.json' +); + +export const GENERATED_CATALOG_LOCAL = './models/generated/catalog.json'; + +/** + * @typedef {{ id: string, name: string, meshUrl: string, colliderUrl?: string, kind?: string, tags?: string[] }} GeneratedAsset + * @typedef {{ version?: number, assets: GeneratedAsset[], source?: string }} GeneratedCatalog + */ + +/** + * @returns {Promise} + */ +export async function loadGeneratedCatalog() { + const urls = [GENERATED_CATALOG_CDN, GENERATED_CATALOG_LOCAL]; + let lastErr; + for (const url of urls) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) throw new Error(`HTTP ${res.status}`); + const json = await res.json(); + if (!Array.isArray(json.assets)) throw new Error('bad catalog'); + // Normalize relative mesh URLs for local catalog + json.assets = json.assets.map((a) => ({ + ...a, + meshUrl: absolutize(a.meshUrl, url), + colliderUrl: a.colliderUrl ? absolutize(a.colliderUrl, url) : undefined, + })); + json._fetchedFrom = url; + return json; + } catch (e) { + lastErr = e; + } + } + console.warn('[generatedCatalog] empty', lastErr); + return { version: 0, assets: [], source: 'empty' }; +} + +function absolutize(meshUrl, catalogUrl) { + if (!meshUrl) return meshUrl; + if (/^https?:\/\//i.test(meshUrl)) return meshUrl; + if (meshUrl.startsWith('./') || meshUrl.startsWith('/')) { + try { + return new URL(meshUrl, window.location.href).href; + } catch { + return meshUrl; + } + } + return meshUrl; +} + +/** + * Load first matching generated GLB into the scene via AssetLoader. + * @param {import('../loaders/AssetLoader.js').AssetLoader} assets + * @param {import('three').Scene|import('three').Object3D} parent + * @param {{ name?: string, position?: [number,number,number] }} [opts] + */ +export async function spawnGeneratedProp(assets, parent, opts = {}) { + const catalog = await loadGeneratedCatalog(); + let entry = catalog.assets[0]; + if (opts.name) { + const n = opts.name.toLowerCase(); + entry = + catalog.assets.find((a) => a.name.toLowerCase().includes(n)) || entry; + } + if (!entry?.meshUrl) throw new Error('no generated assets in catalog'); + + const gltf = await assets.loadGLTF(entry.meshUrl); + const root = gltf.scene; + root.name = `generated:${entry.name}`; + if (opts.position) root.position.set(...opts.position); + parent.add(root); + return { root, entry, catalog }; +} diff --git a/src/audio/skillSfx.js b/src/audio/skillSfx.js new file mode 100644 index 0000000..94ede48 --- /dev/null +++ b/src/audio/skillSfx.js @@ -0,0 +1,418 @@ +/** + * Weapon skill + ability SFX for Casting Lab. + * Local SSOT: public/audio/sfx/*.wav (user-provided). + * Unlock on first gesture (browser autoplay policy). + * + * Roles: + * cast_ramp — channel / cast start (ramp-up) + * cast_chant — longer cast / blood·arcane·holy flavor + * parry — C parry **attempt** melee/metal (not only success) + * parry_magic — magical ward / staff / lightning parry attempt + * impact_magic — magical hit/impact (3 variants, random) + * burn — soft **loop** while player has burn status (not fire impact) + * heal — heal / regenerate (two variants, random) + */ + +const BASE = '/audio/sfx'; + +/** @type {Record} */ +export const SKILL_SFX_URLS = { + cast_ramp: `${BASE}/cast-ramp.wav`, + cast_chant: `${BASE}/cast-chant.wav`, + parry: `${BASE}/parry.wav`, + parry_magic: `${BASE}/parry-magic.wav`, + impact_magic: [ + `${BASE}/impact-magic-a.wav`, + `${BASE}/impact-magic-b.wav`, + `${BASE}/impact-magic-c.wav`, + ], + burn: `${BASE}/burn.wav`, + heal: [`${BASE}/heal-a.wav`, `${BASE}/heal-b.wav`], +}; + +/** Optional CDN mirror later — same filenames under assets.grudge-studio.com/audio/casting/sfx/ */ +export const SKILL_SFX_CDN_BASE = 'https://assets.grudge-studio.com/audio/casting/sfx'; + +/** @type {Map} */ +const pools = new Map(); +let unlocked = false; +let muted = false; +let masterVol = 0.72; +let useCdnFallback = true; +/** Soft burn loop while player has burn status */ +let burnLoop = null; +let burnLoopActive = false; + +function resolveUrl(role, variantIndex = 0) { + const entry = SKILL_SFX_URLS[role]; + if (!entry) return null; + if (Array.isArray(entry)) { + const i = Math.max(0, variantIndex) % entry.length; + return entry[i]; + } + return entry; +} + +function poolKey(url) { + return url; +} + +/** + * @param {string} url + * @returns {HTMLAudioElement} + */ +function acquire(url) { + let list = pools.get(poolKey(url)); + if (!list) { + list = []; + pools.set(poolKey(url), list); + } + for (const a of list) { + if (a.paused || a.ended) return a; + } + const audio = new Audio(); + audio.preload = 'auto'; + audio.src = url; + audio.crossOrigin = 'anonymous'; + list.push(audio); + // Cap concurrent clones per url + if (list.length > 6) list.shift(); + return audio; +} + +/** + * Unlock / warm all role URLs (call after user gesture or on first play). + * @returns {Promise} + */ +export async function unlockSkillSfx() { + if (unlocked) return; + unlocked = true; + const urls = new Set(); + for (const v of Object.values(SKILL_SFX_URLS)) { + if (Array.isArray(v)) v.forEach((u) => urls.add(u)); + else urls.add(v); + } + await Promise.all( + [...urls].map( + (url) => + new Promise((resolve) => { + try { + const a = acquire(url); + a.volume = 0.001; + const p = a.play(); + if (p && typeof p.then === 'function') { + p.then(() => { + a.pause(); + a.currentTime = 0; + a.volume = masterVol; + resolve(); + }).catch(() => resolve()); + } else resolve(); + } catch { + resolve(); + } + }) + ) + ); +} + +export function setSkillSfxMuted(m) { + muted = !!m; + if (muted) setPlayerBurningSfx(false); +} + +export function setSkillSfxVolume(v) { + masterVol = Math.max(0, Math.min(1, Number(v) || 0)); +} + +/** + * Play a role one-shot. + * @param {string} role + * @param {{ volume?: number, rate?: number, variant?: number }} [opts] + * @returns {HTMLAudioElement|null} + */ +export function playSkillSfx(role, opts = {}) { + if (muted) return null; + const vol = (opts.volume != null ? opts.volume : 1) * masterVol; + if (vol <= 0.001) return null; + + let url = resolveUrl(role, opts.variant ?? (role === 'heal' ? Math.floor(Math.random() * 2) : 0)); + if (!url) return null; + + try { + if (!unlocked) { + // Fire-and-forget unlock; still try play + void unlockSkillSfx(); + } + const audio = acquire(url); + audio.pause(); + try { + audio.currentTime = 0; + } catch { + /* ignore */ + } + audio.volume = Math.min(1, Math.max(0, vol)); + if (opts.rate != null && Number.isFinite(opts.rate)) { + audio.playbackRate = Math.max(0.5, Math.min(2, opts.rate)); + } else { + audio.playbackRate = 1; + } + const p = audio.play(); + if (p && typeof p.catch === 'function') { + p.catch(() => { + // Local 404 → optional CDN path by filename + if (!useCdnFallback) return; + const name = url.split('/').pop(); + if (!name) return; + const cdn = `${SKILL_SFX_CDN_BASE}/${name}`; + if (cdn === url) return; + try { + const a2 = acquire(cdn); + a2.volume = audio.volume; + a2.playbackRate = audio.playbackRate; + void a2.play().catch(() => {}); + } catch { + /* ignore */ + } + }); + } + return audio; + } catch (e) { + console.warn('[skillSfx] play failed', role, e); + return null; + } +} + +/** + * Map weapon skill / element → SFX roles for cast begin. + * + * Default = **cast_ramp** (clean cast start). Impact uses impact_magic a/b/c. + * **cast_chant** is the blood/shaman clip — only for explicit dark/blood skills + * (it reads as a death moan on normal staff/spell casts — do not default to it). + * + * @param {{ element?: string, style?: string, id?: string, label?: string, isWard?: boolean }|null|undefined} skill + * @param {{ duration?: number }} [ctx] + */ +export function playForWeaponSkillCast(skill, ctx = {}) { + const el = String(skill?.element || skill?.abilityElement || '').toLowerCase(); + const id = String(skill?.id || skill?.label || '').toLowerCase(); + + if (skill?.isWard || /parry|block|guard|ward/.test(id)) { + playSkillSfx('parry', { volume: 0.85 }); + return 'parry'; + } + if (/heal|regen|restore|holy.?light|mend/.test(id + el) || el === 'holy') { + playSkillSfx('heal', { volume: 0.8 }); + return 'heal'; + } + // Explicit blood / death / void kits only — not arcane/storm/long cast + if (/blood|shaman|shadow.?bolt|death.?coil|void.?bolt|necromanc/.test(id)) { + playSkillSfx('cast_chant', { volume: 0.62 }); + return 'cast_chant'; + } + // All normal spells / fire / ice / storm / arcane / residual cast start + const rate = + el === 'fire' || /burn|flame|inferno|fire/.test(id) + ? 1.05 + : el === 'ice' || el === 'water' + ? 0.95 + : el === 'storm' || el === 'arcane' + ? 1.08 + : 1.0; + playSkillSfx('cast_ramp', { volume: 0.76, rate }); + return 'cast_ramp'; +} + +/** + * Path / ability cast (element spline). + * Prefer ramp + later impact_magic — never death/chant on path cast. + * @param {string} element + */ +export function playForElementCast(element) { + const el = String(element || '').toLowerCase(); + if (el === 'holy' || el === 'nature') { + playSkillSfx('heal', { volume: 0.55, rate: 1.1 }); + return; + } + const rate = + el === 'fire' ? 1.05 : el === 'storm' || el === 'arcane' ? 1.08 : el === 'ice' ? 0.95 : 1.0; + playSkillSfx('cast_ramp', { volume: 0.72, rate }); +} + +/** + * Magical impact one-shot (random of 3 user clips). + * @param {{ volume?: number, rate?: number, variant?: number }} [opts] + */ +export function playMagicImpactSfx(opts = {}) { + const n = Array.isArray(SKILL_SFX_URLS.impact_magic) ? SKILL_SFX_URLS.impact_magic.length : 3; + const variant = opts.variant != null ? opts.variant : Math.floor(Math.random() * n); + return playSkillSfx('impact_magic', { + volume: opts.volume != null ? opts.volume : 0.78, + rate: opts.rate != null ? opts.rate : 0.95 + Math.random() * 0.15, + variant, + }); +} + +/** + * Impact / residual by element or skill. + * Note: fire impact does **not** use burn.wav — that file is the soft burn loop on the player. + * Magical elements / spell residual use impact_magic (a/b/c random). + * @param {string} [element] + * @param {{ skill?: object, kind?: string }} [opts] + */ +export function playForImpact(element, opts = {}) { + const el = String(element || opts.skill?.element || '').toLowerCase(); + const kind = String(opts.kind || '').toLowerCase(); + const skill = opts.skill; + const style = String(skill?.style || '').toLowerCase(); + const idBlob = `${skill?.id || ''} ${skill?.label || ''}`.toLowerCase(); + + if (el === 'holy' || kind === 'heal') { + playSkillSfx('heal', { volume: 0.7 }); + return 'heal'; + } + + const isMagic = + kind === 'magic' || + kind === 'spell' || + kind === 'impact' || + style === 'spell' || + [ + 'fire', + 'ice', + 'water', + 'storm', + 'arcane', + 'holy', + 'nature', + 'earth', + 'wind', + ].includes(el) || + /spell|magic|bolt|orb|nova|staff|wand|arcane|lightning|flame/.test(idBlob); + + // Magical hits → pokemon-style impact pack (random a/b/c) + if (isMagic && kind !== 'melee') { + playMagicImpactSfx({ + volume: kind === 'bomb' || el === 'fire' || el === 'storm' ? 0.82 : 0.74, + }); + return 'impact_magic'; + } + + // Physical residual / melee: light cast-ramp tick only + if (kind === 'residual' || kind === 'melee') { + playSkillSfx('cast_ramp', { volume: 0.32, rate: 1.25 }); + return 'cast_ramp'; + } + + // Default: still prefer magic impact for unknown cast impacts + playMagicImpactSfx({ volume: 0.7 }); + return 'impact_magic'; +} + +/** + * Parry **attempt** (keydown / quick action) — always play when player tries. + * @param {{ magic?: boolean }|boolean} [opts] magic=true → lightning/wizard parry-magic.wav + */ +export function playParrySfx(opts = {}) { + const magic = opts === true || opts?.magic === true; + if (magic) { + return playSkillSfx('parry_magic', { volume: 0.88, rate: 1.0 }); + } + return playSkillSfx('parry', { volume: 0.9 }); +} + +export function playHealSfx() { + return playSkillSfx('heal', { volume: 0.85 }); +} + +/** + * Soft burning noise while the player is on fire (status). + * Loops quietly; stops when burn status ends. + * @param {boolean} active + * @param {{ volume?: number }} [opts] + */ +export function setPlayerBurningSfx(active, opts = {}) { + const want = !!active && !muted; + if (want === burnLoopActive && burnLoop) { + if (want && opts.volume != null) { + burnLoop.volume = Math.min(1, Math.max(0, opts.volume * masterVol)); + } + return burnLoop; + } + burnLoopActive = want; + + if (!want) { + if (burnLoop) { + try { + burnLoop.pause(); + burnLoop.currentTime = 0; + burnLoop.loop = false; + } catch { + /* ignore */ + } + burnLoop = null; + } + return null; + } + + try { + if (!unlocked) void unlockSkillSfx(); + const url = resolveUrl('burn'); + if (!url) return null; + const audio = acquire(url); + audio.loop = true; + // Soft under-player crackle — not impact loudness + audio.volume = Math.min(1, Math.max(0, (opts.volume != null ? opts.volume : 0.22) * masterVol)); + audio.playbackRate = 0.92; + try { + audio.currentTime = 0; + } catch { + /* ignore */ + } + const p = audio.play(); + if (p && typeof p.catch === 'function') p.catch(() => {}); + burnLoop = audio; + return audio; + } catch (e) { + console.warn('[skillSfx] burn loop', e); + burnLoop = null; + burnLoopActive = false; + return null; + } +} + +/** @deprecated Prefer setPlayerBurningSfx — burn.wav is status loop, not impact */ +export function playBurnSfx() { + return setPlayerBurningSfx(true, { volume: 0.22 }); +} + +/** Wire document gesture unlock once. */ +export function installSkillSfxGestureUnlock() { + if (typeof document === 'undefined') return; + const once = () => { + void unlockSkillSfx(); + document.removeEventListener('pointerdown', once); + document.removeEventListener('keydown', once); + }; + document.addEventListener('pointerdown', once, { passive: true }); + document.addEventListener('keydown', once, { passive: true }); +} + +export const SkillSfx = { + play: playSkillSfx, + playForWeaponSkillCast, + playForElementCast, + playForImpact, + playMagicImpact: playMagicImpactSfx, + playParry: playParrySfx, + playHeal: playHealSfx, + playBurn: playBurnSfx, + setPlayerBurning: setPlayerBurningSfx, + unlock: unlockSkillSfx, + setMuted: setSkillSfxMuted, + setVolume: setSkillSfxVolume, + installGestureUnlock: installSkillSfxGestureUnlock, + URLS: SKILL_SFX_URLS, +}; + +export default SkillSfx; diff --git a/src/character/BackSlotEquip.js b/src/character/BackSlotEquip.js new file mode 100644 index 0000000..70af470 --- /dev/null +++ b/src/character/BackSlotEquip.js @@ -0,0 +1,327 @@ +/** + * Back-slot equipment attach — same family as WeaponMeshAttach (hand), + * but for spine/back utilities (windsurf, glider, cape, pack). + * + * Industry layers (canonical-equipment-pattern): + * - Definition: slot = Back / Cloak / utility tag (ITEM-*) + * - Assets: modelUrl on CDN / local ride package + * - Presentation: stowed mesh on **quiver-family back bone** while land loco + * - Runtime: deploy → hide stow + vehicle seat (WalkController) + * + * Windsurf contract: + * - Equipped back item = **shrunk** `back_fly_windsurf.glb` on quiver bone + * - Sail = cloth material + vertex wind (SailCloth) — not a second physics body + * - Deploy (walk Space) = vehicle from HoverboardRide; stow hidden + * - Get-off (E) = vehicle removed; stow shown again + * + * @see docs/WINDSURF_RIDE_SSOT.md + * @see docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md (Back row) + * @see src/materials/SailCloth.js + * @see src/character/WeaponMeshAttach.js + */ + +import { Box3, Group, MathUtils, Vector3 } from 'three'; +import { clone as skeletonClone } from 'three/addons/utils/SkeletonUtils.js'; +import { + applySailClothMaterials, + setSailClothMode, + updateSailCloth +} from '../materials/SailCloth.js'; +import { sharedGltfLoader } from '../loaders/gltfPipeline.js'; +import { + BACK_MOBILITY_CATALOG, + canDeployBackItem, + getBackMobility +} from '../config/backSlotMobilitySsot.js'; + +const _box = new Box3(); +const _size = new Vector3(); + +/** + * Known back-slot item ids → default local models / SI stow. + * Stow size is **quiver-class** (back item), not full vehicle SI. + * Mobility domains: water (windsurf) · land (cape/shell) · air (wings). + * @see config/backSlotMobilitySsot.js + */ +export const BACK_SLOT_DEFS = Object.freeze( + Object.fromEntries( + Object.entries(BACK_MOBILITY_CATALOG).map(([id, m]) => [ + id, + { + id: m.id, + slot: 'back', + equipmentSlot: 'Back', + category: m.domain === 'water' ? 'utility_water' : m.domain === 'air' ? 'utility_air' : 'utility', + deployable: !!m.deployable, + vehicle: m.deployKind === 'vehicle_water' ? 'windsurf' : null, + domain: m.domain, + deployKind: m.deployKind, + modelUrl: m.modelUrl, + deployModelUrl: m.deployModelUrl || null, + stowLengthM: m.stowLengthM ?? 0.6, + stowOffset: m.stowOffset || [0, 0.08, -0.16], + stowEulerDeg: m.stowEulerDeg || [8, 180, 0], + label: m.label, + cloth: !!m.cloth, + flight: m.flight || null, + waterBuffs: m.waterBuffs || null, + notes: m.notes || '' + } + ]) + ) +); + +export { canDeployBackItem, getBackMobility }; + +/** + * Resolve back attach bone — **same family as quiver**. + * Prefer mesh parent of Xtra_quiver / quiver container, else Spine1/2. + * @param {import('three').Object3D} root + * @returns {import('three').Object3D|null} + */ +export function findBackBone(root) { + if (!root) return null; + const map = new Map(); + root.traverse((n) => { + if (n.name) map.set(n.name, n); + }); + + // 1) Quiver mesh exists → attach to its parent bone (true quiver slot) + let quiverMesh = null; + root.traverse((n) => { + if (quiverMesh) return; + const k = (n.name || '').toLowerCase().replace(/[^a-z0-9]/g, ''); + if ( + k.includes('xtraquiver') || + k.includes('quiver') || + k.includes('back_container') || + k.includes('backcontainer') + ) { + quiverMesh = n; + } + }); + if (quiverMesh) { + let p = quiverMesh.parent; + while (p && p !== root) { + if (p.isBone || /spine|chest|clavicle|bip001/i.test(p.name || '')) return p; + p = p.parent; + } + if (quiverMesh.parent) return quiverMesh.parent; + } + + // 2) Named bones (Toon RTS Bip001 — Spine1 is standard quiver height) + const prefer = [ + 'Bip001 Spine1', + 'Bip001_Spine1', + 'Bip001 Spine2', + 'Bip001_Spine2', + 'Bip001 Spine', + 'Bip001_Spine', + 'Spine2', + 'Spine1', + 'Spine', + 'mixamorig:Spine2', + 'mixamorig:Spine1', + 'mixamorig:Spine' + ]; + for (const name of prefer) { + if (map.has(name)) return map.get(name); + } + // Fuzzy + let found = null; + root.traverse((n) => { + if (found) return; + const k = (n.name || '').toLowerCase().replace(/[^a-z0-9]/g, ''); + if (k.includes('spine1') || k.includes('spine2') || k === 'bip001spine') found = n; + }); + return found; +} + +/** + * Manages stowed back-item mesh on the character kit. + */ +export class BackSlotEquip { + /** + * @param {import('three').Object3D|null} kitRoot character model root + */ + constructor(kitRoot = null) { + /** @type {import('three').Object3D|null} */ + this.kitRoot = kitRoot; + /** @type {import('three').Object3D|null} */ + this.backBone = kitRoot ? findBackBone(kitRoot) : null; + /** @type {Group|null} */ + this.holder = null; + /** @type {string} */ + this.itemId = 'none'; + /** @type {object|null} */ + this.def = null; + /** Vehicle deployed — stow mesh hidden */ + this.deployed = false; + this._loadToken = 0; + } + + /** + * Rebind after race kit swap. + * @param {import('three').Object3D|null} kitRoot + */ + rebind(kitRoot) { + this.clear(); + this.kitRoot = kitRoot; + this.backBone = kitRoot ? findBackBone(kitRoot) : null; + if (this.itemId && this.itemId !== 'none') { + this.equip(this.itemId).catch(() => {}); + } + } + + /** + * Equip a back-slot item by id (windsurf | none | future glider…). + * @param {string} itemId + * @param {{ modelUrl?: string }} [opts] + */ + async equip(itemId, opts = {}) { + const id = itemId || 'none'; + const base = BACK_SLOT_DEFS[id] || { + id, + slot: 'back', + equipmentSlot: 'Back', + deployable: true, + modelUrl: opts.modelUrl || null, + stowLengthM: 0.85, + stowOffset: [0, 0.08, -0.18], + stowEulerDeg: [12, 0, 0], + label: id + }; + this.def = { ...base, modelUrl: opts.modelUrl || base.modelUrl }; + this.itemId = id; + + if (id === 'none' || !this.def.modelUrl) { + this.clear(); + this.itemId = 'none'; + return null; + } + if (!this.backBone && this.kitRoot) this.backBone = findBackBone(this.kitRoot); + if (!this.backBone) { + console.warn('[BackSlotEquip] no spine bone — cannot stow', id); + return null; + } + + const token = ++this._loadToken; + try { + const gltf = await sharedGltfLoader().loadAsync(this.def.modelUrl); + if (token !== this._loadToken) return null; + const src = gltf.scene || gltf.scenes?.[0]; + if (!src) return null; + + this.clearMeshOnly(); + const clone = skeletonClone(src); + clone.name = 'BackSlotMesh'; + clone.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.receiveShadow = true; + o.frustumCulled = true; + } + }); + + // Materials: sRGB base maps, cloth sail PBR + wind patch + if (this.def.cloth !== false) { + applySailClothMaterials(clone, { forceCloth: id === 'windsurf' }); + setSailClothMode(clone, 'stow'); + } + + // SI stow: longest axis → stowLengthM (quiver-class ~0.58 m) + clone.updateMatrixWorld(true); + _box.setFromObject(clone); + _box.getSize(_size); + const longest = Math.max(_size.x, _size.y, _size.z, 0.01); + const maxLen = this.def.stowLengthM ?? 0.58; + const s = maxLen / longest; + // Guard absurd author scale (cm→m decade) + clone.scale.setScalar(Number.isFinite(s) && s > 0 && s < 50 ? s : 0.01); + + const holder = new Group(); + holder.name = 'BackSlotAttach'; + holder.userData.backSlot = true; + holder.userData.itemId = id; + holder.userData.equipmentSlot = 'Back'; + holder.userData.deployable = !!this.def.deployable; + holder.userData.stowLengthM = maxLen; + holder.add(clone); + + const off = this.def.stowOffset || [0.02, 0.06, -0.14]; + holder.position.set(off[0], off[1], off[2]); + const e = this.def.stowEulerDeg || [8, 180, 0]; + holder.rotation.set( + MathUtils.degToRad(e[0] || 0), + MathUtils.degToRad(e[1] || 0), + MathUtils.degToRad(e[2] || 0) + ); + + this.backBone.add(holder); + this.holder = holder; + this.setDeployed(this.deployed); + console.info( + '[BackSlotEquip] stowed', + id, + 'on', + this.backBone.name, + `len≈${maxLen}m scale=${(maxLen / longest).toFixed(4)}` + ); + return holder; + } catch (err) { + console.warn('[BackSlotEquip] load failed', this.def.modelUrl, err); + return null; + } + } + + /** + * Vehicle live → hide stow; land → show stow. + * @param {boolean} deployed + */ + setDeployed(deployed) { + this.deployed = !!deployed; + if (this.holder) this.holder.visible = !this.deployed && this.itemId !== 'none'; + } + + /** + * Cloth wind tick (stow visible only). Call from character update. + * @param {number} dt + * @param {{ wind?: number }} [opts] + */ + update(dt, opts = {}) { + if (!this.holder || !this.holder.visible) return; + updateSailCloth(this.holder, dt, { wind: opts.wind ?? 1, speed: 1 }); + } + + /** @returns {boolean} */ + get canDeploy() { + return !!(this.def?.deployable && this.itemId && this.itemId !== 'none'); + } + + clearMeshOnly() { + if (!this.holder) return; + const parent = this.holder.parent; + parent?.remove(this.holder); + this.holder.traverse((c) => { + if (c.geometry) c.geometry.dispose?.(); + if (c.material) { + const mats = Array.isArray(c.material) ? c.material : [c.material]; + for (const m of mats) m.dispose?.(); + } + }); + this.holder = null; + } + + clear() { + this.clearMeshOnly(); + this.itemId = 'none'; + this.def = BACK_SLOT_DEFS.none; + this.deployed = false; + } + + dispose() { + this.clear(); + this.kitRoot = null; + this.backBone = null; + } +} diff --git a/src/character/EquipmentManager.js b/src/character/EquipmentManager.js new file mode 100644 index 0000000..975cdc5 --- /dev/null +++ b/src/character/EquipmentManager.js @@ -0,0 +1,429 @@ +/** + * Toon RTS / grudge6 equipment = child mesh visibility (never body GLB swap). + * SSOT: grudge6-modular-characters + * + * Hard rules: + * - Hide ALL equippable first + * - Show only mesh_ids / loadout slots + * - NEVER show bag / wood / quiver unless carry mode (setCarryVisuals) + */ + +import { EQUIP_SLOTS, WEAPON_SLOTS } from '../config/assets.js'; +import { UTILITY_SLOTS } from '../config/grudge6SSOT.js'; + +const RACE_PREFIX = /^(wk|brb|orc|elf|ud|dwf)_/i; +/** Author names that must stay off for combat showcase */ +const UTILITY_NAME_RE = /xtra_?(bag|wood|quiver)|bone_bag|bone_wood|quiver/i; + +export function meshKey(name) { + return String(name || '') + .toLowerCase() + .replace(RACE_PREFIX, '') + .replace(/units_/g, '') + .replace(/xtra_/g, '') + .replace(/weapon_/g, 'weapon') + .replace(/[^a-z0-9]/g, ''); +} + +function keysMatch(a, b) { + if (!a || !b) return false; + if (a === b) return true; + return a.endsWith(b) || b.endsWith(a); +} + +/** + * Classify a mesh into a loadout slot + variant letter (or _default). + * @returns {{ slot: string, variant: string } | null} + */ +export function classifyMesh(name) { + const key = meshKey(name); + if (!key) return null; + + // Skip pure skeleton / helper names + if (key.startsWith('bip') || key.includes('container') || key === 'armature') return null; + + // grudge6 / Toon RTS: WK_Units_Body_A → bodya after meshKey + const slotPatterns = [ + { slot: 'body', re: /^(?:units)?body([a-z0-9])$/ }, + { slot: 'arms', re: /^(?:units)?arms([a-z0-9])$/ }, + { slot: 'legs', re: /^(?:units)?legs([a-z0-9])$/ }, + { slot: 'head', re: /^(?:units)?head([a-z0-9])$/ }, + { slot: 'shoulders', re: /^(?:units)?shoulderpads?([a-z0-9])$/ }, + { slot: 'sword', re: /^(?:weapon)?sword([a-z0-9])?$/ }, + { slot: 'axe', re: /^(?:weapon)?axe([a-z0-9])?$/ }, + { slot: 'hammer', re: /^(?:weapon)?hammer([a-z0-9])?$/ }, + { slot: 'spear', re: /^(?:weapon)?spear([a-z0-9])?$/ }, + { slot: 'staff', re: /^(?:weapon)?staff([a-z0-9])?$/ }, + { slot: 'bow', re: /^(?:weapon)?bow([a-z0-9])?$/ }, + { slot: 'shield', re: /^shield([a-z0-9])?$/ }, + { slot: 'quiver', re: /^quiver([a-z0-9])?$/ }, + { slot: 'bag', re: /^(?:xtra)?bag([a-z0-9])?$/ }, + { slot: 'wood', re: /^(?:xtra)?wood([a-z0-9])?$/ } + ]; + + for (const { slot, re } of slotPatterns) { + const m = key.match(re); + if (m) { + const variant = (m[1] || '_default').toUpperCase(); + return { slot, variant: variant === '_DEFAULT' ? '_default' : variant }; + } + } + + // Contains match — only armor/weapon keywords, require trailing letter when possible + const contains = [ + ['shoulderpads', 'shoulders'], + ['shoulder', 'shoulders'], + ['body', 'body'], + ['arms', 'arms'], + ['legs', 'legs'], + ['head', 'head'], + ['sword', 'sword'], + ['staff', 'staff'], + ['shield', 'shield'], + ['hammer', 'hammer'], + ['spear', 'spear'], + ['quiver', 'quiver'], + ['bow', 'bow'], + ['axe', 'axe'], + ['bag', 'bag'], + ['wood', 'wood'] + ]; + for (const [frag, slot] of contains) { + if (!key.includes(frag)) continue; + const m = key.match(new RegExp(`${frag}([a-z0-9])$`)); + const variant = m ? m[1].toUpperCase() : '_default'; + return { slot, variant }; + } + + return null; +} + +export class EquipmentManager { + /** + * @param {import('three').Object3D} root kit scene root + * @param {{ preserveVisibility?: boolean }} [opts] + * preserveVisibility: true when scaffold already equipped — do NOT hideAll + * (that was wiping the hero to invisible after a correct equip). + */ + constructor(root, opts = {}) { + this.root = root; + /** @type {Map} slot → meshes */ + this.bySlot = new Map(); + /** @type {Map>} slot → variant → meshes */ + this.bySlotVariant = new Map(); + /** @type {import('three').Object3D[]} */ + this.equippable = []; + /** @type {import('three').Object3D[]} bag/wood/quiver always tracked */ + this.utilityMeshes = []; + /** @type {Record} */ + this.loadout = {}; + /** When false (default combat), bag/wood/quiver stay hidden. */ + this.carryMode = false; + this._preserveVisibility = !!opts.preserveVisibility; + this._catalog(); + // Only nuke visibility on a fresh kit. After toonKitPlay deploy, meshes are + // already exclusive-equipped — hideAll here made the character disappear. + if (!this._preserveVisibility) { + this.hideAll(); + this.hideUtility(); + } else { + // Never hide armor/weapons during catalog; only strip utility + this.hideUtility(); + } + } + + _catalog() { + this.bySlot.clear(); + this.bySlotVariant.clear(); + this.equippable = []; + this.utilityMeshes = []; + + this.root.traverse((node) => { + if (!node.isMesh && !node.isSkinnedMesh) return; + const name = node.name || ''; + + // Always track utility by name even if classify misses + if (UTILITY_NAME_RE.test(name)) { + this.utilityMeshes.push(node); + } + + const info = classifyMesh(name); + if (!info) { + // Unclassified gear — only hide when building a fresh kit + if (/weapon|shield|units_|xtra_/i.test(name)) { + if (!this._preserveVisibility) node.visible = false; + this.equippable.push(node); + } + return; + } + + this.equippable.push(node); + if (!this.bySlot.has(info.slot)) this.bySlot.set(info.slot, []); + this.bySlot.get(info.slot).push(node); + + if (!this.bySlotVariant.has(info.slot)) this.bySlotVariant.set(info.slot, new Map()); + const vm = this.bySlotVariant.get(info.slot); + if (!vm.has(info.variant)) vm.set(info.variant, []); + vm.get(info.variant).push(node); + }); + } + + /** RTS worker carry visuals — bag/wood only while carrying. */ + setCarryVisuals({ bag = false, wood = false, quiver = false } = {}) { + this.carryMode = !!(bag || wood || quiver); + this.hideUtility(); + if (bag) this._show('bag', this.variantsFor('bag')[0] || '_default'); + if (wood) this._show('wood', this.variantsFor('wood')[0] || '_default'); + if (quiver) this._show('quiver', this.variantsFor('quiver')[0] || '_default'); + } + + /** Force bag / wood / quiver off (combat default). */ + hideUtility() { + for (const m of this.utilityMeshes) m.visible = false; + for (const slot of UTILITY_SLOTS) { + const list = this.bySlot.get(slot) || []; + for (const m of list) m.visible = false; + delete this.loadout[slot]; + } + // Name fallback for any that skipped catalog + this.root.traverse((n) => { + if ((!n.isMesh && !n.isSkinnedMesh) || !n.name) return; + if (UTILITY_NAME_RE.test(n.name)) n.visible = false; + }); + } + + /** Variants available for a slot (sorted). */ + variantsFor(slot) { + const vm = this.bySlotVariant.get(slot); + if (!vm) return []; + return [...vm.keys()].sort((a, b) => { + if (a === '_default') return -1; + if (b === '_default') return 1; + return a.localeCompare(b); + }); + } + + /** Hide every equippable mesh. */ + hideAll() { + for (const m of this.equippable) m.visible = false; + } + + /** + * Multiverse path: hide all → show exact mesh_ids (fuzzy name match). + * @param {string[]} meshIds e.g. ['WK_Units_Body_D', 'WK_weapon_staff_A'] + * @returns {{ matched: number, missing: string[], shown: string[] }} + */ + applyMeshIds(meshIds = []) { + this.hideAll(); + this.loadout = {}; + const wanted = (meshIds || []).map(String).filter(Boolean); + const missing = []; + const shown = []; + let matched = 0; + + for (const id of wanted) { + const want = meshKey(id); + let hit = null; + for (const m of this.equippable) { + if (keysMatch(meshKey(m.name), want) || m.name === id) { + hit = m; + break; + } + } + // Also search non-catalog meshes (rare naming) + if (!hit) { + this.root.traverse((n) => { + if (hit || (!n.isMesh && !n.isSkinnedMesh)) return; + if (n.name === id || keysMatch(meshKey(n.name), want)) hit = n; + }); + } + if (hit) { + hit.visible = true; + matched += 1; + shown.push(hit.name); + const info = classifyMesh(hit.name); + if (info) this.loadout[info.slot] = info.variant; + } else { + missing.push(id); + } + } + + // Fail-safe: at least one body + const anyBody = (this.bySlot.get('body') || []).some((m) => m.visible); + if (!anyBody) { + const bodies = this.variantsFor('body'); + if (bodies[0]) { + this._show('body', bodies[0]); + this.loadout.body = bodies[0]; + matched += 1; + } + } + + // Combat default: never leave bag/wood/quiver on unless carryMode + if (!this.carryMode) this.hideUtility(); + + return { + matched, + missing, + shown: shown.filter((n) => !UTILITY_NAME_RE.test(n)), + slots: Object.keys(this.loadout), + catalogSlots: [...this.bySlot.keys()] + }; + } + + /** + * Apply loadout: one variant per armor slot; exclusive weapons. + * @param {Record} loadout e.g. { body:'D', staff:'A' } + * @returns {{ matched: number, missing: string[] }} + */ + applyLoadout(loadout) { + this.loadout = { ...(loadout || {}) }; + this.hideAll(); + + const missing = []; + let matched = 0; + + // Exclusive weapon group: only one main weapon visible + const weaponKeys = WEAPON_SLOTS.filter((s) => this.loadout[s]); + let activeWeapon = null; + if (weaponKeys.includes('staff')) activeWeapon = 'staff'; + else if (weaponKeys.includes('bow')) activeWeapon = 'bow'; + else if (weaponKeys.length) activeWeapon = weaponKeys[0]; + + // Armor first so body exists before weapons + const armorSlots = ['body', 'arms', 'legs', 'head', 'shoulders']; + for (const slot of armorSlots) { + const variant = this.loadout[slot]; + if (!variant) continue; + const shown = this._show(slot, variant); + if (shown) matched += shown; + else missing.push(`${slot}:${variant}`); + } + + for (const [slot, variant] of Object.entries(this.loadout)) { + if (armorSlots.includes(slot)) continue; + if (WEAPON_SLOTS.includes(slot) && slot !== activeWeapon) continue; + const shown = this._show(slot, variant); + if (shown) matched += shown; + else missing.push(`${slot}:${variant}`); + } + + // Fail-safe: always one body variant so kit is never empty/invisible + const anyBody = (this.bySlot.get('body') || []).some((m) => m.visible); + if (!anyBody) { + const bodies = this.variantsFor('body'); + if (bodies[0]) { + this._show('body', bodies[0]); + this.loadout.body = bodies[0]; + matched += 1; + } + } + + if (!this.carryMode) this.hideUtility(); + + return { + matched, + missing, + slots: Object.keys(this.loadout), + catalogSlots: [...this.bySlot.keys()], + activeWeapon + }; + } + + _show(slot, variant) { + const vm = this.bySlotVariant.get(slot); + if (!vm) return 0; + + let list = vm.get(variant); + if (!list?.length && variant !== '_default') { + // try case-insensitive / letter-only + for (const [k, arr] of vm) { + if (k.toUpperCase() === String(variant).toUpperCase()) { + list = arr; + break; + } + } + } + if (!list?.length) { + // fallback: first available for slot + list = vm.values().next().value; + } + if (!list?.length) return 0; + + for (const mesh of list) mesh.visible = true; + return list.length; + } + + /** Toggle a single slot to a variant (updates loadout). */ + setSlot(slot, variant) { + if (WEAPON_SLOTS.includes(slot)) { + // clear other weapons + for (const w of WEAPON_SLOTS) delete this.loadout[w]; + } + if (!variant || variant === 'none') { + delete this.loadout[slot]; + } else { + this.loadout[slot] = variant; + } + return this.applyLoadout(this.loadout); + } + + /** Snapshot for UI. */ + getCatalogSummary() { + const slots = {}; + for (const slot of EQUIP_SLOTS) { + const variants = this.variantsFor(slot); + if (variants.length) { + slots[slot] = { + variants, + selected: this.loadout[slot] || null + }; + } + } + return slots; + } + + /** + * Find hand / shield containers for IK attach. + * @returns {{ rHand: import('three').Object3D|null, lHand: import('three').Object3D|null, shield: import('three').Object3D|null, pelvis: import('three').Object3D|null }} + */ + findBones() { + let rHand = null; + let lHand = null; + let shield = null; + let pelvis = null; + /** Prefer Bone instances (same name may exist as Object3D). */ + const preferBone = (cur, next) => { + if (!cur) return next; + if (next?.isBone && !cur.isBone) return next; + return cur; + }; + + this.root.traverse((n) => { + const name = n.name || ''; + if (/R_hand_container/i.test(name) || /^Bip001[\s_]R[\s_]Hand$/i.test(name)) { + rHand = preferBone(rHand, n); + } + if (/L_hand_container/i.test(name) || /^Bip001[\s_]L[\s_]Hand$/i.test(name)) { + lHand = preferBone(lHand, n); + } + if (/L_shield_container/i.test(name)) shield = shield || n; + if (/^Bip001[\s_]Pelvis$/i.test(name) || name === 'Bip001 Pelvis') { + pelvis = preferBone(pelvis, n); + } + }); + + return { rHand, lHand, shield, pelvis }; + } +} + +/** Fuzzy match helper exported for diagnostics. */ +export function matchMeshById(root, meshId) { + const want = meshKey(meshId); + let found = null; + root.traverse((n) => { + if (found || (!n.isMesh && !n.isSkinnedMesh)) return; + if (keysMatch(meshKey(n.name), want)) found = n; + }); + return found; +} diff --git a/src/character/HandIK.js b/src/character/HandIK.js new file mode 100644 index 0000000..f4b2330 --- /dev/null +++ b/src/character/HandIK.js @@ -0,0 +1,104 @@ +import { Quaternion, Vector3 } from 'three'; + +const _handWorld = new Vector3(); +const _parentWorld = new Vector3(); +const _toTarget = new Vector3(); +const _from = new Vector3(); +const _qa = new Quaternion(); +const _qb = new Quaternion(); + +/** + * Lightweight hand / cast-origin helpers for grudge6 kits. + * + * After AnimationMixer each frame: + * - expose R_hand_container / Bip001 R Hand as cast + weapon origin + * - optional soft aim of the hand toward a world target (cast path head) + * + * Not a second mixer. Full dual-foot CCD IK is optional fleet work. + */ +export class HandIK { + /** + * @param {import('three').Object3D} model kit root + * @param {{ rHand?: import('three').Object3D|null, lHand?: import('three').Object3D|null, pelvis?: import('three').Object3D|null }} bones + */ + constructor(model, bones = {}) { + this.model = model; + this.rHand = bones.rHand || null; + this.lHand = bones.lHand || null; + this.pelvis = bones.pelvis || null; + this.enabled = true; + /** Soft aim weight 0..1 toward world target while casting */ + this.aimWeight = 0; + this._aimTarget = new Vector3(); + } + + setBones(bones) { + this.rHand = bones.rHand || this.rHand; + this.lHand = bones.lHand || this.lHand; + this.pelvis = bones.pelvis || this.pelvis; + } + + /** World-space cast / weapon spawn point. */ + getCastOrigin(out = new Vector3()) { + if (this.rHand) { + this.rHand.getWorldPosition(out); + return out; + } + this.model.getWorldPosition(out); + out.y += 1.35; + return out; + } + + getCastQuaternion(out = new Quaternion()) { + if (this.rHand) { + this.rHand.getWorldQuaternion(out); + return out; + } + this.model.getWorldQuaternion(out); + return out; + } + + setAimTarget(x, y, z, weight = 0.4) { + this._aimTarget.set(x, y, z); + this.aimWeight = weight; + } + + clearAim() { + this.aimWeight = 0; + } + + /** + * Soft-rotate hand so bone→child (or hand local +Z) leans toward aim target. + * Runs after mixer so it layers on top of cast/idle clips. + */ + update() { + if (!this.enabled || this.aimWeight < 1e-3 || !this.rHand) return; + + const hand = this.rHand; + hand.getWorldPosition(_handWorld); + _toTarget.copy(this._aimTarget).sub(_handWorld); + if (_toTarget.lengthSq() < 1e-8) return; + _toTarget.normalize(); + + // Prefer bone→child as "current forward" + const child = hand.children.find((c) => c.isBone) || hand.children[0]; + if (child) { + child.getWorldPosition(_from).sub(_handWorld); + if (_from.lengthSq() < 1e-8) _from.set(0, 0, 1); + else _from.normalize(); + } else { + _from.set(0, 0, 1).applyQuaternion(hand.getWorldQuaternion(_qa)); + } + + _qb.setFromUnitVectors(_from, _toTarget); + hand.getWorldQuaternion(_qa); + _qa.premultiply(_qb); + + if (hand.parent) { + hand.parent.getWorldQuaternion(_qb).invert(); + _qa.premultiply(_qb); + } + + hand.quaternion.slerp(_qa, Math.min(1, this.aimWeight * 0.45)); + } +} diff --git a/src/character/RideIK.js b/src/character/RideIK.js new file mode 100644 index 0000000..1123ac1 --- /dev/null +++ b/src/character/RideIK.js @@ -0,0 +1,334 @@ +import { MathUtils, Quaternion, Vector3 } from 'three'; + +/** + * Post-mixer ride IK for Toon RTS Bip001 on the windsurf / hoverboard. + * + * Sockets from public/models/ride/ride.manifest.json: + * - Author sockets are mirrored vs Toon Bip001: + * L foot → footR · R foot → footL · R hand → sailBoomL · L hand → sailBoomR + * + * Gated by CharacterController.setRideActive — never runs during combat gait. + * Not a second AnimationMixer: soft two-bone aim after mixer.update only. + */ + +const _root = new Vector3(); +const _mid = new Vector3(); +const _end = new Vector3(); +const _target = new Vector3(); +const _pole = new Vector3(); +const _toTarget = new Vector3(); +const _elbow = new Vector3(); +const _dir = new Vector3(); +const _from = new Vector3(); +const _qa = new Quaternion(); +const _qb = new Quaternion(); +const _qc = new Quaternion(); +const _parentInv = new Quaternion(); +const _up = new Vector3(0, 1, 0); +const _fwd = new Vector3(0, 0, 1); + +function orientBone(bone, child, targetDir) { + child.getWorldPosition(_from).sub(bone.getWorldPosition(_root)); + if (_from.lengthSq() < 1e-10) return; + _from.normalize(); + + bone.getWorldQuaternion(_qa); + _qb.setFromUnitVectors(_from, targetDir); + _qa.premultiply(_qb); + if (bone.parent) { + bone.parent.getWorldQuaternion(_parentInv).invert(); + bone.quaternion.copy(_parentInv.multiply(_qa)); + } + bone.updateMatrixWorld(true); +} + +/** + * Two-bone IK: place end effector at world target; mid joint toward poleHint. + * @returns {boolean} + */ +export function solveTwoBone(upper, mid, end, target, poleHint) { + if (!upper || !mid || !end) return false; + + upper.getWorldPosition(_root); + mid.getWorldPosition(_mid); + end.getWorldPosition(_end); + + const upperLen = _root.distanceTo(_mid); + const lowerLen = _mid.distanceTo(_end); + if (upperLen < 1e-4 || lowerLen < 1e-4) return false; + + _target.copy(target); + _toTarget.subVectors(_target, _root); + let dist = _toTarget.length(); + const maxReach = upperLen + lowerLen - 1e-3; + const minReach = Math.abs(upperLen - lowerLen) + 1e-3; + dist = MathUtils.clamp(dist, minReach, maxReach); + if (_toTarget.lengthSq() < 1e-8) _toTarget.set(0, -1, 0); + _toTarget.normalize(); + + _pole.copy(poleHint); + _pole.addScaledVector(_toTarget, -_pole.dot(_toTarget)); + if (_pole.lengthSq() < 1e-8) { + _pole.crossVectors(_toTarget, _up); + if (_pole.lengthSq() < 1e-8) _pole.set(1, 0, 0); + } + _pole.normalize(); + + const along = (upperLen * upperLen - lowerLen * lowerLen + dist * dist) / (2 * dist); + const out = Math.sqrt(Math.max(0, upperLen * upperLen - along * along)); + _elbow.copy(_root).addScaledVector(_toTarget, along).addScaledVector(_pole, out); + + orientBone(upper, mid, _dir.copy(_elbow).sub(_root).normalize()); + mid.getWorldPosition(_mid); + orientBone(mid, end, _dir.copy(_target).sub(_mid).normalize()); + return true; +} + +export function pickBone(map, ...names) { + for (const n of names) { + if (map.has(n)) return map.get(n); + } + for (const n of names) { + const needle = n.toLowerCase().replace(/^bip001[\s_]*/i, '').replace(/_/g, ' '); + for (const [k, v] of map) { + const key = k.toLowerCase().replace(/^bip001[\s_]*/i, '').replace(/_/g, ' '); + if (key === needle || key.endsWith(needle) || k.toLowerCase().includes(needle)) return v; + } + } + return null; +} + +export class RideIK { + /** + * @param {import('three').Object3D} model kit root + */ + constructor(model) { + this.model = model; + this.enabled = false; + this.weight = 0; + /** 0..1 how hard feet plant / hands grip */ + this.footWeight = 0.98; + this.handWeight = 0.92; + /** Blend rate toward enabled target (higher = snappier mount) */ + this.blendRate = 10; + + this.chains = { + leftLeg: { upper: null, mid: null, end: null }, + rightLeg: { upper: null, mid: null, end: null }, + leftArm: { upper: null, mid: null, end: null }, + rightArm: { upper: null, mid: null, end: null } + }; + this.hips = null; + + /** @type {Record} world-space socket targets */ + this.targets = { + footL: new Vector3(), + footR: new Vector3(), + sailRail: new Vector3(), + sailBoomL: new Vector3(), + sailBoomR: new Vector3(), + deckCenter: new Vector3() + }; + + this.valid = false; + this._resolveBones(model); + } + + _resolveBones(root) { + if (!root) { + this.valid = false; + return; + } + this.model = root; + const map = new Map(); + root.traverse((n) => { + if (n.isBone || n.type === 'Bone') map.set(n.name, n); + }); + + this.chains.leftLeg = { + upper: pickBone(map, 'Bip001 L Thigh', 'Bip001_L_Thigh', 'LeftUpLeg', 'mixamorig:LeftUpLeg'), + mid: pickBone(map, 'Bip001 L Calf', 'Bip001_L_Calf', 'LeftLeg', 'mixamorig:LeftLeg'), + end: pickBone(map, 'Bip001 L Foot', 'Bip001_L_Foot', 'LeftFoot', 'mixamorig:LeftFoot') + }; + this.chains.rightLeg = { + upper: pickBone(map, 'Bip001 R Thigh', 'Bip001_R_Thigh', 'RightUpLeg', 'mixamorig:RightUpLeg'), + mid: pickBone(map, 'Bip001 R Calf', 'Bip001_R_Calf', 'RightLeg', 'mixamorig:RightLeg'), + end: pickBone(map, 'Bip001 R Foot', 'Bip001_R_Foot', 'RightFoot', 'mixamorig:RightFoot') + }; + this.chains.leftArm = { + upper: pickBone(map, 'Bip001 L UpperArm', 'Bip001_L_UpperArm', 'LeftArm', 'mixamorig:LeftArm'), + mid: pickBone(map, 'Bip001 L Forearm', 'Bip001_L_Forearm', 'LeftForeArm', 'mixamorig:LeftForeArm'), + end: pickBone( + map, + 'L_hand_container', + 'Bip001 L Hand', + 'Bip001_L_Hand', + 'LeftHand', + 'mixamorig:LeftHand' + ) + }; + this.chains.rightArm = { + upper: pickBone(map, 'Bip001 R UpperArm', 'Bip001_R_UpperArm', 'RightArm', 'mixamorig:RightArm'), + mid: pickBone(map, 'Bip001 R Forearm', 'Bip001_R_Forearm', 'RightForeArm', 'mixamorig:RightForeArm'), + end: pickBone( + map, + 'R_hand_container', + 'Bip001 R Hand', + 'Bip001_R_Hand', + 'RightHand', + 'mixamorig:RightHand' + ) + }; + this.hips = pickBone(map, 'Bip001 Pelvis', 'Bip001_Pelvis', 'Pelvis', 'Hips', 'mixamorig:Hips'); + // Bind local hip Y — grudge packs strip position tracks; mixer never restores this. + // Absolute offset only (never accumulate hips.position.y -= each frame). + this._hipBindY = this.hips ? this.hips.position.y : 0; + + const legs = + this.chains.leftLeg.upper && + this.chains.leftLeg.end && + this.chains.rightLeg.upper && + this.chains.rightLeg.end; + const arms = + (this.chains.leftArm.upper && this.chains.leftArm.end) || + (this.chains.rightArm.upper && this.chains.rightArm.end); + + this.valid = !!(legs || arms); + if (this.valid) { + console.info('[RideIK] bound', { + Lfoot: this.chains.leftLeg.end?.name, + Rfoot: this.chains.rightLeg.end?.name, + Lhand: this.chains.leftArm.end?.name, + Rhand: this.chains.rightArm.end?.name, + hips: this.hips?.name, + hipBindY: this._hipBindY + }); + } else { + console.warn('[RideIK] no usable chains on kit'); + } + } + + /** Re-index after kit reload. */ + rebind(model) { + this._resolveBones(model); + } + + /** + * @param {Record} worldSockets + */ + setTargets(worldSockets) { + for (const [key, v] of Object.entries(worldSockets || {})) { + if (!this.targets[key]) this.targets[key] = new Vector3(); + if (v && v.isVector3) this.targets[key].copy(v); + else if (v && Number.isFinite(v.x)) this.targets[key].set(v.x, v.y, v.z); + } + } + + /** + * @param {boolean} active + */ + setActive(active) { + this.enabled = !!active; + if (!active && this.weight < 0.05) this.weight = 0; + } + + /** + * Call AFTER mixer.update. Plants feet on deck + hands on boom. + * @param {number} dt + * @param {{ boardForward?: Vector3, boardLeft?: Vector3, hipDrop?: number }} [opts] + */ + update(dt, opts = {}) { + if (!this.valid) return; + if (!this.enabled && this.weight <= 1e-3) return; + + const target = this.enabled ? 1 : 0; + this.weight += (target - this.weight) * Math.min(1, dt * this.blendRate); + this.weight = MathUtils.clamp(this.weight, 0, 1); + if (this.weight < 1e-3) return; + + const w = this.weight; + const fw = this.footWeight * w; + const hw = this.handWeight * w; + + // Soft hip drop so knees can bend onto deck straps. + // MUST be absolute vs bind — rotation-only clips never rewrite bone.position. + const hipDrop = opts.hipDrop ?? 0.1; + if (this.hips && fw > 0.05) { + const bindY = Number.isFinite(this._hipBindY) ? this._hipBindY : 0; + this.hips.position.y = bindY - hipDrop * fw * 0.55; + this.hips.updateMatrixWorld(true); + } else if (this.hips && this.weight < 0.05 && Number.isFinite(this._hipBindY)) { + this.hips.position.y = this._hipBindY; + } + + const forward = opts.boardForward || _fwd; + // Board left (windward); default +X if caller did not pass heading + const leftDir = opts.boardLeft || new Vector3(1, 0, 0); + + // Author sockets are mirrored vs Toon Bip001: swap L/R feet and L/R hands. + if (this.targets.footR && this.chains.leftLeg.upper) { + _pole + .copy(forward) + .multiplyScalar(0.32) + .addScaledVector(leftDir, -0.62) + .addScaledVector(_up, 0.18); + this._blendSolve(this.chains.leftLeg, this.targets.footR, _pole, fw); + } + if (this.targets.footL && this.chains.rightLeg.upper) { + _pole + .copy(forward) + .multiplyScalar(0.32) + .addScaledVector(leftDir, 0.62) + .addScaledVector(_up, 0.18); + this._blendSolve(this.chains.rightLeg, this.targets.footL, _pole, fw); + } + + // Hands: R → port boom, L → starboard boom (sockets are mirrored) + const handRTarget = this.targets.sailBoomL || this.targets.sailRail; + const handLTarget = + this.targets.sailBoomR || + (handRTarget && this.targets.sailRail !== handRTarget ? this.targets.sailRail : null); + + if (handRTarget && this.chains.rightArm.upper) { + _pole + .copy(leftDir) + .multiplyScalar(-1.25) + .addScaledVector(_up, -0.28) + .addScaledVector(forward, 0.12); + this._blendSolve(this.chains.rightArm, handRTarget, _pole, hw); + } + if ( + handLTarget && + this.chains.leftArm.upper && + (!handRTarget || handLTarget.distanceToSquared(handRTarget) > 0.04) + ) { + _pole + .copy(leftDir) + .multiplyScalar(1.25) + .addScaledVector(_up, -0.28) + .addScaledVector(forward, 0.12); + this._blendSolve(this.chains.leftArm, handLTarget, _pole, hw * 0.95); + } + } + + _blendSolve(chain, target, pole, weight) { + const { upper, mid, end } = chain; + if (!upper || !mid || !end || weight < 1e-3) return; + + const u0 = upper.quaternion.clone(); + const m0 = mid.quaternion.clone(); + const e0 = end.quaternion.clone(); + + solveTwoBone(upper, mid, end, target, pole); + + const u1 = upper.quaternion.clone(); + const m1 = mid.quaternion.clone(); + const e1 = end.quaternion.clone(); + upper.quaternion.copy(u0).slerp(u1, weight); + mid.quaternion.copy(m0).slerp(m1, weight); + end.quaternion.copy(e0).slerp(e1, weight); + upper.updateMatrixWorld(true); + mid.updateMatrixWorld(true); + end.updateMatrixWorld(true); + } +} diff --git a/src/character/WeaponMeshAttach.js b/src/character/WeaponMeshAttach.js new file mode 100644 index 0000000..7fbcd71 --- /dev/null +++ b/src/character/WeaponMeshAttach.js @@ -0,0 +1,264 @@ +/** + * Attach catalog weapon GLB to R_hand_container for equip preview / prefab QA. + * Uses prod modelUrl from master-weapon-prefabs (SI scale). + * + * Wands/staffs: prefer controlled length; allow slightly wider silhouettes + * (mushroom / resonance heads) without becoming 100× giants. + */ + +import { Group, Box3, Vector3, MathUtils, Object3D } from 'three'; +import { sharedGltfLoader } from '../loaders/gltfPipeline.js'; +import { FLINTLOCK_FIRE } from '../config/pistolAnimSsot.js'; + +const _box = new Box3(); +const _size = new Vector3(); +const _handW = new Vector3(); +const _corner = new Vector3(); +const _best = new Vector3(); + +/** + * @param {import('three').Object3D|null} handBone + * @param {string|null} modelUrl + * @param {{ + * maxLengthM?: number, + * maxWidthM?: number, + * profile?: 'melee'|'wand'|'staff'|'bow'|'pistol'|'shield', + * clear?: boolean + * }} [opts] + * @returns {Promise} + */ +export async function attachWeaponModel(handBone, modelUrl, opts = {}) { + if (!handBone) return null; + clearWeaponAttach(handBone); + + if (!modelUrl) return null; + + const urlLow = String(modelUrl).toLowerCase(); + const profile = + opts.profile || + (/t0-wand|wand\.glb|apprentice/i.test(urlLow) + ? 'wand' + : /t0-nature|staff|sapling|mushroom/i.test(urlLow) + ? 'staff' + : /pistol|handgun/i.test(urlLow) + ? 'pistol' + : /bow|crossbow/i.test(urlLow) + ? 'bow' + : /gun|rifle/i.test(urlLow) + ? 'pistol' + : /shield/i.test(urlLow) + ? 'shield' + : 'melee'); + + // SI: human ~1.8 m — pistol handgun short; wand/staff longer + const maxLen = + opts.maxLengthM ?? + (profile === 'wand' + ? 0.95 + : profile === 'staff' + ? 1.25 + : profile === 'pistol' + ? 0.48 // flintlock SI hand length + : profile === 'bow' + ? 1.4 + : 1.2); + const maxWidth = + opts.maxWidthM ?? + (profile === 'wand' || profile === 'staff' + ? 0.55 + : profile === 'pistol' + ? 0.28 // flintlock barrel + lock + : 0.4); + + try { + // Shared Draco/Meshopt/KTX2 — do not new bare GLTFLoader (compressed CDN weapons) + const gltf = await sharedGltfLoader().loadAsync(modelUrl); + const root = gltf.scene || gltf.scenes?.[0]; + if (!root) return null; + + // Separate mesh nodes stay named (do not merge) — useful for slot tint later + root.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.receiveShadow = true; + o.frustumCulled = true; + } + }); + + const holder = new Group(); + holder.name = 'WeaponAttach'; + holder.userData.weaponAttach = true; + holder.userData.profile = profile; + holder.userData.modelUrl = modelUrl; + holder.add(root); + + // Normalize length first, then soft-cap width (willing wider than long for heads) + _box.setFromObject(root); + _box.getSize(_size); + const longest = Math.max(_size.x, _size.y, _size.z, 0.01); + let s = maxLen / longest; + root.scale.setScalar(s); + _box.setFromObject(root); + _box.getSize(_size); + const width = Math.max(_size.x, _size.z); + if (width > maxWidth) { + s *= maxWidth / width; + root.scale.setScalar(s); + } + + // Grip: shaft along +Y local (Toon R_hand) + // Pistol: barrel should read forward of grip after orient — same -90 pitch as melee + root.rotation.x = MathUtils.degToRad(profile === 'bow' ? -75 : -90); + if (profile === 'pistol') { + // Slight yaw so flintlock sits across palm → barrel out from body + root.rotation.z = MathUtils.degToRad(8); + } + root.position.set(0, 0, 0); + + // SI fit metadata — lab scale editor multiplies this base + holder.userData._fitScale = 1; + holder.userData._appBaseScale = 1; + holder.userData.fitLengthM = maxLen; + holder.userData.sourceLongestM = longest; + holder.userData.meshFitScale = s; + root.userData.meshFitScale = s; + + // Parent first so world AABB / hand origin are valid for muzzle tip + handBone.add(holder); + // Barrel tip marker (muzzle) — farthest mesh extent from hand grip + placeMuzzleMarker(holder, profile); + handBone.updateWorldMatrix?.(true, true); + _box.setFromObject(holder); + _box.getSize(_size); + holder.userData.worldLengthM = Math.max(_size.x, _size.y, _size.z); + console.info( + `[WeaponMeshAttach] ${profile} fit×${s.toFixed(3)} → ~${holder.userData.worldLengthM.toFixed(2)} m (target ${maxLen} m)` + ); + return holder; + } catch (err) { + console.warn('[WeaponMeshAttach] load failed', modelUrl, err); + return null; + } +} + +/** + * Place Object3D muzzle at barrel tip (world-farthest from hand = tip heuristic). + * @param {import('three').Object3D} holder WeaponAttach group + * @param {string} profile + */ +export function placeMuzzleMarker(holder, profile = 'melee') { + if (!holder) return null; + // Clear old + for (const c of [...holder.children]) { + if (c.name === 'WeaponMuzzle' || c.userData?.isMuzzle) holder.remove(c); + } + + const hand = holder.parent; + if (hand) hand.getWorldPosition(_handW); + else holder.getWorldPosition(_handW); + + // Prefer mesh AABB corners in world — tip ≈ max distance from grip + _box.setFromObject(holder); + const { min, max } = _box; + const xs = [min.x, max.x]; + const ys = [min.y, max.y]; + const zs = [min.z, max.z]; + let bestD = -1; + for (const x of xs) { + for (const y of ys) { + for (const z of zs) { + _corner.set(x, y, z); + const d = _corner.distanceToSquared(_handW); + if (d > bestD) { + bestD = d; + _best.copy(_corner); + } + } + } + } + + // Fallback: local +Y scaled (pre -90 mesh long axis often Y) + if (bestD < 1e-8) { + const fb = + profile === 'pistol' + ? FLINTLOCK_FIRE.muzzleFallbackM + : profile === 'bow' + ? 0.9 + : 0.55; + _best.set(0, fb, 0); + holder.localToWorld(_best); + } + + // Slightly past tip along grip→tip + _corner.copy(_best).sub(_handW); + if (_corner.lengthSq() > 1e-8) { + _corner.normalize().multiplyScalar(0.012); + _best.add(_corner); + } + + const muzzle = new Object3D(); + muzzle.name = 'WeaponMuzzle'; + muzzle.userData.isMuzzle = true; + holder.worldToLocal(_best); + muzzle.position.copy(_best); + holder.add(muzzle); + holder.userData.muzzle = muzzle; + holder.userData.muzzleLocal = muzzle.position.clone(); + return muzzle; +} + +/** + * Find WeaponAttach under a hand bone. + * @param {import('three').Object3D|null} handBone + */ +export function getWeaponAttachFromHand(handBone) { + if (!handBone) return null; + for (const c of handBone.children) { + if (c.userData?.weaponAttach || c.name === 'WeaponAttach') return c; + } + let found = null; + handBone.traverse((o) => { + if (!found && (o.userData?.weaponAttach || o.name === 'WeaponAttach')) found = o; + }); + return found; +} + +/** + * World muzzle position. + * @param {import('three').Object3D|null} attach + * @param {import('three').Vector3} [out] + */ +export function getMuzzleWorldFromAttach(attach, out = new Vector3()) { + if (!attach) return out.set(0, 0, 0); + const m = attach.userData?.muzzle; + if (m) { + m.getWorldPosition(out); + return out; + } + attach.getWorldPosition(out); + return out; +} + +/** + * @param {import('three').Object3D|null} handBone + */ +export function clearWeaponAttach(handBone) { + if (!handBone) return; + const doomed = []; + handBone.traverse((o) => { + if (o.userData?.weaponAttach || o.name === 'WeaponAttach') doomed.push(o); + }); + // Only remove direct holders under hand (avoid double-dispose) + for (const o of [...handBone.children]) { + if (o.userData?.weaponAttach || o.name === 'WeaponAttach') { + handBone.remove(o); + o.traverse((c) => { + if (c.geometry) c.geometry.dispose?.(); + if (c.material) { + const mats = Array.isArray(c.material) ? c.material : [c.material]; + for (const m of mats) m.dispose?.(); + } + }); + } + } +} diff --git a/src/character/WeaponSheathRuntime.js b/src/character/WeaponSheathRuntime.js new file mode 100644 index 0000000..0064f54 --- /dev/null +++ b/src/character/WeaponSheathRuntime.js @@ -0,0 +1,453 @@ +/** + * Weapon sheath / unsheath runtime — hand ↔ stow bone reparent. + * + * Best practice (Casting lab · action RPG): + * 1. Combat mesh attaches to **hand container** (R_hand_container / Bip001 R Hand) + * 2. **Sheath** on traversal + mount: reparent to spine/hip stow sockets + * 3. **Unsheath** on combat ready: idle, attack, cast, charge, reload, equip + * 4. Hold-pose residual only while **drawn** (never fights stow transform) + * 5. One mesh instance — never clone for stow (avoid double weapons) + * + * @see WeaponMeshAttach.js · weaponHoldPose.js · BackSlotEquip.findBackBone + */ + +import { MathUtils, Vector3, Euler, Quaternion } from 'three'; +import { findBackBone } from './BackSlotEquip.js'; +import { getWeaponAttachFromHand } from './WeaponMeshAttach.js'; + +/** @typedef {'drawn'|'sheathed'} SheathState */ + +/** + * Why the weapon should be sheathed. + * Higher priority reasons win when mixed. + * @typedef {'mount'|'ride'|'freeride'|'sprint'|'run'|'walk'|'air'|'mobility'|'manual'} SheathReason + */ + +/** + * Policy SSOT — toggle per product feel. + * Default: sheath on walk/run/sprint + air + mount; unsheath idle/combat. + */ +export const WEAPON_SHEATH_POLICY = Object.freeze({ + /** Sheath while gait walk (1) */ + sheathOnWalk: true, + /** Sheath while gait run (2) */ + sheathOnRun: true, + /** Sheath while sprint flag (gait 2 + sprint) */ + sheathOnSprint: true, + /** Sheath while ride / freeride / mount parented */ + sheathOnMount: true, + /** Sheath while jump / fall / air loco */ + sheathOnAir: true, + /** Sheath on dodge / roll / slide */ + sheathOnMobility: true, + /** Blend time feel (visual only — reparent is instant) */ + blendHintSec: 0.12, + /** Min seconds between sheath↔draw thrash (gait noise) */ + debounceSec: 0.18 +}); + +/** + * Per-profile stow pose on back/hip (local space of stow parent). + * pos metres · euler degrees XYZ. + */ +export const STOW_POSES = Object.freeze({ + melee: { + bone: 'hip', + pos: [0.12, 0.05, -0.08], + eulerDeg: [15, 95, 75], + scale: 1 + }, + sword: { + bone: 'hip', + pos: [0.12, 0.05, -0.08], + eulerDeg: [15, 95, 75], + scale: 1 + }, + dagger: { + bone: 'hip', + pos: [0.1, 0.02, -0.06], + eulerDeg: [10, 100, 80], + scale: 1 + }, + staff: { + bone: 'back', + pos: [0.08, 0.12, -0.18], + eulerDeg: [-25, 0, 12], + scale: 1 + }, + wand: { + bone: 'hip', + pos: [0.1, 0.04, -0.05], + eulerDeg: [20, 90, 60], + scale: 1 + }, + bow: { + bone: 'back', + pos: [0.02, 0.18, -0.2], + eulerDeg: [5, 0, 90], + scale: 1 + }, + pistol: { + bone: 'hip', + pos: [0.14, 0.02, 0.02], + eulerDeg: [-10, 90, 0], + scale: 1 + }, + shield: { + // Off-hand gear stays on arm — no sheath reparent + bone: 'none', + pos: [0, 0, 0], + eulerDeg: [0, 0, 0], + scale: 1 + }, + default: { + bone: 'back', + pos: [0.06, 0.1, -0.16], + eulerDeg: [10, 180, 20], + scale: 1 + } +}); + +/** + * Anim / controller states that force **drawn** (combat override). + */ +const FORCE_DRAWN_STATES = new Set([ + 'attack', + 'cast_loop', + 'charge', + 'parry', + 'block' +]); + +/** + * Anim states that force **sheathed** (mobility). + */ +const FORCE_SHEATH_STATES = new Set([ + 'jump', + 'fall', + 'fallLoop', + 'fallLand', + 'fallRoll', + 'dodge', + 'roll', + 'slide' +]); + +/** + * @param {string|undefined} profile + */ +export function stowPoseForProfile(profile) { + const p = String(profile || 'melee').toLowerCase(); + if (STOW_POSES[p]) return STOW_POSES[p]; + if (/sword|blade|axe|hammer|mace|spear|great/.test(p)) return STOW_POSES.melee; + if (/staff|stave/.test(p)) return STOW_POSES.staff; + if (/wand/.test(p)) return STOW_POSES.wand; + if (/bow|crossbow/.test(p)) return STOW_POSES.bow; + if (/pistol|gun|rifle|flint/.test(p)) return STOW_POSES.pistol; + if (/shield/.test(p)) return STOW_POSES.shield; + return STOW_POSES.default; +} + +/** + * Resolve whether policy wants sheathed given controller snapshot. + * @param {{ + * gait?: number, + * sprinting?: boolean, + * rideActive?: boolean, + * rideParented?: boolean, + * animState?: string, + * casting?: boolean, + * pistolReload?: boolean, + * oneShotActive?: boolean, + * policy?: typeof WEAPON_SHEATH_POLICY + * }} snap + * @returns {{ sheath: boolean, reason: string }} + */ +export function resolveSheathDesire(snap = {}) { + const pol = { ...WEAPON_SHEATH_POLICY, ...(snap.policy || {}) }; + const state = String(snap.animState || 'idle'); + + // Combat always wins — hands need the weapon + if (snap.casting || snap.pistolReload || snap.oneShotActive) { + return { sheath: false, reason: 'combat' }; + } + if (FORCE_DRAWN_STATES.has(state)) { + return { sheath: false, reason: state }; + } + + // Mount / freeride / vehicle + if (pol.sheathOnMount && (snap.rideActive || snap.rideParented)) { + return { sheath: true, reason: 'mount' }; + } + + // Air + mobility one-shots + if (pol.sheathOnAir && (FORCE_SHEATH_STATES.has(state) || state.startsWith('fall'))) { + return { sheath: true, reason: state }; + } + if (pol.sheathOnMobility && (state === 'dodge' || state === 'roll' || state === 'slide')) { + return { sheath: true, reason: state }; + } + + const g = Number(snap.gait) || 0; + if (pol.sheathOnSprint && (snap.sprinting || g >= 2.5)) { + return { sheath: true, reason: 'sprint' }; + } + if (pol.sheathOnRun && g >= 2) { + return { sheath: true, reason: 'run' }; + } + if (pol.sheathOnWalk && g >= 1) { + return { sheath: true, reason: 'walk' }; + } + + // Idle / combat ready + return { sheath: false, reason: 'idle' }; +} + +/** + * Runtime: reparents WeaponAttach between hand and stow bone. + */ +export class WeaponSheathRuntime { + /** + * @param {{ + * getModel?: () => import('three').Object3D|null, + * getBones?: () => { rHand?: import('three').Object3D|null, lHand?: import('three').Object3D|null, pelvis?: import('three').Object3D|null }, + * getAttach?: () => import('three').Object3D|null, + * setAttach?: (a: import('three').Object3D|null) => void, + * policy?: Partial + * }} [opts] + */ + constructor(opts = {}) { + this.getModel = opts.getModel || (() => null); + this.getBones = opts.getBones || (() => ({})); + this.getAttach = opts.getAttach || (() => null); + this.setAttach = opts.setAttach || (() => {}); + this.policy = { ...WEAPON_SHEATH_POLICY, ...(opts.policy || {}) }; + + /** @type {SheathState} */ + this.state = 'drawn'; + /** @type {string} */ + this.reason = 'init'; + /** @type {import('three').Object3D|null} */ + this._handParent = null; + /** @type {import('three').Object3D|null} */ + this._stowParent = null; + this._debounce = 0; + this._lastDesire = false; + this.enabled = true; + /** Manual lock: null | true(sheath) | false(draw) */ + this._manual = null; + } + + /** Rebind after kit swap. */ + rebind() { + this._handParent = null; + this._stowParent = null; + // Prefer keep logical state; next tick will reparent + } + + /** + * Force sheath or draw (equip UI). Pass null to return to policy. + * @param {boolean|null} sheath + * @param {string} [reason] + */ + setManual(sheath, reason = 'manual') { + this._manual = sheath === null ? null : !!sheath; + if (this._manual === null) return; + if (this._manual) this.sheath(reason); + else this.unsheath(reason); + } + + get isSheathed() { + return this.state === 'sheathed'; + } + + get isDrawn() { + return this.state === 'drawn'; + } + + /** + * Resolve hand + stow parents from kit. + */ + _resolveParents(attach) { + const bones = this.getBones() || {}; + const model = this.getModel(); + let hand = bones.rHand || null; + if (!hand && attach?.parent) { + // attach may already be on hand or stow + const p = attach.parent; + if (/hand/i.test(p.name || '') || p.userData?.isHandSocket) hand = p; + } + if (!hand && model) { + model.traverse((n) => { + if (hand) return; + if (/R_hand_container/i.test(n.name || '') || /^Bip001[\s_]R[\s_]Hand$/i.test(n.name || '')) { + hand = n; + } + }); + } + this._handParent = hand; + + const pose = stowPoseForProfile(attach?.userData?.profile); + let stow = null; + if (pose.bone === 'hip') { + stow = bones.pelvis || null; + if (!stow && model) { + model.traverse((n) => { + if (stow) return; + if (/^Bip001[\s_]Pelvis$/i.test(n.name || '') || /pelvis/i.test(n.name || '')) stow = n; + }); + } + } + if (!stow && pose.bone !== 'none') { + stow = findBackBone(model) || bones.pelvis || null; + } + this._stowParent = stow; + return { hand, stow, pose }; + } + + /** + * @param {string} [reason] + * @returns {boolean} + */ + sheath(reason = 'policy') { + if (!this.enabled) return false; + const attach = this.getAttach() || null; + if (!attach) return false; + if (attach.userData?.profile === 'shield') return false; + + const { hand, stow, pose } = this._resolveParents(attach); + if (!stow || pose.bone === 'none') { + // No stow bone — hide in hand as soft sheath + if (this.state !== 'sheathed') { + attach.visible = false; + this.state = 'sheathed'; + this.reason = reason + ':hidden'; + } + return true; + } + + if (this.state === 'sheathed' && attach.parent === stow) { + this.reason = reason; + return true; + } + + // Remember hand for unsheath + if (hand) this._handParent = hand; + else if (attach.parent && attach.parent !== stow) this._handParent = attach.parent; + + stow.attach(attach); + const e = pose.eulerDeg || [0, 0, 0]; + const p = pose.pos || [0, 0, 0]; + attach.position.set(p[0] || 0, p[1] || 0, p[2] || 0); + attach.rotation.set( + MathUtils.degToRad(e[0] || 0), + MathUtils.degToRad(e[1] || 0), + MathUtils.degToRad(e[2] || 0) + ); + if (pose.scale && pose.scale !== 1) attach.scale.setScalar(pose.scale); + else attach.scale.set(1, 1, 1); + attach.visible = true; + attach.userData.sheathed = true; + attach.userData.sheathReason = reason; + this.state = 'sheathed'; + this.reason = reason; + this.setAttach(attach); + return true; + } + + /** + * @param {string} [reason] + * @returns {boolean} + */ + unsheath(reason = 'policy') { + if (!this.enabled) return false; + const attach = this.getAttach() || null; + if (!attach) return false; + + const { hand } = this._resolveParents(attach); + const target = hand || this._handParent; + if (!target) { + attach.visible = true; + attach.userData.sheathed = false; + this.state = 'drawn'; + this.reason = reason + ':no-hand'; + return false; + } + + if (this.state === 'drawn' && attach.parent === target) { + attach.visible = true; + attach.userData.sheathed = false; + this.reason = reason; + return true; + } + + target.attach(attach); + // Grip residual comes from weaponHoldPose post-mixer — reset local to grip identity + attach.position.set(0, 0, 0); + attach.rotation.set(0, 0, 0); + attach.scale.set(1, 1, 1); + attach.visible = true; + attach.userData.sheathed = false; + attach.userData.sheathReason = reason; + this.state = 'drawn'; + this.reason = reason; + this.setAttach(attach); + return true; + } + + /** + * Per-frame policy evaluation (call from CharacterController.update). + * @param {{ + * gait?: number, + * sprinting?: boolean, + * rideActive?: boolean, + * rideParented?: boolean, + * animState?: string, + * casting?: boolean, + * pistolReload?: boolean, + * oneShotActive?: boolean, + * dt?: number + * }} snap + */ + update(snap = {}) { + if (!this.enabled) return; + const dt = snap.dt ?? 0; + if (this._debounce > 0) this._debounce -= dt; + + // Refresh attach pointer if equip re-bound under hand + let attach = this.getAttach(); + if (!attach) { + const bones = this.getBones() || {}; + attach = getWeaponAttachFromHand(bones.rHand) || null; + if (attach) this.setAttach(attach); + } + if (!attach) return; + + if (this._manual !== null) { + if (this._manual && this.state !== 'sheathed') this.sheath('manual'); + if (!this._manual && this.state !== 'drawn') this.unsheath('manual'); + return; + } + + const desire = resolveSheathDesire({ ...snap, policy: this.policy }); + if (desire.sheath === this._lastDesire && this._debounce > 0) return; + if (desire.sheath === (this.state === 'sheathed')) { + this._lastDesire = desire.sheath; + this.reason = desire.reason; + return; + } + + // Debounce only gait-driven thrash (walk↔idle), not mount/combat + const hard = + desire.reason === 'mount' || + desire.reason === 'combat' || + FORCE_DRAWN_STATES.has(desire.reason) || + FORCE_SHEATH_STATES.has(desire.reason); + if (!hard && this._debounce > 0) return; + + if (desire.sheath) this.sheath(desire.reason); + else this.unsheath(desire.reason); + this._lastDesire = desire.sheath; + this._debounce = this.policy.debounceSec; + } +} diff --git a/src/character/grudge6Deploy.js b/src/character/grudge6Deploy.js new file mode 100644 index 0000000..a95a4ea --- /dev/null +++ b/src/character/grudge6Deploy.js @@ -0,0 +1,35 @@ +/** + * REMOVED play deploy stack. + * + * Warlords character play = toonKitPlay.js only (Casting live deploy SSOT). + * Re-exports kept so old import paths do not invent a second system. + */ +export { + diagnoseCharacterLook, + countSkeletons, + reGroundToonKit as reGroundAfterAnimSample, + deployToonPlayKit, + fitRootUniformSi, + measureBoneStructuralBBox, +} from './toonKitPlay.js'; + +/** @deprecated throws — use deployToonPlayKit */ +export function scaffoldGrudge6Kit() { + throw new Error( + '[grudge6Deploy] REMOVED — use toonKitPlay.deployToonPlayKit (Casting / ObjectStore Toon RTS play)', + ); +} + +/** @deprecated throws — use deployToonPlayKit */ +export function deployGrudge6Model() { + throw new Error( + '[grudge6Deploy] REMOVED — use toonKitPlay.deployToonPlayKit', + ); +} + +/** @deprecated throws — do not unify for Toon play */ +export function unifySkeletons() { + throw new Error( + '[grudge6Deploy] unifySkeletons REMOVED for Toon RTS play (head-at-feet)', + ); +} diff --git a/src/character/toonKitPlay.js b/src/character/toonKitPlay.js new file mode 100644 index 0000000..77cfaf1 --- /dev/null +++ b/src/character/toonKitPlay.js @@ -0,0 +1,417 @@ +/** + * Warlords / grudge6 PLAY path — HARDENED parity of ObjectStore loadRaceKit. + * + * RIGHT SOURCE (do not invent another): + * info.grudge-studio.com/js/grudge6-kit.js (loadRaceKit) + * api/v1/grudge6-warlords-play-contract.json + * info.grudge-studio.com/grudge6-race-scenes.html + * + * Pipeline: + * Toon RTS GLB → SkeletonUtils.clone → exclusive mesh_ids + * → normalize embeds (never forceAtlas) + * → fitRootUniformSi via BONE structural box (not unskinned mesh AABB) + * → face camera yaw 0 + * → stamp warlordsPlayContract on root.userData + * + * BANNED (throw / never reintroduce): multi-pose, mesh AABB SI, facePlusZ default, + * races/metaverse/FBX play defaults, forceAtlas on good embeds. + * + * Live proof: https://casting-abilities-threejs.vercel.app/ + */ +import { + Box3, + ClampToEdgeWrapping, + DoubleSide, + SRGBColorSpace, + Vector3 +} from 'three'; +import { clone as skeletonClone } from 'three/addons/utils/SkeletonUtils.js'; +import { HUMAN_HEIGHT_M } from '../config/grudge6SSOT.js'; + +export const WARLORDS_PLAY_CONTRACT_VERSION = '2026-08-07.harden.1'; + +const _p = new Vector3(); + +function findNamed(root, names) { + for (const n of names) { + const o = root.getObjectByName(n); + if (o) return o; + } + return null; +} + +/** + * Bone-driven structural AABB — ObjectStore measureBoneStructuralBBox. + * Skinned modular Units_* geometry is local-bind; mesh AABB is wrong. + */ +export function measureBoneStructuralBBox(root) { + if (!root) return null; + root.updateMatrixWorld(true); + root.traverse((o) => { + if (o.isSkinnedMesh && o.skeleton) o.skeleton.update(); + }); + + const groups = [ + ['Bip001 Head', 'Bip001_Head', 'Head'], + ['Bip001 HeadNub', 'Bip001_HeadNub'], + ['Bip001 Pelvis', 'Bip001_Pelvis', 'Pelvis'], + ['Bip001 Spine', 'Bip001_Spine'], + ['Bip001 L Foot', 'Bip001_L_Foot'], + ['Bip001 R Foot', 'Bip001_R_Foot'], + ['Bip001 L Toe0', 'Bip001_L_Toe0'], + ['Bip001 R Toe0', 'Bip001_R_Toe0'], + ['Bip001 L Hand', 'Bip001_L_Hand'], + ['Bip001 R Hand', 'Bip001_R_Hand'], + ['Bip001 L Calf', 'Bip001_L_Calf'], + ['Bip001 R Calf', 'Bip001_R_Calf'] + ]; + + const box = new Box3(); + let n = 0; + for (const names of groups) { + const bone = findNamed(root, names); + if (!bone) continue; + bone.getWorldPosition(_p); + if (!Number.isFinite(_p.x + _p.y + _p.z)) continue; + if (n === 0) { + box.min.copy(_p); + box.max.copy(_p); + } else box.expandByPoint(_p); + n++; + } + if (n < 2) return null; + + const h = Math.max(box.max.y - box.min.y, 1e-4); + const pad = Math.max(h * 0.1, h * 0.02); + box.min.y -= pad * 0.55; + box.max.y += pad * 0.45; + box.min.x -= pad * 0.35; + box.max.x += pad * 0.35; + box.min.z -= pad * 0.35; + box.max.z += pad * 0.35; + return box; +} + +/** + * Uniform SI fit on ROOT only + plant feet (ObjectStore fitRootUniformSi). + * Does NOT touch child mesh/bone scales. Does NOT unify skeletons. + */ +export function fitRootUniformSi(root, targetH = HUMAN_HEIGHT_M, opts = {}) { + const centerXZ = opts.centerXZ !== false; + + root.position.set(0, 0, 0); + root.rotation.set(0, 0, 0); + root.scale.setScalar(1); + root.updateMatrixWorld(true); + + let box = measureBoneStructuralBBox(root); + if (!box) { + box = new Box3().setFromObject(root, false); + } + let h = Math.max(box.max.y - box.min.y, 1e-4); + + // Classic 100× (cm as m) + if (h > 40) { + root.scale.setScalar(0.01); + root.updateMatrixWorld(true); + box = measureBoneStructuralBBox(root) || new Box3().setFromObject(root, false); + h = Math.max(box.max.y - box.min.y, 1e-4); + } + + const s = targetH / h; + root.scale.setScalar(root.scale.x * s); + root.updateMatrixWorld(true); + box = measureBoneStructuralBBox(root) || new Box3().setFromObject(root, false); + + // Feet = structural min.y (NOT pelvis) + root.position.y -= box.min.y; + if (centerXZ) { + const cx = (box.min.x + box.max.x) * 0.5; + const cz = (box.min.z + box.max.z) * 0.5; + root.position.x -= cx; + root.position.z -= cz; + } + root.updateMatrixWorld(true); + box = measureBoneStructuralBBox(root) || new Box3().setFromObject(root, false); + const finalH = box.max.y - box.min.y; + + root.userData.deployHeightM = finalH; + root.userData.deployScaleFactor = root.scale.x; + root.userData.grudgeHeightFit = true; + root.userData.importPipeline = root.userData.importPipeline || 'toon-rts-glb'; + root.userData.artForwardSet = true; + root.userData.artForwardYaw = 0; + + console.info( + `[toonKitPlay] fitRootUniformSi boneH→${finalH.toFixed(2)}m scale=${root.scale.x.toFixed(4)} ` + + `feetY=${box.min.y.toFixed(3)}` + ); + return { height: finalH, scale: root.scale.x, targetH }; +} + +/** Toon RTS play GLBs are art-forward +Z — yaw 0 faces +Z camera. Never Math.PI. */ +export function faceRootTowardCamera(root) { + root.rotation.set(0, 0, 0); +} + +/** + * Hide all equippable → show mesh_ids (ObjectStore EquipmentManager.applyMeshIds style). + */ +export function applyMeshIdsExclusive(root, meshIds = []) { + const want = (meshIds || []).map((id) => meshKey(id)).filter(Boolean); + const shown = []; + const missing = []; + + const meshes = []; + root.traverse((n) => { + if (n.isMesh || n.isSkinnedMesh) meshes.push(n); + }); + + for (const m of meshes) { + if (!isEquippableName(m.name)) continue; + m.visible = false; + } + + if (!want.length) { + // Default A armor + for (const m of meshes) { + if (/units_body_a|body_a/i.test(m.name) && !/weapon/i.test(m.name)) { + m.visible = true; + shown.push(m.name); + } + if (/units_arms_a|arms_a/i.test(m.name)) { + m.visible = true; + shown.push(m.name); + } + if (/units_legs_a|legs_a/i.test(m.name)) { + m.visible = true; + shown.push(m.name); + } + if (/units_head_a|head_a/i.test(m.name) && !/bip001/i.test(m.name)) { + m.visible = true; + shown.push(m.name); + } + } + return { matched: shown.length, shown, missing: ['empty-loadout-used-A'] }; + } + + for (const w of want) { + let hit = null; + // Prefer exact key match; avoid loose endsWith on short fragments (was + // matching every *A* variant and re-showing multipack weapons). + for (const m of meshes) { + const k = meshKey(m.name); + if (k === w) { + hit = m; + break; + } + } + if (!hit) { + for (const m of meshes) { + const k = meshKey(m.name); + // Only allow suffix match when both keys are long enough (full stems) + if (k.length >= 5 && w.length >= 5 && (k.endsWith(w) || w.endsWith(k))) { + hit = m; + break; + } + } + } + if (hit) { + hit.visible = true; + shown.push(hit.name); + } else missing.push(w); + } + + // Hard-hide every equippable that was not explicitly shown (weapons multipack) + const shownSet = new Set(shown); + for (const m of meshes) { + if (!isEquippableName(m.name)) continue; + if (!shownSet.has(m.name)) m.visible = false; + } + + // Rescue body if nothing matched + if (!shown.some((n) => /body/i.test(n))) { + const body = meshes.find((m) => /body/i.test(m.name || '') && !/weapon/i.test(m.name || '')); + if (body) { + body.visible = true; + shown.push(body.name); + } + } + + // Hard-hide utility + for (const m of meshes) { + if (/xtra_|quiver|bag|wood/i.test(m.name || '')) m.visible = false; + } + + console.info(`[toonKitPlay] equip shown=${shown.length} missing=${missing.length}`, shown.slice(0, 8)); + return { matched: shown.length, shown, missing }; +} + +function meshKey(name) { + return String(name || '') + .toLowerCase() + .replace(/^wk_|^brb_|^orc_|^elf_|^ud_|^dwf_/, '') + .replace(/units_/g, '') + .replace(/xtra_/g, '') + .replace(/weapon_/g, 'weapon') + .replace(/[^a-z0-9]/g, ''); +} + +function isEquippableName(name) { + return /body|arms|legs|head|shoulder|weapon|sword|axe|hammer|mace|spear|bow|staff|shield|dagger|pick|quiver|bag|wood|xtra|units_/i.test( + name || '' + ); +} + +export function normalizeEmbeddedMaps(root) { + let n = 0; + root.traverse((obj) => { + if (!obj.isMesh && !obj.isSkinnedMesh) return; + obj.castShadow = true; + obj.receiveShadow = true; + if (obj.isSkinnedMesh) obj.frustumCulled = false; + const mats = Array.isArray(obj.material) ? obj.material : [obj.material]; + for (const m of mats) { + if (!m?.map) continue; + m.map.colorSpace = SRGBColorSpace; + m.map.flipY = false; + m.map.wrapS = m.map.wrapT = ClampToEdgeWrapping; + m.map.needsUpdate = true; + if (m.color) m.color.setHex(0xffffff); + if ('metalness' in m) m.metalness = Math.min(m.metalness ?? 0, 0.15); + if ('roughness' in m && (m.roughness == null || m.roughness < 0.2)) m.roughness = 0.75; + m.side = DoubleSide; + m.needsUpdate = true; + n++; + } + }); + return n; +} + +/** + * Full play deploy after GLTF load — ObjectStore loadRaceKit parity. + * @param {import('three').Object3D} gltfScene + * @param {{ meshIds?: string[], targetH?: number }} opts + */ +export function deployToonPlayKit(gltfScene, opts = {}) { + // SkeletonUtils.clone required for multiple instances; do NOT unify/pose after + const kit = skeletonClone(gltfScene); + kit.userData.importPipeline = 'toon-rts-glb'; + kit.userData.playPath = 'objectstore-loadRaceKit-parity'; + kit.userData.grudge6Play = true; + kit.userData.warlordsPlayContract = WARLORDS_PLAY_CONTRACT_VERSION; + + // Bind pose as loaded — never multi-pose + kit.traverse((o) => { + if (o.isSkinnedMesh && o.skeleton) { + o.skeleton.update(); + o.frustumCulled = false; + } + }); + + const meshIds = opts.meshIds || []; + const equip = applyMeshIdsExclusive(kit, meshIds); + const mats = normalizeEmbeddedMaps(kit); + const fit = fitRootUniformSi(kit, opts.targetH ?? HUMAN_HEIGHT_M, { centerXZ: true }); + faceRootTowardCamera(kit); + + // Re-fit after materials (bbox unchanged for bone measure, but safe) + fitRootUniformSi(kit, opts.targetH ?? HUMAN_HEIGHT_M, { centerXZ: true }); + faceRootTowardCamera(kit); + + const bones = measureBoneStructuralBBox(kit); + const head = findNamed(kit, ['Bip001 Head', 'Bip001_Head']); + const foot = findNamed(kit, ['Bip001 L Foot', 'Bip001_L_Foot', 'Bip001 R Foot']); + let headY = null; + let footY = null; + if (head) { + head.getWorldPosition(_p); + headY = _p.y; + } + if (foot) { + foot.getWorldPosition(_p); + footY = _p.y; + } + const upright = headY != null && footY != null ? headY > footY + 0.3 : null; + + console.info( + `[toonKitPlay] deploy equip=${equip.matched} mats=${mats} h=${fit.height.toFixed(2)}m ` + + `headY=${headY?.toFixed(2)} footY=${footY?.toFixed(2)} upright=${upright} ` + + `boneBox=${bones ? (bones.max.y - bones.min.y).toFixed(2) : '—'}` + ); + + return { + root: kit, + equip, + fit, + upright, + height: fit.height, + headY, + footY + }; +} + +/** Re-ground after anim sample using bone structural box. */ +export function reGroundToonKit(root, groundY = 0) { + if (!root) return 0; + root.updateMatrixWorld(true); + root.traverse((o) => { + if (o.isSkinnedMesh && o.skeleton) o.skeleton.update(); + }); + const box = measureBoneStructuralBBox(root); + if (!box || !Number.isFinite(box.min.y)) return 0; + const dy = groundY - box.min.y; + if (Math.abs(dy) > 1e-5) root.position.y += dy; + root.userData.deployHeightM = box.max.y - box.min.y; + return dy; +} + +export function countSkeletons(root) { + const ids = new Set(); + root?.traverse((o) => { + if (o.isSkinnedMesh && o.skeleton) ids.add(o.skeleton.uuid); + }); + return ids.size; +} + +/** Diagnose height/feet/Bip001 after Toon play deploy (bone structural). */ +export function diagnoseCharacterLook(root, groundY = 0) { + const errors = []; + if (!root) return { ok: false, errors: ['no-root'], height: 0, feetMinY: 0 }; + root.updateMatrixWorld(true); + root.traverse((o) => { + if (o.isSkinnedMesh && o.skeleton) o.skeleton.update(); + }); + const box = measureBoneStructuralBBox(root); + if (!box) { + return { ok: false, errors: ['no-bone-bbox'], height: 0, feetMinY: 0 }; + } + const height = box.max.y - box.min.y; + const feetMinY = box.min.y; + if (height < 1.55 || height > 2.15) { + errors.push(`height ${height.toFixed(2)} not in 1.55–2.15`); + } + if (Math.abs(feetMinY - groundY) > 0.12) { + errors.push(`feet minY ${feetMinY.toFixed(3)} off ground`); + } + const head = root.getObjectByName('Bip001 Head'); + const pelvis = root.getObjectByName('Bip001 Pelvis'); + if (!pelvis) errors.push('no Bip001 Pelvis'); + if (head && pelvis) { + const hp = new Vector3(); + const pp = new Vector3(); + head.getWorldPosition(hp); + pelvis.getWorldPosition(pp); + if (hp.y < pp.y) errors.push('head below pelvis (inverted rig)'); + } + return { + ok: errors.length === 0, + errors, + height, + feetMinY, + scaleFactor: root.userData.deployScaleFactor ?? 1, + artForward: !!root.userData.artForwardSet, + warlordsPlayContract: root.userData.warlordsPlayContract || null, + grudge6Play: !!root.userData.grudge6Play, + }; +} diff --git a/src/character/weaponHoldPose.js b/src/character/weaponHoldPose.js new file mode 100644 index 0000000..c273c75 --- /dev/null +++ b/src/character/weaponHoldPose.js @@ -0,0 +1,392 @@ +/** + * SSOT SOURCE: ObjectStore/js/grudge6-weapon-hold-pose.js + * Keep in sync — do not invent a second hold-pose stack in Casting. + * Play equip: CharacterController after mixer.update → applyWeaponHoldPose. + */ +/** + * grudge6 weapon hold pose SSOT — post-mixer residual on hand sockets. + * + * Stack (do not fork): + * 1. Attach weapon to R/L_hand_container (kit or lab) + * 2. Static gripOffset / gripEuler (grudge6-lab-weapons applyGripTransform) + * 3. **This file** — after AnimationMixer.update, gait residual on hands + * 4. Optional 2H secondary-hand IK later (P1) + * + * Call sites (same function, no parallel stack): + * - main-panel-hero-viewport tick (paperdoll idle) + * - play equip / combat CharacterController after mixer.update + * + * @example + * mixer.update(dt); + * applyWeaponHoldPose(mixer, gait, kind); + */ + +/** @typedef {'idle'|'walk'|'run'|'sprint'} HoldGait */ +/** @typedef {{ pos?: [number, number, number], euler?: [number, number, number] }} HoldSidePose */ + +/** + * Per-kind · per-gait residual (radians / metres) applied AFTER mixer. + * Positive euler.x ≈ tip forward/down; euler.z ≈ roll in palm. + * Dual off-hand uses `off` when present, else mirrored `main`. + */ +export const WEAPON_HOLD_POSE = { + sword: { + idle: { + main: { pos: [0, 0, 0], euler: [0.06, 0.02, 0.1] }, + off: { pos: [0, 0, 0], euler: [0.06, -0.02, -0.1] }, + }, + walk: { + main: { pos: [0, 0, 0], euler: [0.12, 0.04, 0.12] }, + off: { pos: [0, 0, 0], euler: [0.12, -0.04, -0.12] }, + }, + run: { + main: { pos: [0, 0.005, 0], euler: [0.18, 0.06, 0.14] }, + off: { pos: [0, 0.005, 0], euler: [0.18, -0.06, -0.14] }, + }, + }, + dagger: { + idle: { + main: { pos: [0, 0, 0.01], euler: [0.08, 0.05, 0.18] }, + off: { pos: [0, 0, 0.01], euler: [0.08, -0.05, -0.18] }, + }, + walk: { + main: { pos: [0, 0, 0.01], euler: [0.14, 0.06, 0.2] }, + off: { pos: [0, 0, 0.01], euler: [0.14, -0.06, -0.2] }, + }, + run: { + main: { pos: [0, 0.004, 0.012], euler: [0.2, 0.08, 0.22] }, + off: { pos: [0, 0.004, 0.012], euler: [0.2, -0.08, -0.22] }, + }, + }, + knife: { + idle: { + main: { pos: [0, 0, 0.01], euler: [0.08, 0.05, 0.18] }, + off: { pos: [0, 0, 0.01], euler: [0.08, -0.05, -0.18] }, + }, + walk: { + main: { pos: [0, 0, 0.01], euler: [0.14, 0.06, 0.2] }, + off: { pos: [0, 0, 0.01], euler: [0.14, -0.06, -0.2] }, + }, + run: { + main: { pos: [0, 0.004, 0.012], euler: [0.2, 0.08, 0.22] }, + off: { pos: [0, 0.004, 0.012], euler: [0.2, -0.08, -0.22] }, + }, + }, + mace: { + idle: { + main: { pos: [0, 0, 0], euler: [0.05, 0.02, 0.08] }, + off: { pos: [0, 0, 0], euler: [0.05, -0.02, -0.08] }, + }, + walk: { + main: { pos: [0, 0, 0], euler: [0.1, 0.03, 0.1] }, + off: { pos: [0, 0, 0], euler: [0.1, -0.03, -0.1] }, + }, + run: { + main: { pos: [0, 0.004, 0], euler: [0.16, 0.05, 0.12] }, + off: { pos: [0, 0.004, 0], euler: [0.16, -0.05, -0.12] }, + }, + }, + hammer: { + idle: { + main: { pos: [0, 0, 0], euler: [0.05, 0.02, 0.08] }, + off: { pos: [0, 0, 0], euler: [0.05, -0.02, -0.08] }, + }, + walk: { + main: { pos: [0, 0, 0], euler: [0.1, 0.03, 0.1] }, + off: { pos: [0, 0, 0], euler: [0.1, -0.03, -0.1] }, + }, + run: { + main: { pos: [0, 0.004, 0], euler: [0.16, 0.05, 0.12] }, + off: { pos: [0, 0.004, 0], euler: [0.16, -0.05, -0.12] }, + }, + }, + axe: { + idle: { + main: { pos: [0, 0, 0], euler: [0.07, 0.03, 0.12] }, + off: { pos: [0, 0, 0], euler: [0.07, -0.03, -0.12] }, + }, + walk: { + main: { pos: [0, 0, 0], euler: [0.13, 0.04, 0.14] }, + off: { pos: [0, 0, 0], euler: [0.13, -0.04, -0.14] }, + }, + run: { + main: { pos: [0, 0.005, 0], euler: [0.2, 0.06, 0.16] }, + off: { pos: [0, 0.005, 0], euler: [0.2, -0.06, -0.16] }, + }, + }, + spear: { + idle: { main: { pos: [0, 0.02, 0], euler: [0.35, 0, 0.05] } }, + walk: { main: { pos: [0, 0.02, 0], euler: [0.42, 0.02, 0.06] } }, + run: { main: { pos: [0, 0.025, 0], euler: [0.5, 0.03, 0.08] } }, + }, + greatsword: { + idle: { main: { pos: [0, 0.01, 0], euler: [0.15, 0.04, 0.08] } }, + walk: { main: { pos: [0, 0.012, 0], euler: [0.22, 0.05, 0.1] } }, + run: { main: { pos: [0, 0.015, 0], euler: [0.3, 0.06, 0.12] } }, + }, + greataxe: { + idle: { main: { pos: [0, 0.01, 0], euler: [0.14, 0.04, 0.1] } }, + walk: { main: { pos: [0, 0.012, 0], euler: [0.2, 0.05, 0.12] } }, + run: { main: { pos: [0, 0.015, 0], euler: [0.28, 0.06, 0.14] } }, + }, + staff: { + idle: { main: { pos: [0, 0.02, 0], euler: [0.25, 0, 0.04] } }, + walk: { main: { pos: [0, 0.02, 0], euler: [0.32, 0.02, 0.05] } }, + run: { main: { pos: [0, 0.025, 0], euler: [0.4, 0.03, 0.06] } }, + }, + wand: { + idle: { main: { pos: [0, 0, 0.01], euler: [0.1, 0.04, 0.12] } }, + walk: { main: { pos: [0, 0, 0.01], euler: [0.16, 0.05, 0.14] } }, + run: { main: { pos: [0, 0.004, 0.012], euler: [0.22, 0.06, 0.16] } }, + }, + bow: { + idle: { main: { pos: [0, 0.01, 0], euler: [0.2, 0.1, -0.15] } }, + walk: { main: { pos: [0, 0.012, 0], euler: [0.28, 0.12, -0.18] } }, + run: { main: { pos: [0, 0.015, 0], euler: [0.35, 0.14, -0.2] } }, + }, + crossbow: { + idle: { main: { pos: [0, 0.01, 0.02], euler: [0.15, 0.05, 0.05] } }, + walk: { main: { pos: [0, 0.012, 0.02], euler: [0.2, 0.06, 0.06] } }, + run: { main: { pos: [0, 0.014, 0.025], euler: [0.28, 0.08, 0.08] } }, + }, + pistol: { + idle: { main: { pos: [0, 0, 0.02], euler: [0.12, 0.08, 0.15] } }, + walk: { main: { pos: [0, 0, 0.02], euler: [0.18, 0.1, 0.16] } }, + run: { main: { pos: [0, 0.004, 0.025], euler: [0.24, 0.12, 0.18] } }, + }, + tome: { + idle: { main: { pos: [0.02, 0.02, 0.04], euler: [-0.2, 0.1, 0.35] } }, + walk: { main: { pos: [0.02, 0.02, 0.04], euler: [-0.18, 0.12, 0.35] } }, + run: { main: { pos: [0.02, 0.025, 0.04], euler: [-0.15, 0.14, 0.32] } }, + }, + grimoire: { + idle: { main: { pos: [0.02, 0.02, 0.04], euler: [-0.2, 0.1, 0.35] } }, + walk: { main: { pos: [0.02, 0.02, 0.04], euler: [-0.18, 0.12, 0.35] } }, + run: { main: { pos: [0.02, 0.025, 0.04], euler: [-0.15, 0.14, 0.32] } }, + }, +}; + +const R_HAND_NAMES = [ + 'R_hand_container', + 'Bip001 R Hand', + 'Bip001_R_Hand', + 'mixamorig:RightHand', + 'mixamorigRightHand', +]; +const L_HAND_NAMES = [ + 'L_hand_container', + 'Bip001 L Hand', + 'Bip001_L_Hand', + 'mixamorig:LeftHand', + 'mixamorigLeftHand', +]; + +/** + * Normalize gait labels from panel (idle) and play (0/1/2, walk/run/sprint). + * @param {string|number|null|undefined} gait + * @returns {HoldGait} + */ +export function normalizeHoldGait(gait) { + if (gait == null) return 'idle'; + if (typeof gait === 'number') { + if (gait >= 3 || gait === 2.5) return 'sprint'; + if (gait >= 2) return 'run'; + if (gait >= 1) return 'walk'; + return 'idle'; + } + const s = String(gait).toLowerCase(); + if (/sprint|dash/.test(s)) return 'sprint'; + if (/run|jog/.test(s)) return 'run'; + if (/walk|locom|move|patrol/.test(s)) return 'walk'; + return 'idle'; +} + +/** + * Map kit equip slot / catalog kind → hold table key. + * @param {string|null|undefined} kindOrSlot + */ +export function normalizeHoldKind(kindOrSlot) { + const k = String(kindOrSlot || 'sword').toLowerCase(); + if (/greatsword|2h.?sword/.test(k)) return 'greatsword'; + if (/greataxe|2h.?axe/.test(k)) return 'greataxe'; + if (/spear|lance|pole/.test(k)) return 'spear'; + if (/dagger|knife/.test(k)) return k.includes('knife') ? 'knife' : 'dagger'; + if (/mace|club/.test(k)) return 'mace'; + if (/hammer|maul/.test(k)) return 'hammer'; + if (/axe/.test(k)) return 'axe'; + if (/sword|blade|saber/.test(k)) return 'sword'; + if (/staff|stave/.test(k)) return 'staff'; + if (/wand/.test(k)) return 'wand'; + if (/crossbow/.test(k)) return 'crossbow'; + if (/bow|longbow/.test(k)) return 'bow'; + if (/pistol|handgun|flint|gun|rifle/.test(k)) return 'pistol'; + if (/tome|grimoire|codex|book/.test(k)) return /grimoire/.test(k) ? 'grimoire' : 'tome'; + if (/pick/.test(k)) return 'hammer'; + if (WEAPON_HOLD_POSE[k]) return k; + return 'sword'; +} + +/** + * Resolve kind from EquipmentManager (kit) state. + * @param {{ equipped?: Record, equippedOffhand?: { slot?: string }|null }} equip + */ +export function resolveHoldKindFromEquip(equip) { + if (!equip) return 'sword'; + const eq = equip.equipped || {}; + for (const slot of ['sword', 'dagger', 'axe', 'mace', 'hammer', 'spear', 'pick', 'bow', 'staff']) { + if (eq[slot]) return normalizeHoldKind(slot); + } + if (equip.equippedOffhand?.slot) return normalizeHoldKind(equip.equippedOffhand.slot); + return 'sword'; +} + +function findNamed(root, names) { + if (!root) return null; + for (const n of names) { + const o = root.getObjectByName?.(n); + if (o) return o; + } + let hit = null; + const re = new RegExp(names.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|'), 'i'); + root.traverse?.((o) => { + if (hit) return; + if (re.test(String(o.name || ''))) hit = o; + }); + return hit; +} + +/** + * Lookup residual for kind+gait. sprint falls back to run. + * @returns {{ main?: HoldSidePose, off?: HoldSidePose }} + */ +export function getHoldPose(kind, gait) { + const k = normalizeHoldKind(kind); + const g0 = normalizeHoldGait(gait); + const g = g0 === 'sprint' ? 'run' : g0; + const table = WEAPON_HOLD_POSE[k] || WEAPON_HOLD_POSE.sword; + return table[g] || table.idle || {}; +} + +/** + * Mirror main pose → off when off table missing (1H dual). + * @param {HoldSidePose|undefined} main + * @returns {HoldSidePose|null} + */ +function mirrorMain(main) { + if (!main) return null; + const e = main.euler || [0, 0, 0]; + const p = main.pos || [0, 0, 0]; + return { + pos: [-(p[0] || 0), p[1] || 0, p[2] || 0], + euler: [e[0] || 0, -(e[1] || 0), -(e[2] || 0)], + }; +} + +/** + * Apply one side residual onto a hand bone/container (post-mixer). + * Mixer rewrites bone locals each frame — residual is additive after update. + * + * @param {import('three').Object3D|null} hand + * @param {HoldSidePose|null|undefined} pose + * @param {*} THREE + */ +function applySideResidual(hand, pose, THREE) { + if (!hand || !pose || !THREE) return; + const e = pose.euler || [0, 0, 0]; + const p = pose.pos || [0, 0, 0]; + if (e[0] || e[1] || e[2]) { + const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(e[0] || 0, e[1] || 0, e[2] || 0, 'XYZ')); + hand.quaternion.multiply(q); + } + if (p[0] || p[1] || p[2]) { + hand.position.x += p[0] || 0; + hand.position.y += p[1] || 0; + hand.position.z += p[2] || 0; + } +} + +/** + * Post-mixer weapon hold pose. + * + * @param {import('three').AnimationMixer|import('three').Object3D|null} mixer + * AnimationMixer (preferred) or character root Object3D + * @param {string|number} gait idle|walk|run|sprint or 0|1|2 + * @param {string} kind weapon kind / kit slot (sword, dagger, staff, …) + * @param {{ + * THREE?: any, + * hand?: 'main'|'off'|'both', + * offKind?: string|null, + * root?: import('three').Object3D|null + * }} [opts] + * @returns {boolean} true if a residual was applied + */ +export function applyWeaponHoldPose(mixer, gait, kind, opts = {}) { + if (!mixer && !opts.root) return false; + const THREE = opts.THREE || (typeof globalThis !== 'undefined' ? globalThis.THREE : null); + if (!THREE || !THREE.Quaternion || !THREE.Euler) { + // Without THREE we cannot build residual quaternions + return false; + } + + let root = opts.root || null; + if (!root && mixer) { + if (typeof mixer.getRoot === 'function') { + try { + root = mixer.getRoot(); + } catch { + root = null; + } + } + if (!root && mixer.isObject3D) root = mixer; + if (!root && mixer.root) root = mixer.root; + } + if (!root) return false; + + const pose = getHoldPose(kind, gait); + const handMode = opts.hand || 'both'; + let applied = false; + + if (handMode === 'main' || handMode === 'both') { + const rHand = findNamed(root, R_HAND_NAMES); + if (rHand && pose.main) { + applySideResidual(rHand, pose.main, THREE); + applied = true; + } + } + + if (handMode === 'off' || handMode === 'both') { + const lHand = findNamed(root, L_HAND_NAMES); + if (lHand) { + const offKind = opts.offKind != null ? opts.offKind : kind; + // Prefer offKind table when dual different weapons; else same kind off / mirror + let offPose = pose.off; + if (opts.offKind && normalizeHoldKind(opts.offKind) !== normalizeHoldKind(kind)) { + offPose = getHoldPose(offKind, gait).off || getHoldPose(offKind, gait).main; + if (offPose && !getHoldPose(offKind, gait).off) { + offPose = mirrorMain(offPose); + } + } else if (!offPose) { + offPose = mirrorMain(pose.main); + } + if (offPose) { + applySideResidual(lHand, offPose, THREE); + applied = true; + } + } + } + + return applied; +} + +// UMD-ish global for non-module main-panel forks +if (typeof globalThis !== 'undefined') { + globalThis.Grudge6WeaponHoldPose = { + WEAPON_HOLD_POSE, + normalizeHoldGait, + normalizeHoldKind, + resolveHoldKindFromEquip, + getHoldPose, + applyWeaponHoldPose, + }; +} + diff --git a/src/character/weaponMeshCollider.js b/src/character/weaponMeshCollider.js new file mode 100644 index 0000000..c8d4ace --- /dev/null +++ b/src/character/weaponMeshCollider.js @@ -0,0 +1,494 @@ +/** + * Weapon mesh → collider volume SSOT (cylinder shell + tip / grip / axis). + * + * Best practice (melee / gun stock): + * 1. Resolve the **visible weapon mesh** (WeaponAttach child or kit mesh_ids) + * 2. Sample geometry vertices (mesh-local) + * 3. Longest principal extent = blade/barrel axis (not hand +Y stick) + * 4. Cylinder along that axis; **radius = max radial extent + pad (0.02 m)** + * 5. Parent volume to the weapon mesh so it rides the animation + * + * Consumers (same volume — no parallel stacks): + * - melee residual / tip trail / projectiles → tip + axis + contact radius + * - IK grip target → grip point on axis near hand + * - effects spawn → tip / mid / grip markers + * - sounds (whoosh / impact) → tip velocity proxy + * - parry / block → cylinder intersection during guard window + * + * Convex hull note: full ConvexGeometry is optional (heavy). Oriented cylinder + * + 2 cm pad is the fleet default for thin weapons; hull can replace radius + * later without changing the consumer API. + * + * @see space/helpers/colliderHelpers.ts (Open /player-and-grass mesh-fit) + * @see WeaponMeshAttach.placeMuzzleMarker + */ + +import { + Box3, + BufferAttribute, + CylinderGeometry, + Matrix4, + Mesh, + MeshBasicMaterial, + Object3D, + Quaternion, + Vector3 +} from 'three'; + +export const WEAPON_COLLIDER_PAD_M = 0.02; + +const WEAPON_NAME_RE = + /sword|axe|hammer|staff|spear|dagger|knife|blade|mace|scyth|scythe|pick|wand|club|pistol|gun|rifle|bow|crossbow|weapon/i; +const WEAPON_EXCLUDE_RE = + /shield|quiver|bag|arrow|bow_arrow|collider_|helper_|WeaponMuzzle|WeaponTipTrail|WeaponVolume/i; + +const _box = new Box3(); +const _v = new Vector3(); +const _a = new Vector3(); +const _b = new Vector3(); +const _c = new Vector3(); +const _axis = new Vector3(); +const _center = new Vector3(); +const _tmp = new Vector3(); +const _q = new Quaternion(); +const _mat = new Matrix4(); + +/** + * @typedef {object} WeaponMeshVolume + * @property {import('three').Object3D} mesh + * @property {import('three').Object3D} [attach] WeaponAttach group if any + * @property {import('three').Vector3} tipLocal mesh-local tip + * @property {import('three').Vector3} gripLocal mesh-local grip (hand end) + * @property {import('three').Vector3} centerLocal + * @property {import('three').Vector3} axisLocal unit long axis (grip → tip) + * @property {number} radiusM cylinder radius including pad + * @property {number} lengthM cylinder length (m) + * @property {number} padM + * @property {string} shape 'cylinder' + * @property {import('three').Object3D|null} debugMesh + * @property {import('three').Object3D|null} markers tip/grip/mid Object3Ds under mesh + */ + +/** + * Prefer catalog WeaponAttach mesh, else visible kit weapon meshes. + * @param {import('three').Object3D|null} root character model + * @param {import('three').Object3D|null} [weaponAttach] + * @returns {import('three').Object3D|null} + */ +export function resolveWeaponMesh(root, weaponAttach = null) { + // 1) WeaponAttach payload (catalog GLB under hand) + if (weaponAttach) { + let best = null; + let bestVol = 0; + weaponAttach.traverse((o) => { + if (!o.isMesh || !o.visible) return; + if (WEAPON_EXCLUDE_RE.test(o.name || '')) return; + if (!o.geometry) return; + if (!o.geometry.boundingBox) o.geometry.computeBoundingBox?.(); + const bb = o.geometry.boundingBox; + if (!bb || bb.isEmpty()) return; + const s = bb.getSize(_tmp); + const vol = s.x * s.y * s.z; + if (vol > bestVol) { + bestVol = vol; + best = o; + } + }); + if (best) return best; + // whole attach as fallback + return weaponAttach; + } + + if (!root) return null; + const hits = []; + root.traverse((o) => { + if (!o.isMesh || !o.visible) return; + const n = o.name || ''; + if (WEAPON_EXCLUDE_RE.test(n)) return; + if (WEAPON_NAME_RE.test(n) || o.userData?.labWeapon || o.userData?.equipGroup === 'weapon_r') { + hits.push(o); + } + }); + hits.sort((a, b) => { + const score = (m) => + /sword/i.test(m.name) ? 0 : /axe|hammer|spear/i.test(m.name) ? 1 : 2; + return score(a) - score(b); + }); + return hits[0] || null; +} + +/** + * Collect mesh-local vertex positions (handles indexed + non-indexed). + * @param {import('three').Mesh} mesh + * @param {number} [maxSamples] + * @returns {Float32Array|null} xyz packed + */ +export function sampleMeshLocalPositions(mesh, maxSamples = 512) { + const geo = mesh?.geometry; + if (!geo) return null; + const pos = geo.attributes?.position; + if (!pos || !pos.count) return null; + + const n = pos.count; + const step = Math.max(1, Math.floor(n / maxSamples)); + const count = Math.ceil(n / step); + const out = new Float32Array(count * 3); + let w = 0; + for (let i = 0; i < n; i += step) { + out[w++] = pos.getX(i); + out[w++] = pos.getY(i); + out[w++] = pos.getZ(i); + } + return out; +} + +/** + * Fit oriented cylinder to weapon mesh vertices + pad. + * Axis = longest AABB extent in mesh-local (stable for blades/barrels). + * + * @param {import('three').Object3D} mesh + * @param {{ padM?: number, maxSamples?: number }} [opts] + * @returns {WeaponMeshVolume|null} + */ +export function fitWeaponCylinderFromMesh(mesh, opts = {}) { + if (!mesh) return null; + const padM = opts.padM != null ? opts.padM : WEAPON_COLLIDER_PAD_M; + + // Prefer raw geometry samples in mesh local + let samples = mesh.isMesh ? sampleMeshLocalPositions(mesh, opts.maxSamples ?? 512) : null; + + // Fallback: world AABB → mesh local corners + if (!samples || samples.length < 9) { + mesh.updateWorldMatrix(true, true); + _box.setFromObject(mesh); + if (_box.isEmpty()) return null; + const inv = new Matrix4().copy(mesh.matrixWorld).invert(); + const { min, max } = _box; + const corners = []; + for (const x of [min.x, max.x]) { + for (const y of [min.y, max.y]) { + for (const z of [min.z, max.z]) { + _v.set(x, y, z).applyMatrix4(inv); + corners.push(_v.x, _v.y, _v.z); + } + } + } + samples = new Float32Array(corners); + } + + // Centroid + let cx = 0; + let cy = 0; + let cz = 0; + const nPts = samples.length / 3; + for (let i = 0; i < samples.length; i += 3) { + cx += samples[i]; + cy += samples[i + 1]; + cz += samples[i + 2]; + } + cx /= nPts; + cy /= nPts; + cz /= nPts; + _center.set(cx, cy, cz); + + // Covariance for principal axis (longest eigenvector ≈ blade) + let cxx = 0; + let cxy = 0; + let cxz = 0; + let cyy = 0; + let cyz = 0; + let czz = 0; + for (let i = 0; i < samples.length; i += 3) { + const dx = samples[i] - cx; + const dy = samples[i + 1] - cy; + const dz = samples[i + 2] - cz; + cxx += dx * dx; + cxy += dx * dy; + cxz += dx * dz; + cyy += dy * dy; + cyz += dy * dz; + czz += dz * dz; + } + // Power iteration for dominant eigenvector + _axis.set(1, 0.2, 0.1).normalize(); + for (let it = 0; it < 12; it++) { + const x = cxx * _axis.x + cxy * _axis.y + cxz * _axis.z; + const y = cxy * _axis.x + cyy * _axis.y + cyz * _axis.z; + const z = cxz * _axis.x + cyz * _axis.y + czz * _axis.z; + _axis.set(x, y, z); + if (_axis.lengthSq() < 1e-12) { + _axis.set(0, 1, 0); + break; + } + _axis.normalize(); + } + + // Project points onto axis → min/max scalar + radial max + let tMin = Infinity; + let tMax = -Infinity; + let rMax = 0; + for (let i = 0; i < samples.length; i += 3) { + _v.set(samples[i] - cx, samples[i + 1] - cy, samples[i + 2] - cz); + const t = _v.dot(_axis); + if (t < tMin) tMin = t; + if (t > tMax) tMax = t; + _tmp.copy(_v).addScaledVector(_axis, -t); + const r = _tmp.length(); + if (r > rMax) rMax = r; + } + + let lengthM = Math.max(0.08, tMax - tMin); + let radiusM = Math.max(0.015, rMax) + padM; + + // Grip = end closer to hand if hand provided later; default low-t = grip + // Tip = high-t end of axis + const gripLocal = new Vector3().copy(_center).addScaledVector(_axis, tMin); + const tipLocal = new Vector3().copy(_center).addScaledVector(_axis, tMax); + const centerLocal = new Vector3().copy(_center).addScaledVector(_axis, (tMin + tMax) * 0.5); + const axisLocal = _axis.clone().normalize(); + + /** @type {WeaponMeshVolume} */ + const vol = { + mesh, + attach: mesh.parent?.userData?.weaponAttach ? mesh.parent : null, + tipLocal, + gripLocal, + centerLocal, + axisLocal, + radiusM, + lengthM, + padM, + shape: 'cylinder', + debugMesh: null, + markers: null + }; + return vol; +} + +/** + * Build tip / mid / grip marker Object3Ds under the weapon mesh (mesh-local). + * @param {WeaponMeshVolume} vol + * @param {{ debug?: boolean }} [opts] + */ +export function attachWeaponVolumeMarkers(vol, opts = {}) { + if (!vol?.mesh) return null; + // Clear previous + const old = vol.mesh.getObjectByName('WeaponVolume'); + if (old) vol.mesh.remove(old); + + const root = new Object3D(); + root.name = 'WeaponVolume'; + root.userData.weaponVolume = true; + root.userData.radiusM = vol.radiusM; + root.userData.lengthM = vol.lengthM; + root.userData.padM = vol.padM; + + const tip = new Object3D(); + tip.name = 'WeaponTip'; + tip.userData.isWeaponTip = true; + tip.position.copy(vol.tipLocal); + root.add(tip); + + const grip = new Object3D(); + grip.name = 'WeaponGrip'; + grip.userData.isWeaponGrip = true; + grip.position.copy(vol.gripLocal); + root.add(grip); + + const mid = new Object3D(); + mid.name = 'WeaponMid'; + mid.userData.isWeaponMid = true; + mid.position.copy(vol.centerLocal); + root.add(mid); + + // Axis helper: +Y of empty points grip→tip for lookAt consumers + root.userData.tip = tip; + root.userData.grip = grip; + root.userData.mid = mid; + root.userData.axisLocal = vol.axisLocal.clone(); + + if (opts.debug) { + // Cylinder along local axis: default CylinderGeometry is +Y; rotate to axis + const cyl = new CylinderGeometry(vol.radiusM, vol.radiusM, vol.lengthM, 10, 1, true); + const mat = new MeshBasicMaterial({ + color: 0xff3344, + wireframe: true, + transparent: true, + opacity: 0.55, + depthWrite: false + }); + const debug = new Mesh(cyl, mat); + debug.name = 'WeaponVolumeDebug'; + debug.position.copy(vol.centerLocal); + // Align +Y to axisLocal + const yUp = new Vector3(0, 1, 0); + _q.setFromUnitVectors(yUp, vol.axisLocal.clone().normalize()); + debug.quaternion.copy(_q); + debug.userData.layer = 'helpers'; + root.add(debug); + vol.debugMesh = debug; + } + + vol.mesh.add(root); + vol.markers = root; + // Mirror tip on attach for getWeaponTip / muzzle parity + const attach = vol.attach || (vol.mesh.parent?.userData?.weaponAttach ? vol.mesh.parent : null); + if (attach) { + attach.userData.muzzle = tip; + attach.userData.weaponTip = tip; + attach.userData.weaponGrip = grip; + attach.userData.weaponVolume = vol; + } + vol.mesh.userData.weaponVolume = vol; + return root; +} + +/** + * Orient grip toward hand: if grip is farther from hand than tip, flip axis. + * @param {WeaponMeshVolume} vol + * @param {import('three').Object3D|null} handBone + */ +export function orientVolumeGripToHand(vol, handBone) { + if (!vol?.mesh || !handBone) return vol; + vol.mesh.updateWorldMatrix(true, true); + handBone.updateWorldMatrix?.(true, true); + handBone.getWorldPosition(_a); + _b.copy(vol.gripLocal); + vol.mesh.localToWorld(_b); + _c.copy(vol.tipLocal); + vol.mesh.localToWorld(_c); + const dGrip = _a.distanceToSquared(_b); + const dTip = _a.distanceToSquared(_c); + if (dTip < dGrip) { + // Flip: tip was closer to hand + const t = vol.tipLocal.clone(); + vol.tipLocal.copy(vol.gripLocal); + vol.gripLocal.copy(t); + vol.axisLocal.negate(); + } + return vol; +} + +/** + * Full pipeline: resolve mesh → fit cylinder → markers → optional debug. + * @param {import('three').Object3D|null} characterRoot + * @param {{ + * weaponAttach?: import('three').Object3D|null, + * handBone?: import('three').Object3D|null, + * padM?: number, + * debug?: boolean + * }} [opts] + * @returns {WeaponMeshVolume|null} + */ +export function buildWeaponMeshVolume(characterRoot, opts = {}) { + const mesh = resolveWeaponMesh(characterRoot, opts.weaponAttach || null); + if (!mesh) return null; + const vol = fitWeaponCylinderFromMesh(mesh, { padM: opts.padM ?? WEAPON_COLLIDER_PAD_M }); + if (!vol) return null; + if (opts.weaponAttach) vol.attach = opts.weaponAttach; + orientVolumeGripToHand(vol, opts.handBone || null); + attachWeaponVolumeMarkers(vol, { debug: !!opts.debug }); + return vol; +} + +/** + * World tip from volume (or fallback). + * @param {WeaponMeshVolume|null} vol + * @param {import('three').Vector3} [out] + */ +export function getVolumeTipWorld(vol, out = new Vector3()) { + if (!vol?.mesh) return out.set(0, 0, 0); + const tip = vol.markers?.userData?.tip; + if (tip) { + tip.getWorldPosition(out); + return out; + } + out.copy(vol.tipLocal); + vol.mesh.localToWorld(out); + return out; +} + +/** + * World grip for IK / whoosh origin. + * @param {WeaponMeshVolume|null} vol + * @param {import('three').Vector3} [out] + */ +export function getVolumeGripWorld(vol, out = new Vector3()) { + if (!vol?.mesh) return out.set(0, 0, 0); + const grip = vol.markers?.userData?.grip; + if (grip) { + grip.getWorldPosition(out); + return out; + } + out.copy(vol.gripLocal); + vol.mesh.localToWorld(out); + return out; +} + +/** + * Unit axis grip→tip in world space. + * @param {WeaponMeshVolume|null} vol + * @param {import('three').Vector3} [out] + */ +export function getVolumeAxisWorld(vol, out = new Vector3()) { + if (!vol?.mesh) return out.set(0, 0, 1); + _a.copy(vol.gripLocal); + _b.copy(vol.tipLocal); + vol.mesh.localToWorld(_a); + vol.mesh.localToWorld(_b); + out.copy(_b).sub(_a); + if (out.lengthSq() < 1e-10) out.set(0, 0, 1); + else out.normalize(); + return out; +} + +/** + * Melee contact radius for residual / parry (cylinder radius). + * @param {WeaponMeshVolume|null} vol + * @param {number} [extraBeyond=0] + */ +export function getVolumeContactRadius(vol, extraBeyond = 0) { + const r = vol?.radiusM ?? 0.05; + return r + Math.max(0, extraBeyond); +} + +/** + * Point-vs-cylinder test in world space (for parry / block). + * @param {WeaponMeshVolume|null} vol + * @param {import('three').Vector3} worldPoint + * @param {number} [pointRadius=0] + */ +export function pointHitsWeaponVolume(vol, worldPoint, pointRadius = 0) { + if (!vol?.mesh || !worldPoint) return false; + getVolumeGripWorld(vol, _a); + getVolumeTipWorld(vol, _b); + _axis.copy(_b).sub(_a); + const len = _axis.length(); + if (len < 1e-6) return false; + _axis.multiplyScalar(1 / len); + _tmp.copy(worldPoint).sub(_a); + let t = _tmp.dot(_axis); + t = Math.max(0, Math.min(len, t)); + _c.copy(_a).addScaledVector(_axis, t); + const dist = _c.distanceTo(worldPoint); + return dist <= vol.radiusM + pointRadius; +} + +/** + * Serialize for prefab / save (SI metres, mesh-local). + * @param {WeaponMeshVolume|null} vol + */ +export function weaponVolumeToJSON(vol) { + if (!vol) return null; + return { + shape: 'cylinder', + padM: vol.padM, + radiusM: vol.radiusM, + lengthM: vol.lengthM, + meshName: vol.mesh?.name || null, + tipLocal: { x: vol.tipLocal.x, y: vol.tipLocal.y, z: vol.tipLocal.z }, + gripLocal: { x: vol.gripLocal.x, y: vol.gripLocal.y, z: vol.gripLocal.z }, + centerLocal: { x: vol.centerLocal.x, y: vol.centerLocal.y, z: vol.centerLocal.z }, + axisLocal: { x: vol.axisLocal.x, y: vol.axisLocal.y, z: vol.axisLocal.z } + }; +} diff --git a/src/combat/CombatFocus.js b/src/combat/CombatFocus.js new file mode 100644 index 0000000..20cadb8 --- /dev/null +++ b/src/combat/CombatFocus.js @@ -0,0 +1,433 @@ +import { MathUtils, Raycaster, Vector2, Vector3 } from 'three'; +import { EventEmitter } from '../utils/EventEmitter.js'; +import { settings } from '../config/settings.js'; + +const _ndc = new Vector2(); +const _hit = new Vector3(); +const _tmp = new Vector3(); +const _camFwd = new Vector3(); + +/** + * Soft-lock combat focus (production targeting SSOT). + * + * From grudge-combat-targeting + grudge-third-person-controller angles: + * - RMB **click** toggles focusEnabled (sticky hard-focus flag) + * - RMB **hold** = orbit only (does not clear toggle) + * - Soft lock: keep selectedTarget without snapping camera hard + * - Body faces camera-forward when focus on; travel dir when off + * + * Does **not** replace MouseAim ground ray — complements it. + * LMB path cast stays primary in Casting lab; soft lock target via + * raycast pick when focus is on (optional) or explicit select list. + * + * @see docs/COMBAT_CAMERA_FOCUS_SSOT.md + * @see skill grudge-combat-targeting + */ +export class CombatFocus extends EventEmitter { + constructor() { + super(); + /** Sticky focus mode (RMB toggle) */ + this.focusEnabled = false; + /** Soft lock active (auto ON with focus when settings.aim.softLockOnFocus) */ + this.softLockEnabled = false; + /** RMB currently held (orbit) */ + this.rmbHeld = false; + /** + * Soft-lock target + * @type {{ id: string, point: Vector3, mesh?: import('three').Object3D, kind?: string }|null} + */ + this.selectedTarget = null; + /** Screen crosshair should show */ + this.showCrosshair = false; + /** Meshes tagged userData.selectable for pick */ + this.selectables = []; + this.raycaster = new Raycaster(); + this.raycaster.far = 80; + this._rmbDownAt = 0; + this._rmbMoved = false; + /** Index into sorted target list for Tab cycle */ + this._cycleIndex = -1; + } + + /** + * @param {import('three').Object3D[]} meshes + */ + setSelectables(meshes) { + this.selectables = meshes || []; + } + + addSelectable(mesh, kind = 'hostile') { + if (!mesh) return; + mesh.userData.selectable = kind; + if (!this.selectables.includes(mesh)) this.selectables.push(mesh); + } + + /** + * @param {import('three').Object3D} mesh + */ + removeSelectable(mesh) { + if (!mesh) return; + this.selectables = this.selectables.filter((m) => m !== mesh); + if (this.selectedTarget?.mesh === mesh) this.clearTarget(); + } + + clearTarget() { + this.selectedTarget = null; + this.emit('target', null); + } + + /** + * @param {{ id?: string, point: Vector3, mesh?: import('three').Object3D, kind?: string }} ref + */ + setTarget(ref) { + if (!ref?.point) { + this.clearTarget(); + return; + } + this.selectedTarget = { + id: ref.id || ref.mesh?.uuid || 'target', + point: ref.point.clone(), + mesh: ref.mesh, + kind: ref.kind || ref.mesh?.userData?.selectable || 'hostile' + }; + this.emit('target', this.selectedTarget); + } + + toggleFocus() { + this.focusEnabled = !this.focusEnabled; + this.showCrosshair = this.focusEnabled; + // Soft lock engages with focus (Fortnite/TPS style — not free roam) + if (this.focusEnabled && settings.aim?.softLockOnFocus !== false) { + this.softLockEnabled = true; + } else if (!this.focusEnabled) { + // Keep selected target for re-focus, but mark soft lock idle + this.softLockEnabled = false; + } + this.emit('focus', this.focusEnabled); + return this.focusEnabled; + } + + /** Human toast copy for focus mode. */ + focusToast() { + return this.focusEnabled + ? 'Focus ON · mouse = look · crosshair aim · LMB attack' + : 'Focus OFF · free cursor · LMB select'; + } + + /** + * Pointer down RMB — start hold; click vs drag decided on up. + * @param {PointerEvent} e + */ + onPointerDown(e) { + if (e.button !== 2) return; + this.rmbHeld = true; + this._rmbDownAt = performance.now(); + this._rmbMoved = false; + this._lastX = e.clientX; + this._lastY = e.clientY; + // Hold mode: focus while RMB down (settings.controls.focusToggle === false) + if (settings.controls?.focusToggle === false && !this.focusEnabled) { + this.focusEnabled = true; + this.showCrosshair = true; + this.emit('focus', true); + this.emit('toast', this.focusToast()); + } + } + + onPointerMove(e) { + if (!this.rmbHeld) return; + const dx = e.clientX - (this._lastX || e.clientX); + const dy = e.clientY - (this._lastY || e.clientY); + if (Math.hypot(dx, dy) > 4) this._rmbMoved = true; + this._lastX = e.clientX; + this._lastY = e.clientY; + } + + /** + * RMB release — if short click without drag, toggle focus. + * @param {PointerEvent} [e] + */ + onPointerUp(e) { + if (e && e.button !== 2 && e.button !== undefined) return; + const held = performance.now() - this._rmbDownAt; + const wasHeld = this.rmbHeld; + this.rmbHeld = false; + if (!wasHeld) return; + + // Hold mode: release RMB ends focus + if (settings.controls?.focusToggle === false) { + if (this.focusEnabled) { + this.focusEnabled = false; + this.showCrosshair = false; + this.emit('focus', false); + this.emit('toast', this.focusToast()); + } + return; + } + + // Toggle mode (default): short click without drag + if (!this._rmbMoved && held < 280) { + this.toggleFocus(); + this.emit('toast', this.focusToast()); + } + } + + /** Clear latched RMB on blur / tab hide */ + clearHeld() { + this.rmbHeld = false; + } + + /** + * Raycast pick from NDC into selectables (soft lock target). + * @param {import('three').Camera} camera + * @param {Vector2} ndc + * @returns {boolean} + */ + pickFromNdc(camera, ndc) { + if (!this.selectables.length) return false; + this.raycaster.setFromCamera(ndc, camera); + const hits = this.raycaster.intersectObjects(this.selectables, true); + for (const h of hits) { + let o = h.object; + while (o && !o.userData?.selectable && o.parent) o = o.parent; + if (o?.userData?.selectable) { + const pt = h.point.clone(); + pt.y += 1.1; // chest-ish + this.setTarget({ + id: o.uuid, + point: pt, + mesh: o, + kind: o.userData.selectable + }); + return true; + } + } + return false; + } + + /** + * Soft-lock aim point: magnetic blend toward target (no hard snap). + * Prefer 3D hit points from MouseAim.updateFocusAim (cone-limited). + * @param {Vector3} playerPos + * @param {Vector3} groundAim MouseAim.point or hitPoint + * @param {Vector3} out + * @returns {Vector3} + */ + resolveAimPoint(playerPos, groundAim, out = new Vector3()) { + // Track moving soft-lock mesh first + if (this.selectedTarget?.mesh) { + this.selectedTarget.mesh.getWorldPosition(_hit); + _hit.y += 1.1; + this.selectedTarget.point.lerp(_hit, 0.4); + } + if (!this.focusEnabled || !this.selectedTarget) { + return out.copy(groundAim); + } + // Soft lock: blend toward locked target (3D-aware) + const soft = settings.aim?.softLockBlend ?? 0.55; + out.copy(groundAim); + out.x = MathUtils.lerp(out.x, this.selectedTarget.point.x, soft); + out.z = MathUtils.lerp(out.z, this.selectedTarget.point.z, soft); + out.y = MathUtils.lerp( + out.y, + this.selectedTarget.point.y, + soft * 0.75 + ); + // Keep ahead of player (min 1.5 m XZ) + _tmp.subVectors(out, playerPos); + _tmp.y = 0; + const d = _tmp.length(); + if (d < 1.5 && d > 1e-4) { + _tmp.multiplyScalar(1.5 / d); + const y = out.y; + out.copy(playerPos).add(_tmp); + out.y = y; + } + return out; + } + + /** Soft-lock world point for MouseAim magnetic cone (null if none). */ + getSoftLockPoint() { + const softOn = + this.softLockEnabled || + (this.focusEnabled && settings.aim?.softLockOnFocus !== false); + if (!softOn || !this.selectedTarget) return null; + if (this.selectedTarget.mesh) { + // Drop dead / removed meshes + if (!this.selectedTarget.mesh.parent) { + this.clearTarget(); + return null; + } + this.selectedTarget.mesh.getWorldPosition(_hit); + _hit.y += 1.1; + this.selectedTarget.point.lerp(_hit, 0.35); + } + return this.selectedTarget.point; + } + + /** + * Live selectable entries with world points (for Tab cycle + auto-acquire). + * @param {Vector3} playerPos + * @param {number} [range] + * @returns {Array<{ id: string, point: Vector3, mesh: import('three').Object3D, kind: string, dist: number }>} + */ + listTargetsInRange(playerPos, range) { + const maxR = range ?? settings.aim?.softLockRange ?? 28; + const maxR2 = maxR * maxR; + /** @type {Array<{ id: string, point: Vector3, mesh: import('three').Object3D, kind: string, dist: number }>} */ + const out = []; + for (const mesh of this.selectables) { + if (!mesh || !mesh.parent || mesh.visible === false) continue; + if (mesh.userData?.dead || mesh.userData?.selectable === false) continue; + mesh.getWorldPosition(_hit); + _hit.y += 1.1; + const dx = _hit.x - playerPos.x; + const dz = _hit.z - playerPos.z; + const d2 = dx * dx + dz * dz; + if (d2 > maxR2) continue; + out.push({ + id: mesh.uuid, + point: _hit.clone(), + mesh, + kind: mesh.userData?.selectable || 'hostile', + dist: Math.sqrt(d2) + }); + } + // Nearest first for stable Tab order + out.sort((a, b) => a.dist - b.dist); + return out; + } + + /** + * Score targets for soft lock: prefer in-front of camera / body facing. + * Lower score = better. + * @param {{ point: Vector3, dist: number }} t + * @param {Vector3} playerPos + * @param {Vector3|null} forward XZ unit (camera or body) + */ + _scoreTarget(t, playerPos, forward) { + let score = t.dist; + if (forward && forward.lengthSq() > 1e-6) { + _tmp.subVectors(t.point, playerPos); + _tmp.y = 0; + if (_tmp.lengthSq() > 1e-6) { + _tmp.normalize(); + const dot = MathUtils.clamp(forward.dot(_tmp), -1, 1); + const ang = Math.acos(dot); // 0 = dead ahead + // Heavy weight on frontal cone — action soft-lock awareness + score = t.dist * (0.35 + ang * 1.4) + ang * 8; + } + } + return score; + } + + /** + * Auto soft-lock best directional target when focus engages. + * @param {Vector3} playerPos + * @param {Vector3|null} [forward] camera XZ forward for cone preference + * @returns {boolean} + */ + acquireNearest(playerPos, forward = null) { + return this.acquireBest(playerPos, forward); + } + + /** + * Best target: distance × frontal cone (directional awareness). + * @param {Vector3} playerPos + * @param {Vector3|null} [forward] + * @returns {boolean} + */ + acquireBest(playerPos, forward = null) { + const list = this.listTargetsInRange(playerPos); + if (!list.length) return false; + let best = list[0]; + let bestS = this._scoreTarget(best, playerPos, forward); + for (let i = 1; i < list.length; i++) { + const s = this._scoreTarget(list[i], playerPos, forward); + if (s < bestS) { + bestS = s; + best = list[i]; + } + } + // Order cycle list by score for Tab + list.sort( + (a, b) => + this._scoreTarget(a, playerPos, forward) - this._scoreTarget(b, playerPos, forward) + ); + this._cycleList = list; + this._cycleIndex = list.findIndex((t) => t.id === best.id); + this.setTarget(best); + this.softLockEnabled = true; + return true; + } + + /** + * Tab / Shift+Tab soft-lock cycle — directional order when possible. + * @param {Vector3} playerPos + * @param {boolean} [reverse] + * @param {Vector3|null} [forward] + * @returns {boolean} + */ + cycleTarget(playerPos, reverse = false, forward = null) { + const list = this.listTargetsInRange(playerPos); + if (!list.length) { + this.clearTarget(); + this._cycleIndex = -1; + this.emit('toast', 'No targets in range'); + return false; + } + list.sort( + (a, b) => + this._scoreTarget(a, playerPos, forward) - this._scoreTarget(b, playerPos, forward) + ); + this._cycleList = list; + let idx = list.findIndex((t) => t.id === this.selectedTarget?.id); + if (idx < 0) idx = reverse ? 0 : -1; + idx = reverse + ? (idx - 1 + list.length) % list.length + : (idx + 1) % list.length; + this._cycleIndex = idx; + this.setTarget(list[idx]); + this.softLockEnabled = true; + const label = + list[idx].mesh?.userData?.displayName || + list[idx].mesh?.name || + list[idx].kind || + 'Target'; + this.emit('toast', `Target · ${label} (${idx + 1}/${list.length})`); + return true; + } + + /** + * Desired body yaw for facing. + * Focus ON → camera forward; else aim / travel. + * @param {number} aimYaw + * @param {number} travelYaw + * @param {boolean} moving + * @param {import('three').Camera} camera + */ + resolveBodyYaw(aimYaw, travelYaw, moving, camera) { + if (this.focusEnabled || this.rmbHeld) { + camera.getWorldDirection(_camFwd); + _camFwd.y = 0; + if (_camFwd.lengthSq() < 1e-6) return aimYaw; + _camFwd.normalize(); + return Math.atan2(_camFwd.x, _camFwd.z); + } + if (moving && settings.aim?.faceTravelWhenMoving) return travelYaw; + return aimYaw; + } + + snapshot() { + return { + focusEnabled: this.focusEnabled, + softLockEnabled: + this.softLockEnabled || + (this.focusEnabled && settings.aim?.softLockOnFocus !== false), + rmbHeld: this.rmbHeld, + hasTarget: !!this.selectedTarget, + targetId: this.selectedTarget?.id || null, + showCrosshair: this.showCrosshair || this.focusEnabled + }; + } +} diff --git a/src/combat/DrcCombatController.js b/src/combat/DrcCombatController.js new file mode 100644 index 0000000..5192d89 --- /dev/null +++ b/src/combat/DrcCombatController.js @@ -0,0 +1,3121 @@ +import { CatmullRomCurve3, MathUtils, Vector3 } from 'three'; +import { + getActiveSkills, + getMeleeStrikeSkill, + setActiveSkillTree, + setSkillKitPage, + skillBySlot, + skillForFKey +} from './drcSkills.js'; +import { settings } from '../config/settings.js'; +import { residualFromSettings } from '../vfx/effectPrefab.js'; +import { getSkillBinding } from './skillBindings.js'; +import { bindFromCatalogSkill, staffBindFor } from './staffWeaponSkillsBind.js'; +import { vfxIdForSkill, animRoleForSkill } from '../api/weaponSkillsCatalog.js'; +import { dodgeDistanceM, mmToM, mToMm } from './motionMath.js'; +import { + skillCastCosts, + pathCastCosts, + castIntensity +} from './castResources.js'; +import { signatureForElement } from './staffSignatureSkills.js'; +import { + enrichSkillDelivery, + resolveDeliveryPose, + STAFF_CHARGE_MESH +} from './skillDelivery.js'; +import { SkillProjectileSystem } from './SkillProjectileSystem.js'; +import { applyKnockback } from './hitReaction.js'; +import { + isStaffNormalAttack, + STAFF_NORMAL_ATTACK, + staffProjectileMeshUrl +} from '../vfx/staffOrbVfx.js'; +import { inferElementAttackKind } from '../vfx/elementAttackVfx.js'; +import { + isPistolBulletSkill, + pistolBulletCount, + PISTOL_BULLET +} from '../vfx/pistolBulletVfx.js'; +import { + FLINTLOCK_FIRE, + FLINTLOCK_RELOAD, + PISTOL_SOFT_LOCK, + pistolHitFrameSec +} from '../config/pistolAnimSsot.js'; +import { + playForWeaponSkillCast, + playForImpact, + playParrySfx, + playHealSfx, + setPlayerBurningSfx +} from '../audio/skillSfx.js'; +import { + createFlintlockChamber, + skillNeedsLoad, + isFlintlockContext, + makeReloadSkillDef +} from './flintlockChamber.js'; +import { getEquippedWeapon } from './equippedWeaponRuntime.js'; +import { + WeaponChargeSession, + isChargeableWeaponSkill, + chargeBarState, + weaponChargeConfig, + weaponRestAfterFire +} from './weaponChargeSystem.js'; +import { + planElementalLinearCast, + fireLinearFromPlan +} from './elementalLinearCast.js'; +import { SkillStatusSystem } from './skillStatusSystem.js'; +import { WeaponTipTrailSystem } from '../vfx/weaponTipTrail.js'; +import { pointHitsWeaponVolume } from '../character/weaponMeshCollider.js'; +import { getBackWaterBuffs } from '../config/backSlotMobilitySsot.js'; +import { WORLD } from '../config/worldScale.js'; + +const _origin = new Vector3(); +const _tip = new Vector3(); +const _fwd = new Vector3(); +const _end = new Vector3(); +const _mid = new Vector3(); +const _move = new Vector3(); + +/** + * DRC combat session on Casting Abilities systems: + * - session: equip | combat (Q toggles) + * - WASD locomotion on Toon RTS gait clips + * - skills 1–4 fire AbilityManager VFX + CharacterController one-shots + * - melee skill uses attack anim + short slash path + * + * Does not re-implement epicfight CombatController — hosts this sandbox only. + */ +export class DrcCombatController { + /** + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * abilities: import('../abilities/AbilityManager.js').AbilityManager, + * camera: import('three').Camera, + * physics?: import('../physics/PhysicsWorld.js').PhysicsWorld|null, + * vfx?: import('../vfx/VfxDirector.js').VfxDirector|null, + * aim?: import('../input/MouseAim.js').MouseAim|null, + * onToast?: (msg: string) => void, + * onSession?: (session: 'equip'|'combat') => void + * }} opts + */ + constructor(opts) { + this.character = opts.character; + this.abilities = opts.abilities; + this.camera = opts.camera; + this.physics = opts.physics || null; + this.vfx = opts.vfx || null; + this.aim = opts.aim || null; + /** Mesh projectiles + contact force (fire/ice summons) */ + this.scene = opts.scene || null; + this.projectiles = + opts.projectiles || + (opts.scene + ? new SkillProjectileSystem({ + scene: opts.scene, + vfx: this.vfx, + onHit: (hit) => this._onProjectileHit(hit) + }) + : null); + /** + * Weapon-tip trail + apex residual (blade ribbon · fire blur · physics past tip). + * @type {WeaponTipTrailSystem|null} + */ + this.tipTrail = + opts.tipTrail || + (opts.scene + ? new WeaponTipTrailSystem({ + scene: opts.scene, + character: this.character, + projectiles: this.projectiles, + vfx: this.vfx, + abilities: this.abilities + }) + : null); + /** @type {import('./CombatFocus.js').CombatFocus|null} */ + this.combatFocus = opts.combatFocus || null; + /** @type {import('../core/SessionState.js').SessionState|null} */ + this.sessionState = opts.sessionState || null; + this.onToast = opts.onToast || (() => {}); + this.onSession = opts.onSession || (() => {}); + /** Snow-brawl alternate hand for projectile spawn offset */ + this._throwHand = 'right'; + /** + * Linear skillshot bridge (learned LinearAbilityCasting systems). + * @type {import('../skillshot/LinearSkillBridge.js').LinearSkillBridge|null} + */ + this.linearSkills = opts.linearSkills || null; + /** Production status effects (freeze/stun/push/slow/…) */ + this.statuses = new SkillStatusSystem({ + onToast: (m) => this.onToast(m), + getElapsed: () => this.elapsed + }); + /** Last skill used (for hit status package) */ + this._lastSkill = null; + + /** @type {'equip'|'combat'} — mirrored in SessionState.drc */ + this.session = this.sessionState?.drc || 'combat'; + this.skills = getActiveSkills(); + /** Focus buff: until elapsed, spell damage mul (T0 Apprentice Wand Focus) */ + this._focusUntil = 0; + this._focusMul = 1; + /** Ward / Take Cover: until elapsed, incoming damage mul (1 − reduce) */ + this._wardUntil = 0; + this._wardReduce = 0; + /** Flintlock single-load chamber (empty → key 1 = Reload) */ + this.flintlock = createFlintlockChamber(); + /** Hold-to-charge weapon session (Charged Shot UX) */ + this.weaponCharge = new WeaponChargeSession(); + /** Best rest — next weapon skill blocked until elapsed */ + this._weaponRestUntil = 0; + /** Short global combat timer between weapon attacks */ + this._weaponGcdUntil = 0; + /** Air fall locomotion (deterministic) */ + this._airTime = 0; + this._lastVy = 0; + this._impactVy = 0; + // Catalog starters: ?wand=1 · ?sapling=1 · ?sword=1 Training Sword · ?arcane=1 + if (typeof location !== 'undefined') { + if (/[?&]wand=1\b/.test(location.search)) { + setActiveSkillTree('wand'); + this.skills = getActiveSkills(); + } else if (/[?&]sapling=1\b/.test(location.search)) { + setActiveSkillTree('sapling'); + this.skills = getActiveSkills(); + } else if (/[?&]sword=1\b/.test(location.search)) { + setActiveSkillTree('equipped'); + this.skills = getActiveSkills(); + this._pendingEquipId = 't0-sword'; + } else if (/[?&]arcane=1\b/.test(location.search)) { + setActiveSkillTree('arcane'); + this.skills = getActiveSkills(); + } + } + /** @type {Map} skillId → readyAt elapsed */ + this._cdUntil = new Map(); + this.stamina = 100; + this.maxStamina = 100; + /** Mana pool (spells) — dual resource with stamina */ + this.mana = settings.drc?.manaMax ?? 100; + this.maxMana = settings.drc?.manaMax ?? 100; + /** Health pool — real number (starts full; no fake regen UI when undamaged) */ + this.maxHealth = settings.drc?.healthMax ?? 100; + this.health = this.maxHealth; + this.elapsed = 0; + /** Last path-cast intensity (for HUD / VFX) */ + this.lastCastIntensity = 1; + + /** + * Active cast channel (cast bar + cast anim). + * @type {{ + * label: string, + * element: string, + * startedAt: number, + * duration: number, + * endsAt: number, + * onComplete: () => void, + * interruptible: boolean, + * aim?: { x: number, y: number, z: number } + * }|null} + */ + this._cast = null; + /** @type {((state: object|null) => void)|null} */ + this.onCastBar = opts.onCastBar || null; + + this.moveSpeed = settings.drc?.moveSpeed ?? 3.6; + this.sprintMul = settings.drc?.sprintMul ?? 1.65; + this._moveX = 0; + this._moveZ = 0; + this._sprinting = false; + /** Toggle-sprint latch (when settings.controls.sprintToggle) */ + this._sprintLatched = false; + /** + * Backtick (`) auto run / freeride sail — holds forward + sprint until toggled off. + * Same intent as Multiverse mvAutoTraverse. + */ + this._autoTraverse = false; + this._wasShiftDown = false; + this._yaw = 0; + this._usePhysics = true; + + /** Jump state machine */ + this._jumpsLeft = settings.drc?.maxJumps ?? 2; + this._wasJumpDown = false; + this._grounded = true; + this._airborne = false; + /** After backflip, reverse dash + hang window */ + this._backflipBoostT = 0; + this._backflipDir = new Vector3(); + this._backflipHardStopT = 0; + /** Look yaw held during backflip (camera does not whip reverse) */ + this._flipHoldYaw = null; + this._hangT = 0; + this._frontflipBoostT = 0; + this._frontflipDir = new Vector3(); + /** @type {Map} utility action max CD for HUD */ + this._cdMax = new Map(); + + /** Double-tap dodge: AA left · DD right · WW forward · X back */ + this._lastTap = { KeyA: 0, KeyD: 0, KeyW: 0, KeyS: 0 }; + this._keyWasDown = new Set(); + /** Active dodge / roll / slide impulse (world XZ) */ + this._dodgeT = 0; + this._dodgeDur = 0; + this._dodgeVel = new Vector3(); + /** I-frames while MM dodge / afterimage trail runs (seconds remaining) */ + this.invuln = 0; + /** Edge state for Ctrl roll + Shift+Ctrl slide */ + this._ctrlWasDown = false; + this._rollKeyWas = { KeyA: false, KeyD: false, KeyW: false, KeyS: false }; + + /** Underwater oxygen (s) — shark_fin sets breatheUnderwater → never drains */ + this.oxygen = 20; + this.maxOxygen = 20; + this._drownTick = 0; + this._inWater = false; + this._submerged = false; + } + + /** + * Equipped back-slot water buffs (shark fin → 2× swim · no shark aggro · breath). + */ + getBackWaterBuffs() { + const id = + this.character?.backSlot?.itemId || + settings.walk?.backSlot || + 'none'; + return getBackWaterBuffs(id); + } + + /** Shark / fauna AI: true = do not aggro player */ + get sharkAggroImmune() { + return !!this.getBackWaterBuffs().sharkAggroImmune; + } + + /** True while dodge MM + afterimage invuln window is active. */ + get isInvincible() { + return this.invuln > 0 || this._dodgeT > 0; + } + + /** + * Oxygen while submerged; shark_fin.breatheUnderwater → full O₂ forever. + * @param {number} dt + * @param {{ breatheUnderwater?: boolean }} buffs + */ + _tickUnderwaterBreath(dt, buffs) { + if (!this._submerged) { + // Surface recovery + this.oxygen = Math.min(this.maxOxygen, this.oxygen + dt * 4); + this._drownTick = 0; + return; + } + if (buffs?.breatheUnderwater) { + this.oxygen = this.maxOxygen; + this._drownTick = 0; + return; + } + this.oxygen = Math.max(0, this.oxygen - dt); + if (this.oxygen <= 0) { + this._drownTick += dt; + // Damage every ~0.5s while drowning + if (this._drownTick >= 0.5) { + this._drownTick = 0; + this.health = Math.max(0, (this.health || 0) - (settings.drc?.drownDamage ?? 4)); + this.onToast?.('Drowning… equip Shark Fin to breathe'); + } + } + } + + setPhysics(physics) { + this.physics = physics; + } + + setVfx(vfx) { + this.vfx = vfx; + } + + setAim(aim) { + this.aim = aim; + } + + /** + * Attach linear skillshot bridge (elemental LINE/ZONE casts). + * @param {import('../skillshot/LinearSkillBridge.js').LinearSkillBridge|null} linear + */ + setLinearSkills(linear) { + this.linearSkills = linear || null; + } + + get inCombat() { + return this.sessionState ? this.sessionState.inCombat : this.session === 'combat'; + } + + /** Prefer session.gates when present. */ + get gates() { + return this.sessionState?.gates || null; + } + + setSessionState(sessionState) { + this.sessionState = sessionState || null; + if (sessionState) this.session = sessionState.drc; + } + + toggleSession() { + this.setSession(this.session === 'combat' ? 'equip' : 'combat'); + } + + /** + * @param {'equip'|'combat'} session + */ + setSession(session) { + const next = session === 'combat' ? 'combat' : 'equip'; + if (this.session === next) return; + this.session = next; + settings.drc = settings.drc || {}; + settings.drc.session = next; + // Report to SessionState (App applies camera/HUD once on change) + this.sessionState?.setDrc?.(next); + this.onSession(next); + this.onToast( + next === 'combat' + ? 'Combat · Shift run-turn · Ctrl+A/D roll · Shift+Ctrl slide · AA/DD dodge · C parry' + : 'Equip — I inventory · mesh loadout' + ); + } + + /** + * @param {Set} keys InputManager.keys codes + * @param {number} dt + */ + /** + * Projectile contact → knockback / hit reaction (physical results). + * @param {import('./SkillProjectileSystem.js').ProjectileHit} hit + */ + _onProjectileHit(hit) { + if (hit.endEvent === 'blink' && hit.point) { + const p = this.character.position; + p.x = hit.point.x; + p.z = hit.point.z; + this.vfx?.deploy?.('arcane_swirl', { origin: hit.point.clone(), intensity: 1 }); + this.onToast?.(`Blink · ${hit.endEvent}`); + return; + } + + // Production status package (damage · push · freeze · stun · slow · …) + const skill = this._lastSkill || { + id: 'projectile', + damage: 0, + element: hit.element, + effects: hit.freeze ? ['Freeze 2.5s'] : [], + statuses: hit.freeze + ? [{ id: 'freeze', durationSec: hit.freezeSec || 2.5, magnitude: 1 }] + : [], + force: hit.force, + knockbackMm: hit.knockbackMm, + knockupVy: hit.knockupVy + }; + + const isHostile = hit.target?.kind === 'hostile'; + const applyToPlayer = + !hit.target || + hit.target.kind === 'self' || + hit.target.id === 'player' || + hit.target.applyToPlayer; + + // P0 defensive early-out (also enforced in SkillStatusSystem.applyHit): + // invuln / C-parry weapon cylinder vs incoming projectile contact on player. + if (applyToPlayer && hit.point) { + if (this.isInvincible || this.invuln > 0) { + this.onToast?.('Invincible'); + return; + } + const attackR = + Number(hit.contactRadius ?? hit.radius ?? hit.hitRadius ?? 0.18) || 0.18; + if (this.tryParryBlock(hit.point, attackR)) { + return; + } + } + + this.statuses.applyHit({ + target: hit.target || { id: 'aim', point: hit.point, kind: 'aim' }, + skill: { + ...skill, + damage: skill.damage, + // Inject freeze from projectile flag when skill had no statuses + effects: skill.effects, + statuses: + skill.statuses?.length + ? skill.statuses + : hit.freeze + ? [{ id: 'freeze', durationSec: hit.freezeSec || 2.5, magnitude: 1 }] + : undefined + }, + hit: { + ...hit, + damage: skill.damage, + // Hostiles: push from skill physics; player only if self-hit test + knockbackMm: isHostile || applyToPlayer ? hit.knockbackMm : 0 + }, + character: this.character, + physics: this.physics, + drc: this, + applyToPlayer: applyToPlayer && !hit.freeze + }); + } + + /** + * Deploy skill by delivery pattern (over/under/around/projectile…). + * Catalog skills only — pattern inferred from skill row. + * @param {object} skill + * @param {{ origin: Vector3, forward: Vector3, aim?: Vector3 }} pose + */ + _deploySkillDelivery(skill, pose) { + const enriched = enrichSkillDelivery(skill); + const pattern = enriched.delivery; + const phys = enriched.deliveryPhysics || {}; + + // Prefer snow-brawl launch origin from pose (chest + hand) when provided + if (pose?.origin) _origin.copy(pose.origin); + else this.character.getCastOrigin(_origin); + // Barrel muzzle for pistol; melee tip otherwise + if (typeof this.character.getWeaponTip === 'function') { + const tipOff = + this.character.animPackId === 'pistol' + ? FLINTLOCK_FIRE.muzzleFallbackM + : settings.residual?.tipOffset ?? 0.55; + this.character.getWeaponTip(_tip, tipOff); + } else { + _tip.copy(_origin); + } + + // Soft-lock assist: auto-acquire frontal hostile for gun shots if none selected + if ( + isPistolBulletSkill(skill) && + PISTOL_SOFT_LOCK.acquireOnFire && + this.combatFocus && + !this.combatFocus.selectedTarget + ) { + const feet = this.character.position; + const fwd0 = + pose.forward?.clone?.() || + this.aim?.forward?.clone?.() || + new Vector3(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)); + this.combatFocus.acquireBest?.(feet, fwd0); + } + + // 3D hit (focus) > soft-lock target > ground marker + const aimPt = + (this.aim?.valid && this.aim.hitPoint?.clone?.()) || + pose.aim?.clone?.() || + (this.aim?.valid && this.aim.point?.clone?.()) || + this.character.position.clone().addScaledVector(pose.forward, skill.rangeM || 8); + + const targetPt = + this.combatFocus?.selectedTarget?.point?.clone?.() || aimPt.clone(); + + // Prefer soft-lock / aim launch dir; barrel for presentation only at spawn + let fwd3 = + (this.combatFocus?.selectedTarget?.point + ? targetPt.clone().sub(_tip) + : null) || + pose.forward?.clone?.() || + (this.aim?.forward3d?.lengthSq?.() > 1e-6 + ? this.aim.forward3d.clone() + : null) || + this.aim?.forward?.clone?.() || + new Vector3(0, 0, 1); + if (fwd3.lengthSq() > 1e-8) fwd3.normalize(); + else if (typeof this.character.getWeaponForward === 'function') { + this.character.getWeaponForward(fwd3); + } + + const resolved = resolveDeliveryPose(pattern, { + casterPos: this.character.position.clone(), + castOrigin: _origin.clone(), + weaponTip: _tip.clone(), + aimPoint: aimPt, + targetPoint: targetPt, + forward: fwd3, + skyHeight: 8, + groundY: 0.05 + }); + + // Hostiles as projectile contact targets (soft-lock) + const targets = []; + if (this.combatFocus?.selectedTarget?.point) { + targets.push({ + id: this.combatFocus.selectedTarget.id, + point: this.combatFocus.selectedTarget.point.clone(), + mesh: this.combatFocus.selectedTarget.mesh, + kind: this.combatFocus.selectedTarget.kind || 'hostile' + }); + } + // Aim point as soft target for lab (always) + targets.push({ + id: 'aim', + point: resolved.target.clone(), + kind: 'aim' + }); + + // Cast tell VFX + if (enriched.castEffectId) { + this.vfx?.deploy?.(enriched.castEffectId, { + origin: resolved.origin.clone(), + forward: resolved.forward.clone(), + intensity: 0.9 + }); + } + + // Element attack kinds (freeze nova / earth rocks / water bubbles / arrows) + const atk = inferElementAttackKind(enriched); + if (this.projectiles && atk.kind) { + if (atk.kind === 'freeze_nova') { + this.projectiles.spawnFreezeNova?.({ + origin: this.character.position.clone(), + radiusM: atk.aoeM, + freeze: true, + targets, + element: 'ice' + }); + enriched._deliveryLabel = 'Freeze Nova'; + return enriched; + } + if (atk.kind === 'earth_rocks') { + void this.projectiles.spawnEarthRocks?.({ + casterPos: this.character.position.clone(), + target: resolved.target, + forward: resolved.forward, + rockCount: atk.rockCount ?? 1, + aimMode: atk.aimMode || 'linear', + targets, + speed: phys.speed ?? 13 + }); + enriched._deliveryLabel = + atk.aimMode === 'aimed' ? 'Earth Rocks · aimed' : 'Earth Rocks · linear'; + return enriched; + } + if (atk.kind === 'water_bubbles') { + this.projectiles.spawnBubbleStream?.({ + origin: resolved.origin, + target: resolved.target, + forward: resolved.forward, + speed: phys.speed ?? 11, + targets + }); + enriched._deliveryLabel = 'Water Bubbles'; + return enriched; + } + if (atk.kind === 'arrow_path' || atk.kind === 'arrow_loft') { + const dist = this.character.position.distanceTo(resolved.target); + void this.projectiles.spawnArrow?.({ + origin: resolved.origin, + target: resolved.target, + system: atk.kind === 'arrow_loft' ? 'loft' : 'path', + endEvent: atk.endEvent, + distanceM: dist, + targets + }); + enriched._deliveryLabel = `Arrow · ${atk.endEvent || atk.kind}`; + return enriched; + } + } + + if (pattern === 'toggle_aura' || pattern === 'around_caster' || pattern === 'around_target' || pattern === 'at_location') { + const el = enriched.element || enriched.abilityElement; + // Ice around_caster → freeze nova when catalog implies freeze + if ( + (pattern === 'around_caster' || pattern === 'around_target') && + (el === 'ice' || el === 'frost') && + this.projectiles?.spawnFreezeNova + ) { + this.projectiles.spawnFreezeNova({ + origin: + pattern === 'around_target' + ? resolved.origin.clone() + : this.character.position.clone(), + radiusM: phys.aoe ?? 5, + targets, + element: 'ice' + }); + enriched._deliveryLabel = 'Freeze Nova'; + return enriched; + } + this.projectiles?.pulse?.({ + origin: resolved.origin, + aoe: phys.aoe ?? 1.5, + force: phys.force, + knockbackMm: phys.knockbackMm, + knockupVy: phys.knockupVy, + element: el, + targets, + intensity: 1.1, + freeze: el === 'ice' || el === 'frost' + }); + return enriched; + } + + if (pattern === 'weapon') { + this._fireMeleeResidual(skill, pose, { + rangeOverride: skill.rangeM, + hit: { kind: 'light', step: 0 } + }); + return enriched; + } + + // Traveling mesh projectile — staff normals use per-element orbs (gd_orbs split) + if (this.projectiles && phys.meshKey !== 'residual') { + const el = enriched.element || enriched.abilityElement || 'arcane'; + // Flintlock / handgun — muzzle origin · hit-frame delay · burst · reload + if (isPistolBulletSkill(enriched) && this.projectiles.spawnBullet) { + const count = pistolBulletCount(enriched); + const speed = enriched.projectileSpeed || PISTOL_BULLET.speed; + const meshUrl = enriched.projectileMeshUrl || PISTOL_BULLET.meshUrl; + // Charged Shot: slightly longer hit-frame (wind-up already played) + const chargeMul = Number(enriched._chargeMul) || 1; + const hitSec = + pistolHitFrameSec(enriched) * + (chargeMul > 1.01 ? (settings.drc?.weaponCharge?.hitFrameMul ?? 1.15) : 1); + const gapSec = FLINTLOCK_FIRE.burstGapSec; + const spreadRad = FLINTLOCK_FIRE.burstSpreadRad; + const fireOne = (spreadYaw = 0) => { + // Live barrel tip at fire frame (hand moved with gunplay) + if (typeof this.character.getWeaponTip === 'function') { + this.character.getWeaponTip(_tip, FLINTLOCK_FIRE.muzzleFallbackM); + } + const origin = _tip.clone(); + let f; + if (this.combatFocus?.selectedTarget?.point) { + f = this.combatFocus.selectedTarget.point.clone().sub(origin); + } else if (this.aim?.hitPoint) { + f = this.aim.hitPoint.clone().sub(origin); + } else if (typeof this.character.getWeaponForward === 'function') { + f = this.character.getWeaponForward(new Vector3()); + } else { + f = resolved.forward.clone(); + } + if (!f || f.lengthSq() < 1e-8) f = resolved.forward.clone(); + f.normalize(); + if (spreadYaw !== 0) { + const c = Math.cos(spreadYaw); + const s = Math.sin(spreadYaw); + const x = f.x * c - f.z * s; + const z = f.x * s + f.z * c; + f.x = x; + f.z = z; + f.normalize(); + } + const tgt = origin.clone().addScaledVector(f, 48); + void this.projectiles.spawnBullet({ + origin: origin.clone(), + target: tgt, + forward: f, + targets, + speed, + meshUrl + }); + }; + + const scheduleFire = () => { + // Consume chamber on first round leave barrel (single-load flintlock) + if (this.flintlock && isFlintlockContext(enriched, getEquippedWeapon?.(), this.character?.animPackId)) { + this.flintlock.consume(); + this.onSession?.({ flintlock: this.flintlock.state }); + } + if (count <= 1) { + fireOne(0); + enriched._deliveryLabel = 'Bullet · muzzle'; + } else { + for (let i = 0; i < count; i++) { + const yaw = (i - (count - 1) / 2) * spreadRad; + const delayMs = Math.round(i * gapSec * 1000); + if (delayMs <= 0) fireOne(yaw); + else { + setTimeout(() => { + try { + fireOne(yaw); + } catch { + /* dispose mid-cast */ + } + }, delayMs); + } + } + enriched._deliveryLabel = `Bullet · burst ×${count}`; + } + // Soft-lock status package (Suppressing Shot slow) + if (this.statuses && enriched.statuses?.length && this.combatFocus?.selectedTarget) { + this.statuses.applyHit({ + target: { + id: this.combatFocus.selectedTarget.id, + point: this.combatFocus.selectedTarget.point, + mesh: this.combatFocus.selectedTarget.mesh, + kind: this.combatFocus.selectedTarget.kind || 'hostile' + }, + skill: enriched, + hit: { damage: enriched.damage, force: phys.force }, + character: this.character, + physics: this.physics, + drc: this + }); + } + // Optional lab auto-reload (default OFF — production uses key 1 when empty) + if (FLINTLOCK_RELOAD.afterShot === true && this.character?.playPistolReload) { + const lastRound = (count - 1) * gapSec; + const reloadAt = Math.round( + (lastRound + FLINTLOCK_FIRE.reloadAfterShotSec) * 1000 + ); + setTimeout(() => { + try { + if (this.flintlock?.isEmpty()) { + this.useSkill(0, { skill: makeReloadSkillDef() }); + } + } catch { + /* */ + } + }, reloadAt); + } else if (this.flintlock?.isEmpty()) { + this.onToast('Empty · 1 Reload'); + } + }; + + const hitMs = Math.round(Math.max(0, hitSec) * 1000); + if (hitMs <= 0) scheduleFire(); + else { + setTimeout(() => { + try { + scheduleFire(); + } catch { + /* */ + } + }, hitMs); + } + return enriched; + } + // Nature stream: prefer rocks over orbs for earth school staffs + if ( + (el === 'nature' || el === 'earth') && + (enriched.pathMode === 'stream' || isStaffNormalAttack(enriched)) && + this.projectiles.spawnEarthRocks + ) { + void this.projectiles.spawnEarthRocks({ + casterPos: this.character.position.clone(), + target: resolved.target, + forward: resolved.forward, + rockCount: 1, + aimMode: 'linear', + targets, + speed: phys.speed ?? 14 + }); + return enriched; + } + // Ice stream soft bolts: bubbles + orb + if ((el === 'ice' || el === 'frost' || el === 'water') && this.projectiles.spawnBubbleStream) { + this.projectiles.spawnBubbleStream({ + origin: resolved.origin, + target: resolved.target, + forward: resolved.forward, + count: 4, + speed: phys.speed ?? 12, + targets + }); + } + const meshUrl = + enriched.projectileMeshUrl || + enriched.summonMeshUrl || + (isStaffNormalAttack(enriched) ? staffProjectileMeshUrl(el) : null); + const orbSize = + enriched.useOrbProjectile || isStaffNormalAttack(enriched) + ? STAFF_NORMAL_ATTACK.projectileDiameterM + : phys.size; + void this.projectiles.spawn({ + origin: resolved.origin, + target: resolved.target, + forward: resolved.forward, + element: el, + meshUrl, + speed: phys.speed ?? 16, + gravity: phys.gravity, + contactRadius: phys.contactRadius ?? 0.4, + life: phys.life, + force: phys.force, + knockbackMm: phys.knockbackMm, + knockupVy: phys.knockupVy, + aoe: phys.aoe, + size: orbSize, + targets, + explodeOnHit: phys.explodeOnHit !== false, + useOrbMaterials: true + }); + } + return enriched; + } + + update(dt, keys) { + this.elapsed += dt; + this.projectiles?.update?.(dt); + this.tipTrail?.update?.(dt, this.elapsed); + this.statuses?.update?.(this.elapsed); + // Soft burn crackle only while player has burn status (not on fire impacts) + try { + setPlayerBurningSfx(!!this.statuses?.hasStatus?.('player', 'burn'), { volume: 0.22 }); + } catch (_) {} + this.flintlock?.tick?.(this.elapsed); + this._tickWeaponCharge(dt, keys); + // Dual resource regen (settings.drc) + const staR = settings.drc?.staminaRegen ?? 18; + const manaR = settings.drc?.manaRegen ?? 12; + this.stamina = Math.min(this.maxStamina, this.stamina + dt * staR); + this.mana = Math.min(this.maxMana, this.mana + dt * manaR); + + // Cast channel tick (bar + complete) before loco so interrupt can cancel + this._tickCast(dt, keys); + + // Session gates (preferred) — land loco off while riding / equip / walk mode + const g = this.gates; + const parentName = this.character.root?.parent?.name || ''; + const riding = + g?.rideParented || + this.sessionState?.riding || + this.character._rideActive || + this.character._rideParented || + this.character.isRideParented || + parentName === 'RideSeat' || + parentName.startsWith('socket_'); + if (riding || (g && !g.landLoco)) { + this.character.setGait?.(0, false); + if (this.invuln > 0) this.invuln = Math.max(0, this.invuln - dt); + // Never write root.position while seat-parented (world coords would fling hero) + // Skills still via useSkill when gates.combatSkills — no land move + if (riding || !this.inCombat) return; + if (g && !g.landLoco) return; + } + if (!this.inCombat) { + this.character.setGait?.(0, false); + return; + } + + const ctrlHeld = keys.has('ControlLeft') || keys.has('ControlRight'); + // Sprint: toggle (default) or hold — settings.controls.sprintToggle + this._updateSprint(keys); + + // I-frame timer (dodge sets this; other systems can extend) + if (this.invuln > 0) this.invuln = Math.max(0, this.invuln - dt); + + // Shift+tap Ctrl = slide · Ctrl(+dir) = roll · AA/DD dodges + this._pollSlide(keys, ctrlHeld); + this._pollCtrlRoll(keys, ctrlHeld); + if (!ctrlHeld) this._pollDoubleTapDodge(keys); + this._ctrlWasDown = ctrlHeld; + + // Active dodge / roll / slide impulse overrides move + if (this._dodgeT > 0) { + this._dodgeT -= dt; + const vx = this._dodgeVel.x; + const vz = this._dodgeVel.z; + if (this.physics?.ready && this._usePhysics) { + const pose = this.physics.movePlayer(vx, vz, dt); + this.character.root.position.set(pose.x, pose.y, pose.z); + this._grounded = !!pose.grounded; + } else { + this.character.root.position.x += vx * dt; + this.character.root.position.z += vz * dt; + } + // Continuous model afterimage trail while MM dodge / invuln runs + const src = this.character.model || this.character.root; + const pos = this.character.root?.position; + this.vfx?.updateDodgeTrail?.( + dt, + true, + src, + pos, + this.character.facing + ); + if (this._dodgeT <= 0) { + this._dodgeVel.set(0, 0, 0); + // Keep fading residual ghosts after impulse ends + this.vfx?.updateDodgeTrail?.(0, false, null, null); + } + return; + } + + // Fade leftover afterimages when not dodging + this.vfx?.updateDodgeTrail?.(dt, false, null, null); + + // ── WASD locomotion ────────────────────────────────────────────── + // Focus ON (RMB toggle): camera-relative move + character rotates WITH camera + // Focus OFF: tank turn with A/D, W/S along body facing (camera free) + // Ctrl held: A/D reserved for roll · Shift hold = sprint + const focusOn = !!this.combatFocus?.focusEnabled; + let ix = 0; + let iz = 0; + if (keys.has('KeyW') || keys.has('ArrowUp') || this._autoTraverse) iz -= 1; + if ((keys.has('KeyS') || keys.has('ArrowDown')) && !this._autoTraverse) iz += 1; + + if (focusOn) { + // Strafe relative to camera (A/D don't turn body — camera yaw does) + if (!ctrlHeld) { + if (keys.has('KeyA') || keys.has('ArrowLeft')) ix += 1; + if (keys.has('KeyD') || keys.has('ArrowRight')) ix -= 1; + } + } else if (!ctrlHeld) { + // Free aim: A/D rotate character in place (tank turn) + const turnRate = settings.aim?.tankTurnSpeed ?? 2.6; // rad/s + let turn = 0; + if (keys.has('KeyA') || keys.has('ArrowLeft')) turn += 1; + if (keys.has('KeyD') || keys.has('ArrowRight')) turn -= 1; + if (turn !== 0) { + const yaw = this.character.facing + turn * turnRate * dt; + this._yaw = yaw; + this.character.setFacing(yaw); + } + } + + const len = Math.hypot(ix, iz); + if (len > 1e-4) { + ix /= len; + iz /= len; + } + + // Movement basis + if (focusOn) { + // Camera-relative + this.camera.getWorldDirection(_fwd); + _fwd.y = 0; + if (_fwd.lengthSq() < 1e-6) _fwd.set(0, 0, 1); + else _fwd.normalize(); + } else { + // Body-facing (W/S only after A/D turn) + const yaw = this.character.facing; + _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + } + + const fx = _fwd.x; + const fz = _fwd.z; + const rx = fz; + const rz = -fx; + + _move.set(0, 0, 0); + if (focusOn) { + _move.x = fx * -iz + rx * ix; + _move.z = fz * -iz + rz * ix; + } else { + // Only W/S along body + _move.x = fx * -iz; + _move.z = fz * -iz; + } + if (_move.lengthSq() > 1e-6) _move.normalize(); + + // Water surface / submerge (shark fin back-slot buffs) + const waterY = WORLD.waterY ?? settings.walk?.freerideWaterY ?? -0.04; + const feetY = this.character?.position?.y ?? this.character?.root?.position?.y ?? 0; + const headY = feetY + (this.character?.height || 1.8) * 0.88; + this._inWater = feetY < waterY + 0.35; + this._submerged = headY < waterY - 0.05; + const waterBuffs = this.getBackWaterBuffs(); + // Sync for AI / fishing / other systems + try { + if (this.character) { + this.character.sharkAggroImmune = !!waterBuffs.sharkAggroImmune; + this.character.userData = this.character.userData || {}; + this.character.userData.sharkAggroImmune = !!waterBuffs.sharkAggroImmune; + this.character.userData.breatheUnderwater = !!waterBuffs.breatheUnderwater; + this.character.userData.swimSpeedMul = waterBuffs.swimSpeedMul; + } + } catch { + /* optional */ + } + + let swimMul = 1; + if (this._inWater || this._submerged) { + swimMul = waterBuffs.swimSpeedMul || 1; + } + + const speed = + this.moveSpeed * + (this._sprinting ? this.sprintMul : 1) * + (settings.global?.animationSpeed || 1) * + swimMul; + const moving = _move.lengthSq() > 1e-6; + let vx = moving ? _move.x * speed : 0; + let vz = moving ? _move.z * speed : 0; + + // Underwater vertical assist (kinematic vy sample — no dedicated swim API yet) + if (this._submerged) { + if (keys.has('Space')) this._kinVy = Math.max(this._kinVy || 0, 2.8 * swimMul); + else if (keys.has('ControlLeft') || keys.has('ControlRight') || keys.has('KeyC')) { + this._kinVy = Math.min(this._kinVy || 0, -2.4 * swimMul); + } else if (waterBuffs.breatheUnderwater) { + // Fin: slight neutral buoyancy (hold depth) + this._kinVy = (this._kinVy || 0) * 0.9; + } + } + + // Breath / drown + this._tickUnderwaterBreath(dt, waterBuffs); + + // Face: focus → match camera; free → A/D already turned body + this._updateFacingToAim(dt, moving); + + // ── Jump / double-jump / S+Space backflip ───────────────────────── + this._handleJump(dt, keys, moving); + + // Frontflip slight forward push + if (this._frontflipBoostT > 0) { + this._frontflipBoostT -= dt; + const sp = settings.drc?.frontflipSpeed ?? 3.2; + vx = this._frontflipDir.x * sp; + vz = this._frontflipDir.z * sp; + } + + // Backflip: hard stop → reverse dash (horizontal-heavy) + if (this._backflipHardStopT > 0) { + this._backflipHardStopT -= dt; + vx = 0; + vz = 0; + } else if (this._backflipBoostT > 0) { + this._backflipBoostT -= dt; + const sp = settings.drc?.backflipSpeed ?? 6.8; + vx = this._backflipDir.x * sp; + vz = this._backflipDir.z * sp; + } + + // Hang gravity window (after backflip — air attacks) + if (this._hangT > 0) { + this._hangT -= dt; + if (this._hangT <= 0) { + this.physics?.setGravityScale?.(1); + this._kinGravityScale = 1; + } + } + + // Vertical velocity sample for fall state machine + let sampleVy = this._kinVy || 0; + if (this.physics?.ready && this._usePhysics) { + const pose = this.physics.movePlayer(vx, vz, dt); + this.character.root.position.set(pose.x, pose.y, pose.z); + // Prefer physics vertical speed when available + if (Number.isFinite(pose.vy)) sampleVy = pose.vy; + else if (Number.isFinite(pose.velocityY)) sampleVy = pose.velocityY; + else sampleVy = (pose.y - (this._lastPoseY ?? pose.y)) / Math.max(1e-4, dt); + this._lastPoseY = pose.y; + + const wasGrounded = this._grounded; + this._grounded = !!pose.grounded; + if (this._grounded) { + if (!wasGrounded && this._airborne) { + // Deterministic land: impact vy + horizontal speed + this._impactVy = this._lastVy < 0 ? this._lastVy : sampleVy; + const horiz = Math.hypot(vx, vz); + const wantRoll = + keys?.has?.('KeyW') || keys?.has?.('ShiftLeft') || keys?.has?.('ShiftRight'); + const land = this.character.playFallLand?.({ + impactVy: this._impactVy, + horizSpeed: horiz, + wantRoll + }); + if (!land?.ok) { + this.character.clearFlip?.(); + this.character.clearAirJumpHold?.(); + } + } else { + this.character.clearFlip?.(); + } + this._jumpsLeft = settings.drc?.maxJumps ?? 2; + this._airborne = false; + this._airTime = 0; + this._backflipBoostT = 0; + this._backflipHardStopT = 0; + this._frontflipBoostT = 0; + this._hangT = 0; + this._flipHoldYaw = null; + this.physics?.setGravityScale?.(1); + } else { + this._airborne = true; + this._airTime = (this._airTime || 0) + dt; + this._lastVy = sampleVy; + // Jump hold → fallLoop when descending (author fall FBX) + this.character.updateAirLocomotion?.({ + airborne: true, + vy: sampleVy, + airTime: this._airTime, + flipping: !!(this.character._flipActive || this._backflipBoostT > 0 || this._frontflipBoostT > 0) + }); + } + } else { + // Kinematic fallback (no Rapier) + if (moving || this._backflipBoostT > 0 || this._frontflipBoostT > 0) { + this.character.root.position.x += vx * dt; + this.character.root.position.z += vz * dt; + } + this._integrateKinematicJump(dt, keys); + if (!this._grounded) { + this._airTime = (this._airTime || 0) + dt; + this._lastVy = this._kinVy || 0; + this.character.updateAirLocomotion?.({ + airborne: true, + vy: this._kinVy || 0, + airTime: this._airTime, + flipping: !!(this.character._flipActive || this._backflipBoostT > 0) + }); + } else { + this._airTime = 0; + } + } + + // Gait: lock during jump/flip one-shots + // Focus: intelligent strafe — world move vs camera right/forward (not pure A/D only) + if (!this.character._gaitLocked && this._grounded) { + if (!moving) { + this.character.setGait?.(0, false); + } else { + let strafe = null; + if (focusOn && moving) { + // lateral = move · cameraRight ; fwd = move · cameraFwd + const lat = _move.x * rx + _move.z * rz; + const fwd = _move.x * fx + _move.z * fz; + const absLat = Math.abs(lat); + const absFwd = Math.abs(fwd); + // Prefer side gait when lateral dominates or strong side component + if (absLat > 0.28 && absLat >= absFwd * 0.55) { + // KeyA → ix +1 maps to camera-left; lat sign matches move vs right + strafe = lat > 0 ? 'right' : 'left'; + } + } + this.character.setGait?.(this._sprinting ? 2 : 1, this._sprinting, { strafe }); + } + } else if (!this._grounded && !this.character._gaitLocked) { + // Air: keep last gait weight low — jump clip owns pose when present + this.character.setGait?.(0, false); + } + } + + /** + * Facing: + * - Focus ON (RMB toggle): character rotates **with camera** (cam-forward) + * - Focus OFF: A/D tank-turn only (already applied in update); no cam rotate + * - Backflip / roll / dodge lock own facing while active + * @param {number} dt + * @param {boolean} moving + */ + _updateFacingToAim(dt, moving) { + // Backflip = setup move: keep facing look direction (do NOT snap body/cam 180°) + if (this.character?.isBackflip || (this._backflipBoostT > 0 && this._flipHoldYaw != null)) { + const hold = + this.character?._flipCameraHoldYaw ?? + this._flipHoldYaw ?? + this.character.facing; + this._yaw = hold; + this.character.setFacing(hold); + return; + } + if (this._dodgeT > 0) return; + const locked = this.character._gaitLocked; + const st = this.character.animState; + if (locked && (st === 'dodge' || st === 'roll' || st === 'slide' || st === 'flip')) return; + + const focusOn = !!this.combatFocus?.focusEnabled; + // Free aim: body yaw only from A/D tank turn in update() — do not follow camera/aim + if (!focusOn) { + this._yaw = this.character.facing; + return; + } + + // Focus: lag-follow camera yaw — do NOT snap body to every mouse twitch + if (settings.aim?.focusTurnOnlyWhenMoving && !moving) { + this._yaw = this.character.facing; + return; + } + this.camera.getWorldDirection(_fwd); + _fwd.y = 0; + if (_fwd.lengthSq() < 1e-6) return; + _fwd.normalize(); + const targetYaw = Math.atan2(_fwd.x, _fwd.z); + + let cur = this.character.facing; + let diff = targetYaw - cur; + while (diff > Math.PI) diff -= Math.PI * 2; + while (diff < -Math.PI) diff += Math.PI * 2; + + // Deadzone: ignore small look offsets so camera pans without spinning body + const dead = + MathUtils.degToRad(settings.aim?.focusTurnDeadzoneDeg ?? 16); + if (Math.abs(diff) < dead) { + this._yaw = cur; + return; + } + // Pull only excess past deadzone (so we don't overshoot into twitchy center) + const sign = Math.sign(diff); + const excess = Math.abs(diff) - dead; + const turn = settings.aim?.focusTurnSpeed ?? 6.5; + const maxStep = turn * dt; + const step = Math.min(excess, maxStep); + cur += sign * step; + this._yaw = cur; + this.character.setFacing(cur); + } + + /** + * Sprint: toggle on Shift press (default) or hold while Shift down. + * @param {Set} keys + */ + _updateSprint(keys) { + const shiftDown = keys.has('ShiftLeft') || keys.has('ShiftRight'); + const toggle = settings.controls?.sprintToggle !== false; + if (toggle) { + if (shiftDown && !this._wasShiftDown) { + this._sprintLatched = !this._sprintLatched; + this.onToast?.(this._sprintLatched ? 'Sprint ON' : 'Sprint OFF'); + } + this._sprinting = this._sprintLatched || this._autoTraverse; + } else { + this._sprinting = shiftDown || this._autoTraverse; + this._sprintLatched = false; + } + this._wasShiftDown = shiftDown; + } + + /** Toggle ` auto run / freeride sail-row. */ + toggleAutoTraverse() { + this._autoTraverse = !this._autoTraverse; + if (this._autoTraverse) this._sprintLatched = true; + this.onToast?.(this._autoTraverse ? 'Auto RUN/SAIL ON (`)' : 'Auto OFF (`)'); + return this._autoTraverse; + } + + isAutoTraverse() { + return !!this._autoTraverse; + } + + /** + * Edge-detect Space: + * - Ground → jump anim blend + * - Air 2nd → frontflip (standard) + * - Air 2nd + S → hard stop + horizontal backflip + hang (air attacks) + * @param {number} dt + * @param {Set} keys + * @param {boolean} moving + */ + _handleJump(dt, keys, moving) { + const jumpDown = keys.has('Space'); + const pressed = jumpDown && !this._wasJumpDown; + this._wasJumpDown = jumpDown; + if (!pressed) return; + if (this.character._rideActive) return; + + const cfg = settings.drc || {}; + const maxJ = cfg.maxJumps ?? 2; + const holdS = keys.has('KeyS') || keys.has('ArrowDown'); + + // Ground / coyote: refresh jumps when grounded + if (this._grounded) this._jumpsLeft = maxJ; + + if (this._jumpsLeft <= 0) return; + + const isSecond = this._jumpsLeft < maxJ || !this._grounded; + const wantBackflip = isSecond && holdS; + + if (wantBackflip) { + // Hard stop → reverse horizontal boost · keep look yaw (setup for air attack) + this.camera.getWorldDirection(_fwd); + _fwd.y = 0; + if (_fwd.lengthSq() < 1e-6) { + const yaw0 = this.character.facing; + _fwd.set(Math.sin(yaw0), 0, Math.cos(yaw0)); + } else _fwd.normalize(); + // Hold look: do not face reverse + this._flipHoldYaw = Math.atan2(_fwd.x, _fwd.z); + this._backflipDir.set(-_fwd.x, 0, -_fwd.z); + this._backflipHardStopT = cfg.backflipHardStop ?? 0.1; + this._backflipBoostT = cfg.backflipDuration ?? 0.52; + this._frontflipBoostT = 0; + const jv = cfg.backflipVertical ?? 2.4; + if (this.physics?.ready) { + this.physics.zeroVerticalVelocity?.(); + this.physics.jump(jv); + this.physics.setGravityScale?.(cfg.backflipHangGravity ?? 0.32); + } else { + this._kinVy = jv; + this._kinGravityScale = cfg.backflipHangGravity ?? 0.32; + } + this._hangT = cfg.backflipHangDuration ?? 1.15; + this.character.playBackflip?.(cfg.backflipDuration ?? 0.52, { + holdYaw: this._flipHoldYaw + }); + this.character.setFacing(this._flipHoldYaw); + this._yaw = this._flipHoldYaw; + this._jumpsLeft = 0; + this._grounded = false; + this._airborne = true; + this.onToast?.('Backflip · camera hold · air setup'); + return; + } + + if (isSecond) { + // Standard double jump = quick frontflip + const yaw = this.character.facing; + this._frontflipDir.set(Math.sin(yaw), 0, Math.cos(yaw)); + this._frontflipBoostT = cfg.frontflipDuration ?? 0.48; + this._backflipBoostT = 0; + this._backflipHardStopT = 0; + const jv = cfg.doubleJumpVelocity ?? 5.0; + if (this.physics?.ready) this.physics.jump(jv); + else this._kinVy = jv; + this.character.playFrontflip?.(cfg.frontflipDuration ?? 0.48); + this._jumpsLeft = 0; + this._grounded = false; + this._airborne = true; + this.onToast?.('Frontflip'); + return; + } + + // First jump — blend jump clip, hold pose while airborne + const jv = cfg.jumpVelocity ?? 5.4; + if (this.physics?.ready) this.physics.jump(jv); + else this._kinVy = jv; + this.character.playJump?.(0.1, { holdAir: true }); + this._jumpsLeft -= 1; + this._grounded = false; + this._airborne = true; + } + + /** Simple ballistic Y when Rapier unavailable. */ + _integrateKinematicJump(dt, keys) { + if (this._kinVy === undefined) this._kinVy = 0; + if (this._kinGravityScale === undefined) this._kinGravityScale = 1; + const g = -9.81 * this._kinGravityScale; + if (this._grounded && this._kinVy <= 0) { + this.character.root.position.y = 0; + this._kinVy = 0; + this._jumpsLeft = settings.drc?.maxJumps ?? 2; + this._kinGravityScale = 1; + return; + } + this._kinVy += g * dt; + this.character.root.position.y += this._kinVy * dt; + if (this.character.root.position.y <= 0) { + const impactVy = this._kinVy; + this.character.root.position.y = 0; + this._kinVy = 0; + this._grounded = true; + this._jumpsLeft = settings.drc?.maxJumps ?? 2; + this._backflipBoostT = 0; + this._backflipHardStopT = 0; + this._frontflipBoostT = 0; + this._hangT = 0; + this._kinGravityScale = 1; + this.character.clearFlip?.(); + const wantRoll = + keys?.has?.('KeyW') || keys?.has?.('ShiftLeft') || keys?.has?.('ShiftRight'); + const land = this.character.playFallLand?.({ + impactVy, + horizSpeed: 2.5, + wantRoll + }); + if (!land?.ok) this.character.clearAirJumpHold?.(); + } else { + this._grounded = false; + // fall loop driven by outer updateAirLocomotion + } + } + + get isCasting() { + return !!this._cast; + } + + getCastBarState() { + if (!this._cast) return null; + const c = this._cast; + const prog = MathUtils.clamp( + (this.elapsed - c.startedAt) / Math.max(0.001, c.duration), + 0, + 1, + ); + return { + active: true, + label: c.label, + progress01: prog, + duration: c.duration, + remaining: Math.max(0, c.endsAt - this.elapsed), + element: c.element || 'arcane', + }; + } + + _beginCast(opts) { + const duration = Math.max(0, Number(opts.duration) || 0); + if (duration < 0.06) { + opts.onComplete?.(); + return true; + } + if (this._cast) this._interruptCast('replaced', false); + this._cast = { + label: opts.label || 'Casting', + element: opts.element || 'arcane', + startedAt: this.elapsed, + duration, + endsAt: this.elapsed + duration, + onComplete: opts.onComplete, + interruptible: opts.interruptible !== false, + aim: opts.aim || null, + showCharge: opts.showCharge !== false, + skill: opts.skill || null + }; + this.character.setCasting?.(true, opts.aim || null); + this.character.playWeaponCombat?.('cast') || + this.character.playCastFlourish?.() || + this.character.requestOneShot?.('cast'); + try { + playForWeaponSkillCast(opts.skill || { element: opts.element, label: opts.label }, { + duration + }); + } catch (_) {} + // Small elemental charge shell (kamehameha bake) at tip during cast + if (this._cast.showCharge && this.projectiles?.spawnCharge) { + this._chargeOrigin = this._chargeOrigin || new Vector3(); + this._getChargeOrigin(this._chargeOrigin); + void this.projectiles.spawnCharge({ + origin: this._chargeOrigin, + element: this._cast.element, + meshUrl: opts.chargeMeshUrl || STAFF_CHARGE_MESH, + size: 0.32 + }); + } + this.onCastBar?.(this.getCastBarState()); + return true; + } + + /** Staff tip / cast hand for charge shell. */ + _getChargeOrigin(out) { + if (typeof this.character.getWeaponTip === 'function') { + this.character.getWeaponTip(out, settings.residual?.tipOffset ?? 0.55); + return out; + } + this.character.getCastOrigin?.(out) || out.copy(this.character.position); + out.y += 1.1; + return out; + } + + _tickCast(dt, keys) { + if (!this._cast) return; + if (this._cast.interruptible && settings.drc?.castInterruptOnMove !== false) { + const moving = + keys?.has?.('KeyW') || + keys?.has?.('KeyA') || + keys?.has?.('KeyS') || + keys?.has?.('KeyD') || + keys?.has?.('ArrowUp') || + keys?.has?.('ArrowDown') || + keys?.has?.('ArrowLeft') || + keys?.has?.('ArrowRight'); + if (moving && this.elapsed - this._cast.startedAt > 0.12) { + this._interruptCast('moved'); + return; + } + } + if (this.elapsed >= this._cast.endsAt) { + const done = this._cast.onComplete; + this._clearCast(); + try { + done?.(); + } catch (e) { + console.warn('[DrcCombat] cast complete', e); + } + this.onCastBar?.(null); + return; + } + this.character.setCasting?.(true, this._cast.aim || null); + // Pulse charge shell at tip while channeling + if (this._cast.showCharge && this.projectiles?.updateCharge) { + this._chargeOrigin = this._chargeOrigin || new Vector3(); + this._getChargeOrigin(this._chargeOrigin); + const prog = MathUtils.clamp( + (this.elapsed - this._cast.startedAt) / Math.max(0.01, this._cast.duration), + 0, + 1 + ); + this.projectiles.updateCharge(this._chargeOrigin, dt, prog); + } + this.onCastBar?.(this.getCastBarState()); + } + + _interruptCast(reason = 'cancel', toast = true) { + if (!this._cast) return; + this._clearCast(); + this.character.setCasting?.(false); + if (toast) this.onToast(reason === 'moved' ? 'Cast interrupted' : 'Cast cancelled'); + this.onCastBar?.({ active: false, interrupted: true }); + } + + _clearCast() { + this.projectiles?.clearCharge?.(); + this._cast = null; + this.character.setCasting?.(false); + } + + /** + * F key — equipped weapon skill (primary / Showcase bind `f`). + * Full cast-time + prefab path. Not residual, not class ability. + * @returns {boolean} + */ + useWeaponSkillF() { + if (!this.inCombat) { + this.onToast('Enter combat (Q) for weapon skills'); + return false; + } + if (this._cast) { + this.onToast('Already casting'); + return false; + } + const skill = skillForFKey(); + if (!skill) { + this.onToast('No weapon skill — equip a weapon (I → Weapon)'); + return false; + } + // Chargeable F (Charged Shot) — begin hold; App should call release on keyup + return this.beginWeaponCharge('f', { skill }); + } + + /** + * Weapon rest / GCD gate (best rest after charged shot). + * @param {boolean} [toast] + */ + _weaponReady(toast = true) { + if (this.elapsed < this._weaponRestUntil) { + if (toast) this.onToast('Weapon rest…'); + return false; + } + if (this.elapsed < this._weaponGcdUntil) { + if (toast) this.onToast('…'); + return false; + } + return true; + } + + /** + * Begin hold-to-charge (keydown). Tap→release fires quick; hold builds Charged Shot. + * @param {number|string} slot + * @param {{ skill?: object }} [opts] + */ + beginWeaponCharge(slot, opts = {}) { + if (!this.inCombat) { + this.onToast('Enter combat (Q) to use weapon skills'); + return false; + } + if (this.weaponCharge?.active) return true; + if (!this._weaponReady(true)) return false; + if (this._cast) { + this.onToast('Already casting'); + return false; + } + + let skill = opts.skill || (slot === 'f' || slot === -1 ? skillForFKey() : skillBySlot(slot)); + if (!skill) return false; + + const pack = this.character?.animPackId || ''; + const weapon = typeof getEquippedWeapon === 'function' ? getEquippedWeapon() : null; + const flintCtx = isFlintlockContext(skill, weapon, pack); + + if (flintCtx && this.flintlock) { + this.flintlock.tick(this.elapsed); + const slotN = slot === 'f' || slot === -1 ? 0 : Number(slot); + if ( + (slotN === 0 || slot === 'f' || slot === -1) && + this.flintlock.isEmpty() && + !this.flintlock.isReloading() + ) { + return this.useSkill(slot, { skill: makeReloadSkillDef(), skipCharge: true }); + } + if (skillNeedsLoad(skill) && (this.flintlock.isEmpty() || this.flintlock.isReloading())) { + this.onToast(this.flintlock.isReloading() ? 'Reloading…' : 'Empty · press 1 to Reload'); + return false; + } + } + + if (!isChargeableWeaponSkill(skill, { animPack: pack, weaponId: weapon?.id })) { + return this.useSkill(slot, { skill, skipCharge: true }); + } + + const animRole = + pack === 'pistol' || flintCtx + ? 'skill2' + : skill.animRole === 'skill2' + ? 'skill2' + : 'cast'; + this.weaponCharge.begin( + slot === 'f' || slot === -1 ? 0 : Number(slot), + skill, + this.elapsed, + animRole + ); + this.character.beginWeaponChargeAnim?.(animRole) || + this.character.requestOneShot?.(animRole) || + this.character.requestOneShot?.('cast'); + this.onCastBar?.(chargeBarState({ holdSec: 0, skill, active: true })); + this.onToast(`${skill.label} · hold to charge`); + return true; + } + + /** + * Keyup — tap fire or charged release. + * @param {{ cancel?: boolean }} [opts] + */ + releaseWeaponCharge(opts = {}) { + if (!this.weaponCharge?.active) return false; + const payload = this.weaponCharge.end(opts.cancel ? 'cancel' : 'release'); + this.onCastBar?.(null); + this.character.endWeaponChargeAnim?.(payload.reason === 'release' && payload.isCharged); + + if (payload.reason === 'cancel' || opts.cancel) { + this._weaponRestUntil = this.elapsed + payload.restSec; + this.onToast('Charge cancel'); + return false; + } + + const skill = payload.skill; + if (!skill) return false; + + return this.useSkill(payload.slot, { + skill, + skipCharge: true, + chargeHoldSec: payload.isCharged ? payload.holdSec : 0, + chargeDamageMul: payload.isCharged ? payload.damageMul : 1, + chargeIntensity: payload.isCharged ? payload.intensity : 1, + chargeLevel: payload.level, + forceAnimRole: payload.isCharged + ? 'skill2' + : skill.animRole || (skill.style === 'ranged' ? 'attack' : null) + }); + } + + /** @param {number} dt @param {Set} [keys] */ + _tickWeaponCharge(dt, keys) { + if (!this.weaponCharge?.active) return; + const cfg = weaponChargeConfig(); + const u = this.weaponCharge.update(dt, this.elapsed); + if (u.tick || u.levelChanged) { + this.onCastBar?.( + chargeBarState({ + holdSec: u.holdSec, + skill: this.weaponCharge.skill, + active: true + }) + ); + this.character.tickWeaponChargeAnim?.(u.progress01, u.holdSec); + if (u.levelChanged && u.level.id !== 'tap') { + this.onToast(`${this.weaponCharge.skill?.label || 'Charge'} · ${u.level.label}`); + } + } + if (u.holdSec >= cfg.maxHoldSec) { + this.releaseWeaponCharge(); + } + } + + /** + * Fire weapon skill slot 0–3, or F via opts. + * @param {number|string} slot 0–3 or 'f' + * @param {{ + * skill?: object, + * bound?: object|null, + * skipCharge?: boolean, + * chargeHoldSec?: number, + * chargeDamageMul?: number, + * chargeIntensity?: number, + * chargeLevel?: object, + * forceAnimRole?: string + * }} [opts] + * @returns {boolean} + */ + useSkill(slot, opts = {}) { + if (!this.inCombat) { + this.onToast('Enter combat (Q) to use weapon skills'); + return false; + } + const g = this.gates; + if (g && !g.combatSkills) { + this.onToast('Skills locked'); + return false; + } + // Windsurf freeride: allow ranged/staff skills (tslda boat combat feel) + if (this.character._rideActive && !this._allowRideSkill()) return false; + + if (this.weaponCharge?.active && !opts.skipCharge) { + return true; + } + + if (!opts.skipCharge && !this._weaponReady(true)) return false; + + let skill = opts.skill || (slot === 'f' || slot === -1 ? skillForFKey() : skillBySlot(slot)); + if (!skill) return false; + + // Route chargeable → hold UX (unless release/tap already set skipCharge) + if (!opts.skipCharge) { + const pack0 = this.character?.animPackId || ''; + const weapon0 = typeof getEquippedWeapon === 'function' ? getEquippedWeapon() : null; + if (isChargeableWeaponSkill(skill, { animPack: pack0, weaponId: weapon0?.id })) { + return this.beginWeaponCharge(slot, { skill }); + } + } + + // ── Flintlock chamber: empty → digit 1 is Reload (production Warlords) ── + const pack = this.character?.animPackId || ''; + const weapon = typeof getEquippedWeapon === 'function' ? getEquippedWeapon() : null; + const flintCtx = isFlintlockContext(skill, weapon, pack); + if (flintCtx && this.flintlock) { + this.flintlock.tick(this.elapsed); + const slotN = slot === 'f' || slot === -1 ? 0 : Number(slot); + // Key 1 (slot 0) or F primary when empty → Reload + if ( + (slotN === 0 || slot === 'f' || slot === -1) && + this.flintlock.isEmpty() && + !this.flintlock.isReloading() && + !skill.isReload + ) { + skill = makeReloadSkillDef(); + this.onToast('Empty · Reload'); + } else if (skillNeedsLoad(skill) && this.flintlock.isEmpty()) { + this.onToast('Empty · press 1 to Reload'); + return false; + } else if (skillNeedsLoad(skill) && this.flintlock.isReloading()) { + this.onToast('Reloading…'); + return false; + } else if ( + (skill.isReload || /reload/i.test(`${skill.id} ${skill.label}`)) && + this.flintlock.isLoaded() + ) { + this.onToast('Already loaded'); + return false; + } + } + + // Catalog binding (Showcase) — true master-weaponSkills id when set + const bound = + opts.bound !== undefined + ? opts.bound + : getSkillBinding(slot === -1 ? 'f' : slot); + const boundName = bound?.name || skill.label; + + // Merge WEAPON_SKILLS STAFF row only — no invented skills + const staffId = bound?.skillId || skill.catalogSkillId || skill.id; + const staffB = + bindFromCatalogSkill({ + id: staffId, + name: bound?.name || skill.label, + description: skill.description || '', + damageType: bound?.damageType || skill.damageType, + effects: skill.effects, + cooldown: skill.cooldown, + castTime: skill.castDuration, + range: skill.rangeM, + damage: skill.damage, + slotType: skill.slotType + }) || staffBindFor(staffId); + if (staffB) { + skill = { + ...skill, + element: staffB.element, + abilityElement: staffB.element, + pathMode: staffB.pathMode, + presentation: staffB.presentation, + castEffectId: staffB.castEffectId, + travelEffectId: staffB.travelEffectId, + impactEffectId: staffB.impactEffectId, + abilityClass: staffB.abilityClass, + animRole: 'cast', + rangeM: staffB.rangeM || skill.rangeM, + castDuration: staffB.castDuration || skill.castDuration, + cooldown: staffB.cooldown || skill.cooldown, + catalogSkillId: staffId, + label: boundName || staffB.name || skill.label, + description: staffB.description, + effects: staffB.effects, + useOrbProjectile: staffB.useOrbProjectile ?? skill.useOrbProjectile, + projectileMeshUrl: staffB.projectileMeshUrl || skill.projectileMeshUrl, + chargeMeshUrl: staffB.chargeMeshUrl || skill.chargeMeshUrl + }; + } + + const readyAt = this._cdUntil.get(skill.id) || 0; + if (this.elapsed < readyAt) { + this.onToast(`${skill.label} CD`); + return false; + } + // Digit / charged: hold intensity scales cost (Charged Shot) + const holdSec = Number(opts.chargeHoldSec) || 0; + const costs = skillCastCosts(skill, holdSec, 0); + if (!this._spendResources(costs.mana, costs.stamina, skill.label)) return false; + this._cdUntil.set(skill.id, this.elapsed + skill.cooldown); + if (this._cast) { + this.onToast('Already casting'); + return false; + } + // Charge mul on damage / force for this cast + const dmgMul = Number(opts.chargeDamageMul) || 1; + if (dmgMul > 1.01) { + skill = { + ...skill, + damage: Math.round((Number(skill.damage) || 0) * dmgMul), + force: (Number(skill.force) || 6) * dmgMul, + _chargeMul: dmgMul, + _chargeLevel: opts.chargeLevel?.id || null, + _chargeHoldSec: holdSec + }; + } + if (opts.forceAnimRole) { + skill = { ...skill, animRole: opts.forceAnimRole }; + } + // Production hit package for projectiles / melee residual + this._lastSkill = skill; + // Combat timer: short GCD + best rest after any weapon fire + const cfgCh = weaponChargeConfig(); + this._weaponGcdUntil = this.elapsed + cfgCh.gcdSec; + this._weaponRestUntil = + this.elapsed + + weaponRestAfterFire(holdSec, opts.chargeIntensity || costs.intensity || 1, cfgCh); + + // Flintlock reload path (baked pistol/reload + procedural powder pose) + if ( + skill.isReload || + skill.skillKind === 'reload' || + /reload/i.test(`${skill.id} ${skill.label}`) + ) { + if (flintCtx && this.flintlock) { + const dur = FLINTLOCK_RELOAD.durationSec; + if (!this.flintlock.beginReload(dur, this.elapsed)) { + this.onToast(this.flintlock.isLoaded() ? 'Already loaded' : 'Reload busy'); + return false; + } + // Baked clip first + this.character.playPistolReload?.({ power: false }) || + this.character.requestOneShot?.('reload') || + this.character.requestOneShot?.('draw'); + this._cdUntil.set(skill.id, this.elapsed + Math.max(skill.cooldown || 0, 0.5)); + // Complete chamber when reload anim ends + setTimeout(() => { + try { + this.flintlock?.completeReload(); + this.onToast('Loaded · flintlock ready'); + this.onSession?.({ flintlock: this.flintlock?.state }); + } catch { + /* */ + } + }, Math.round(dur * 1000)); + this.onToast('Reload · powder + ball'); + this.onSession?.({ flintlock: this.flintlock.state }); + return true; + } + } + + const yaw = this.character.facing; + // Focus: snow-brawl 3D launch vector; free: horizontal aim forward + const use3d = + settings.aim?.use3dLaunch !== false && + this.aim?.forward3d && + this.aim.forward3d.lengthSq() > 1e-6 && + this.aim?.valid; + if (use3d) _fwd.copy(this.aim.forward3d).normalize(); + else if (this.aim?.valid) _fwd.copy(this.aim.forward); + else _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + this.character.getCastOrigin(_origin); + // Prefer MouseAim.computeLaunch origin when available (hand offset + chest) + let spawnOrigin = _origin.clone(); + let aimTarget = this.aim?.hitPoint?.clone?.() || null; + if (this.aim?.computeLaunch && this.character?.position) { + try { + const launch = this.aim.computeLaunch(this.character.position, { + hand: this._throwHand === 'left' ? 'left' : 'right' + }); + if (launch?.origin) spawnOrigin.copy(launch.origin); + if (launch?.direction) _fwd.copy(launch.direction); + if (launch?.target) aimTarget = launch.target.clone(); + this._throwHand = this._throwHand === 'left' ? 'right' : 'left'; + } catch { + /* keep cast origin */ + } + } + const rangeM = skill.rangeM || 8; + const pose = { + origin: spawnOrigin.clone(), + forward: _fwd.clone(), + aim: + aimTarget || + _end.copy(spawnOrigin).addScaledVector(_fwd, rangeM * 0.65) + }; + const aimPt = { x: pose.aim.x, y: pose.aim.y, z: pose.aim.z }; + const castDur = + skill.style === 'melee' + ? 0 + : Math.max(0, Number(skill.castDuration ?? skill.castTime ?? 0.55)); + + const releaseSpell = () => { + // Catalog VFX when bound; else DRC skill beauty + if (bound) { + const vfxId = vfxIdForSkill({ + id: bound.skillId, + name: bound.name, + description: '', + damageType: bound.damageType, + labStyle: bound.labPack === 'magic' ? 'spell' : skill.style + }); + this.vfx?.deploy?.(vfxId, { ...pose, intensity: 1.1 }); + } else { + this.vfx?.deploySkill?.(skill.id, pose, 'cast'); + } + + // Catalog buffs: Focus (next spell) · Take Cover / Nature Ward (self DR) + if (skill.isFocus || skill.isWard || skill.skillKind === 'buff') { + const dur = skill.focusDurationSec || 3; + if (skill.castEffectId) { + this.vfx?.deploy?.(skill.castEffectId, { ...pose, intensity: 0.85 }); + } + if (skill.isFocus) { + this._focusUntil = this.elapsed + dur; + this._focusMul = skill.focusDamageMul || 1.35; + this.onToast(`Focus · next spell +${Math.round((this._focusMul - 1) * 100)}% (${dur}s)`); + } else if ( + skill.isWard || + /ward|shield|cover|guard|brace|lumber/i.test(`${skill.id} ${skill.label}`) + ) { + // Take Cover / Nature Ward — self damage reduction for catalog duration + const reduce = + skill.wardDamageReduce != null + ? Number(skill.wardDamageReduce) + : (() => { + const blob = (skill.effects || []).join(' ').toLowerCase(); + const pct = /(\d+)\s*%/.exec(blob); + return pct ? Math.min(0.9, Number(pct[1]) / 100) : 0.2; + })(); + this._wardUntil = this.elapsed + dur; + this._wardReduce = reduce; + this.statuses?.applyHit?.({ + target: { id: 'player', kind: 'player' }, + skill: { + ...skill, + statuses: skill.statuses?.length + ? skill.statuses + : [{ id: 'ward', durationSec: dur, magnitude: reduce }] + }, + applyToPlayer: true, + character: this.character, + physics: this.physics, + drc: this + }); + this.vfx?.deploy?.(skill.impactEffectId || 'earth_surge', { + ...pose, + intensity: 0.9 + }); + this.onToast( + `${skill.label} · −${Math.round(reduce * 100)}% dmg taken · ${dur}s` + ); + } else { + this.onToast(`${skill.label} · buff ${dur}s`); + } + return; + } + + // ── Elemental × linear plan (learned LinearAbilityCasting + staff skills) ── + const focusOnPre = this.elapsed < this._focusUntil; + const focusMulPre = focusOnPre ? this._focusMul || 1.35 : 1; + const castPlan = planElementalLinearCast(skill, { + focusCombat: !!this.combatFocus?.focusEnabled || this.inCombat, + pathDrawn: false, + intensity: focusMulPre + }); + skill._castPlan = castPlan; + + // Linear LINE/ZONE skillshot (castToward — no Alt+Shift arm required) + if (castPlan.useLinear && this.linearSkills) { + try { + const feet = this.character.position.clone(); + const aimPt3 = + pose.aim?.clone?.() || + feet.clone().addScaledVector(pose.forward, skill.rangeM || 12); + const okLin = fireLinearFromPlan(this.linearSkills, castPlan, { + origin: pose.origin, + feet, + aim: aimPt3 + }); + if (okLin) skill._deliveryLabel = `Linear · ${castPlan.linearId}`; + } catch (e) { + console.warn('[DrcCombat] linear cast', e); + } + } + + // Delivery pattern (over/under/around/projectile) + mesh force projectiles + try { + const deliv = this._deploySkillDelivery(skill, pose); + if (deliv?.deliveryLabel) { + skill._deliveryLabel = deliv.deliveryLabel; + } + } catch (e) { + console.warn('[DrcCombat] delivery', e); + } + + // VFX: spell → elemental ability + creative presentation (volley/meteor/vines/…) + if (skill.style === 'spell' && (skill.element || skill.abilityElement)) { + // Product element (fire|storm|ice|nature|holy|arcane) or legacy — AbilityManager maps pool + const el = skill.element || skill.abilityElement; + const pathMode = skill.pathMode || 'stream'; + // Path Ability beauty when plan says so (staffCast learning + presentation) + if (castPlan.usePathAbility !== false) { + const curve = this._curveForPathMode(pathMode, skill.rangeM); + this.abilities.select(el); + this.abilities.cast(curve, el); + } + + const focusOn = this.elapsed < this._focusUntil; + const focusMul = focusOn ? this._focusMul || 1.35 : 1; + const intensity = focusOn ? 1.0 * focusMul : 1.0; + if (focusOn) { + // Consume focus on first damaging spell + this._focusUntil = 0; + } + // Push intensity into linear global knobs (live sample mid-cast) + this.linearSkills?.applyIntensity?.(intensity); + + const isMeteor = + /meteor|inferno/i.test(skill.id + skill.label + (skill.catalogSkillId || '')) || + skill.presentation === 'meteor'; + const isVolley = + /volley|spark|practice/i.test(skill.id + skill.label) || + skill.presentation === 'volley' || + ((el === 'fire' || el === 'arcane') && !/meteor/i.test(skill.id + skill.label)); + const isLightning = + el === 'storm' || + /lightning|chain.?lightning|tempest|storm/i.test(skill.id + skill.label) || + skill.presentation === 'lightning'; + const isShield = + /shield|ward|guard/i.test(skill.id + skill.label) || skill.presentation === 'shield'; + // Catalog presentation only (staff school style — no invented skill systems) + const pathFromBind = skill.pathMode || pathMode; + const presStyle = skill.presentation || skill.prefab?.presentation || null; + + this.vfx?.deployPresentation?.(el, { ...pose, intensity }, { + pathKind: pathFromBind, + presentation: presStyle, + meteor: isMeteor || presStyle === 'meteor', + volley: (isVolley && !isLightning) || presStyle === 'volley', + lightning: (isLightning && !isShield) || presStyle === 'lightning', + chain: isLightning, + shield: isShield || presStyle === 'shield' || pathFromBind === 'wall' + }); + + const dmg = skill.damage ? ` · ${Math.round(skill.damage * focusMul)} dmg` : ''; + const cat = skill.catalogSkillId ? ` → ${skill.catalogSkillId}` : ''; + const focusTag = focusOn ? ' · FOCUSED' : ''; + const styleTag = presStyle ? ` · ${presStyle}` : ''; + const linTag = castPlan.useLinear ? ` · lin:${castPlan.linearId}` : ''; + const layerTag = + castPlan.layers?.length > 1 ? ` · [${castPlan.layers.slice(0, 3).join('+')}]` : ''; + this.onToast( + bound + ? `${boundName} · ${bound.skillId}${linTag}` + : `${skill.label}${skill.pathMode ? ` · ${skill.pathMode}` : ''}${styleTag}${dmg}${focusTag}${linTag}${layerTag}${cat}` + ); + return; + } + + if (skill.style === 'melee') { + // Guard / ward: no residual slash + if (!(skill.isWard || skill.animRole === 'block' || skill.skillKind === 'buff')) { + const aoe = skill.residualAoe; + this._fireMeleeResidual(skill, pose, { + rangeOverride: skill.rangeM, + hit: { + kind: skill.animRole === 'finisher' ? 'finisher' : 'light', + step: /^attack([123])$/.exec(skill.animRole || '')?.[1] + ? Number(RegExp.$1) - 1 + : 0 + } + }); + // Wider contact for Wide Sweep etc. + if (aoe > 0 && settings.residual) { + /* residualFromSettings already used; rangeOverride covers reach */ + } + } + this.onToast(bound ? `${boundName} · ${bound.skillId}` : skill.label); + return; + } + + // Ranged (flintlock · bow · xbow) — delivery already fired bullets/arrows + if (skill.style === 'ranged') { + const n = pistolBulletCount(skill); + const dmg = skill.damage ? ` · ${skill.damage} dmg` : ''; + const multi = n > 1 ? ` · ×${n}` : ''; + const del = skill._deliveryLabel ? ` · ${skill._deliveryLabel}` : ''; + this.onToast( + bound + ? `${boundName} · ${bound.skillId}${multi}` + : `${skill.label}${dmg}${multi}${del}` + ); + return; + } + + // Bound catalog skill on a spell-less bar slot — still fire residual / path + if (bound) { + this._fireMeleeResidual(skill, pose); + this.onToast(`${boundName} · ${bound.skillId}`); + return; + } + }; + + // Instant melee / ranged / near-zero castDuration + if (skill.style === 'melee' || skill.style === 'ranged' || castDur < 0.08) { + const animRole = + skill.animRole || + (bound + ? animRoleForSkill({ + labStyle: + bound.labPack === 'magic' + ? 'spell' + : bound.labPack === 'longbow' + ? 'ranged' + : skill.style, + animation: null, + id: bound.skillId, + name: bound.name, + slotType: 'ability' + }) + : 'attack'); + // T0 / catalog roles: attack1–3 light, block guard, attack ranged, cast buff + if (animRole === 'block' || animRole === 'parry' || skill.isWard) { + this.character.playParry?.() || + this.character.requestOneShot?.('block') || + this.character.requestOneShot?.('parry'); + try { + // Wards / magic kits → lightning wizard parry; else metal + const magic = + skill.isWard || + skill.style === 'spell' || + this._isMagicParryContext() || + /ward|barrier|shield|arcane|magic|holy/.test( + String(skill.id || '') + String(skill.label || '') + ); + playParrySfx({ magic }); + } catch (_) {} + } else if (animRole === 'dodgeB' || /evade/i.test(skill.id + skill.label)) { + this.character.playDodge?.('back') || this.character.requestOneShot?.('dodgeB'); + } else if (/^attack[123]$/.test(animRole)) { + this.character.requestOneShot?.(animRole) || + this.character.playMeleeComboLight?.() || + this.character.playWeaponAttack?.(); + } else if (animRole === 'finisher' || animRole === 'finisherAir') { + this.character.playMeleeFinisher?.({ + airborne: animRole === 'finisherAir' || !!this._airborne + }); + } else if (skill.style === 'ranged' || animRole === 'attack') { + this.character.playWeaponCombat?.('attack') || + this.character.requestOneShot?.('attack') || + this.character.playWeaponAttack?.(); + } else if (skill.style === 'melee') { + this.character.playMeleeAttack?.({ + airborne: !!this._airborne || !this._grounded, + largeMmTowardTarget: this._isLargeMmTowardTarget?.() + }) || + this.character.playWeaponCombat?.('attack') || + this.character.playWeaponAttack?.() || + this.character.requestOneShot?.('attack'); + } else { + this.character.playWeaponCombat?.('cast') || + this.character.requestOneShot?.(animRole || skill.animRole) || + this.character.playCastFlourish?.(); + } + releaseSpell(); + return true; + } + + const el = skill.element || skill.abilityElement || 'arcane'; + const staffNormal = isStaffNormalAttack(skill) || skill.style === 'spell'; + this._beginCast({ + label: skill.label, + duration: castDur, + element: el, + interruptible: true, + aim: aimPt, + onComplete: releaseSpell, + showCharge: staffNormal, + chargeMeshUrl: skill.chargeMeshUrl || STAFF_CHARGE_MESH, + skill + }); + this.onToast( + staffNormal + ? `${skill.label} · normal · charge → orb` + : `${skill.label} · cast ${castDur.toFixed(1)}s` + ); + return true; + } + + /** Skills while on windsurf: staff / bow packs only (settings.walk.skillsWhileRide). */ + _allowRideSkill() { + if (settings.walk?.skillsWhileRide === false) return false; + const pack = this.character.animPackId || ''; + return pack === 'longbow' || pack === 'magic' || pack.includes('bow') || pack.includes('magic'); + } + + /** + * @deprecated Use useWeaponSkillF — F is weapon skill, not residual-only strike. + * Kept for callers; routes to weapon skill path. + */ + useMeleeStrike() { + return this.useWeaponSkillF(); + } + + /** + * Large MM toward focus/aim: sprinting into aim or recent forward mobility impulse. + * Threshold from settings.meleeCombo.finisherMm (100 MM = 1 m). + */ + _isLargeMmTowardTarget() { + const thrMm = settings.meleeCombo?.finisherMm ?? 280; + // Active dodge/lunge already large + if (this._dodgeT > 0 && this._dodgeVel.lengthSq() > 1e-6) { + const speed = this._dodgeVel.length(); + const estMm = mToMm(speed * Math.max(this._dodgeDur, 0.2)); + if (estMm >= thrMm * 0.55) return true; + } + // Sprint while moving roughly toward aim / focus + if (!this._sprinting) return false; + if (this.aim?.valid) { + _fwd.copy(this.aim.forward); + } else { + this.camera.getWorldDirection(_fwd); + } + _fwd.y = 0; + if (_fwd.lengthSq() < 1e-6) return false; + _fwd.normalize(); + // Body forward vs aim + const yaw = this.character.facing; + const bx = Math.sin(yaw); + const bz = Math.cos(yaw); + const align = bx * _fwd.x + bz * _fwd.z; + if (align < 0.55) return false; + // Sprint speed as continuous MM (sprint ≈ 3.6*1.65 m/s → ~594 MM/s) + const sprintMps = this.moveSpeed * this.sprintMul; + const continuousMm = mToMm(sprintMps * 0.55); + return continuousMm >= thrMm * 0.5 && (this._moveZ > 0.35 || align > 0.85); + } + + /** + * Melee residual at attack apex + weapon-tip trail during the swing. + * Trail samples tip each frame; at hitFrameDelay (apex) fires: + * getsuga_slash · fire-style blur path · physics residual past blade. + * Combo: each attack1/2/3 / finisher calls this again → apex per hit. + * + * @param {import('./drcSkills.js').DrcWeaponSkill} skill + * @param {{ origin: Vector3, forward: Vector3 }} pose + * @param {{ rangeOverride?: number, hit?: { kind?: string, step?: number } }} [opts] + */ + _fireMeleeResidual(skill, pose, opts = {}) { + const prim = residualFromSettings(); + if (settings.residual?.enabled === false) return; + + const range = opts.rangeOverride ?? prim.range ?? skill.rangeM ?? 3.2; + const hitFrameDelay = prim.hitFrameDelay ?? settings.residual?.hitFrameDelay ?? 0.18; + const step = opts.hit?.step; + // Combo lights: slightly shorter trail; finishers longer + fire blur + const isFin = + opts.hit?.kind === 'finisher' || + opts.hit?.kind === 'finisherAir' || + skill.animRole === 'finisher'; + const trailDur = + (settings.residual?.trailDuration ?? 0.34) * + (isFin ? 1.25 : step === 2 ? 1.1 : 1); + const fireBlur = + settings.residual?.fireTrail !== false && + (isFin || settings.residual?.fireTrail === true); + + let fwd = pose.forward?.clone?.() || new Vector3(0, 0, 1); + if (typeof this.character.getWeaponForward === 'function') { + this.character.getWeaponForward(_fwd); + if (_fwd.lengthSq() > 1e-8) fwd = _fwd.clone().normalize(); + } + + // Prefer tip-trail system (ribbon + apex projectile). Fallback to legacy timeout. + if (this.tipTrail?.beginSwing) { + this.tipTrail.beginSwing({ + duration: trailDur, + hitFrameDelay, + forward: fwd, + skill, + hit: opts.hit || { + kind: isFin ? 'finisher' : 'light', + step: Number.isFinite(step) ? step : 0 + }, + rangeM: range, + fireBlur, + color: fireBlur + ? settings.residual?.trailColor || '#ff6a22' + : settings.residual?.color || '#7dd3fc', + beyondBladeM: settings.residual?.beyondBladeM ?? 0.38, + width: + (settings.residual?.trailWidth ?? 0.14) * + (isFin ? 1.35 : 1 + (Number(step) || 0) * 0.08) + }); + return; + } + + // Legacy fallback (no scene / tipTrail) + const delayMs = Math.max(0, hitFrameDelay * 1000); + const intensity = + (prim.intensity ?? 1) * + (settings.effect?.intensity ?? 1) * + (isFin ? 1.25 : 1); + const fire = () => { + const tipOff = prim.tipOffset ?? settings.residual?.tipOffset ?? 0.55; + if (typeof this.character.getWeaponTip === 'function') { + this.character.getWeaponTip(_tip, tipOff); + } else { + this.character.getCastOrigin(_tip); + _tip.addScaledVector(fwd, tipOff); + } + const beyond = settings.residual?.beyondBladeM ?? 0.38; + _tip.addScaledVector(fwd, beyond); + const pathRange = MathUtils.clamp(range, 1, 10); + _end.copy(_tip).addScaledVector(fwd, pathRange); + _end.y = Math.max(0.12, _tip.y * 0.4); + _mid.lerpVectors(_tip, _end, 0.45); + _mid.y = Math.max(_tip.y, _mid.y) + pathRange * 0.04; + const curve = new CatmullRomCurve3( + [_tip.clone(), _mid.clone(), _end.clone()], + false, + 'catmullrom', + 0.5 + ); + this.vfx?.deploy?.('getsuga_slash', { + origin: _tip.clone(), + forward: fwd.clone(), + aim: _end.clone(), + fromTip: true, + intensity, + aoe: prim.aoe, + size: prim.size, + speed: prim.speed, + color: prim.color + }); + try { + playForImpact(fireBlur ? 'fire' : skill?.element, { + skill, + kind: fireBlur ? 'fire' : 'residual' + }); + } catch (_) {} + if (settings.residual?.enabled !== false) { + const el = fireBlur ? 'fire' : this.abilities.selected || 'storm'; + this.abilities.cast(curve, el); + } + }; + if (delayMs > 4) setTimeout(fire, delayMs); + else fire(); + } + + /** Alt+sandbox hotkey from vfxgrudge.puter.site */ + previewSandboxEffect(effectId) { + if (!this.vfx || !effectId) return false; + const yaw = this.character.facing; + _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + this.vfx.deploy(effectId, { + origin: this.character.position.clone(), + forward: _fwd.clone() + }); + return true; + } + + /** Build CatmullRom from hand → aim point for Ability.spawn */ + _aimCurve(rangeM) { + this.character.getCastOrigin(_origin); + // Prefer 3D focus launch, else horizontal mouse aim + if ( + settings.aim?.use3dLaunch !== false && + this.aim?.forward3d && + this.aim.forward3d.lengthSq() > 1e-6 + ) { + _fwd.copy(this.aim.forward3d).normalize(); + } else if (this.aim?.valid) { + _fwd.copy(this.aim.forward); + } else { + const yaw = this.character.facing; + _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + } + if (this.aim?.hitPoint && this.aim.valid) { + // Path endpoint uses true 3D hit for elevated / soft-lock aims + _end.copy(this.aim.hitPoint); + _mid.lerpVectors(_origin, _end, 0.45); + _mid.y += Math.min(2.2, _origin.distanceTo(_end) * 0.08); + return new CatmullRomCurve3([_origin.clone(), _mid.clone(), _end.clone()]); + } + _end.copy(_origin).addScaledVector(_fwd, rangeM); + _end.y = Math.max(0.15, _origin.y * 0.35); + _mid.lerpVectors(_origin, _end, 0.5); + _mid.y = Math.max(_origin.y, _mid.y) + rangeM * 0.06; + + const pts = [_origin.clone(), _mid.clone(), _end.clone()]; + return new CatmullRomCurve3(pts, false, 'catmullrom', 0.5); + } + + /** + * Curve shaped by kit pathMode (stream · aoe · spikes · wall). + * @param {'stream'|'aoe'|'spikes'|'wall'} pathMode + * @param {number} rangeM + */ + _curveForPathMode(pathMode, rangeM) { + const mode = pathMode || 'stream'; + if (mode === 'aoe') { + // Short hop into aim point — FireAbility / impact place + this.character.getCastOrigin(_origin); + if (this.aim?.valid) _fwd.copy(this.aim.forward); + else _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)); + _end.copy(_origin).addScaledVector(_fwd, Math.min(rangeM, 4.5)); + _end.y = Math.max(0.12, _origin.y * 0.25); + _mid.copy(_end).add(new Vector3(0, 0.9, 0)); + const start = _end.clone().add(new Vector3(0.05, 1.1, 0.05)); + return new CatmullRomCurve3([start, _mid.clone(), _end.clone()], false, 'catmullrom', 0.5); + } + if (mode === 'wall') { + // Wider lateral stroke for barrier feel + this.character.getCastOrigin(_origin); + if (this.aim?.valid) _fwd.copy(this.aim.forward); + else _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)); + const right = new Vector3(-_fwd.z, 0, _fwd.x); + const len = Math.max(rangeM * 0.85, 8); + const a = _origin.clone().addScaledVector(right, -len * 0.35).addScaledVector(_fwd, 1.2); + const b = _origin.clone().addScaledVector(_fwd, len * 0.55); + const c = _origin.clone().addScaledVector(right, len * 0.35).addScaledVector(_fwd, 1.2); + a.y = b.y = c.y = Math.max(0.15, _origin.y * 0.3); + return new CatmullRomCurve3([a, b, c], false, 'catmullrom', 0.5); + } + if (mode === 'spikes') { + // Medium ground-hugging path + const curve = this._aimCurve(Math.min(rangeM, 10)); + const pts = curve.getPoints(4).map((p, i) => { + const q = p.clone(); + q.y = 0.12 + i * 0.08; + return q; + }); + return new CatmullRomCurve3(pts, false, 'catmullrom', 0.5); + } + // stream — full range arc + return this._aimCurve(rangeM); + } + + /** + * Switch 10-spell kit page for 1–4 bar (0=1–4, 1=5–8, 2=9–10). + * @param {number} page + */ + setSpellKitPage(page) { + setSkillKitPage(page); + this.skills = getActiveSkills(); + this.onToast(`Spell kit page ${page + 1}/3 · ${this.skills.map((s) => s.label).join(' · ')}`); + return this.skills; + } + + /** Cooldown fraction 0..1 remaining for HUD (skill id or utility action id). */ + cooldown01(skillId) { + const readyAt = this._cdUntil.get(skillId) || 0; + if (this.elapsed >= readyAt) return 0; + const skill = + this.skills.find((s) => s.id === skillId) || + (skillId === 'drc_melee_strike' ? getMeleeStrikeSkill() : null); + const cdMax = skill?.cooldown ?? this._cdMax.get(skillId) ?? 1; + if (!skill && !this._cdUntil.has(skillId)) return 0; + return MathUtils.clamp((readyAt - this.elapsed) / Math.max(0.01, cdMax), 0, 1); + } + + /** + * Danger Room / tight-bar quick action (parry, dodge, heavy, bag, skills…). + * Lab implementations: real skill fire where wired; mobility stubs with CD toast. + * @param {string} actionId QuickActionId + * @returns {boolean} + */ + performQuickAction(actionId) { + if (!this.inCombat && actionId !== 'mode') { + this.onToast('Enter combat (Q)'); + return false; + } + // Mobility utilities blocked on board; combat skills ok if ride skill allowed + if (this.character._rideActive) { + if (actionId === 'dodge' || actionId === 'parry' || actionId === 'block') return false; + if (!this._allowRideSkill() && actionId !== 'mode') return false; + } + + switch (actionId) { + case 'primary': + case 'interact': + case 'fskill': + // Weapon skill F (cast bar + prefab) — pickup/harvest handled in App before this + return this.useWeaponSkillF(); + case 'sig1': + return this.useSkill(0); + case 'sig2': + return this.useSkill(1); + case 'sig3': + return this.useSkill(2); + case 'sig4': + return this.useSkill(3); + case 'dodge': + // X = back dodge (Danger Room) + return this.dodge('back'); + case 'parry': + return this.parry(); + case 'block': + // E = block / guard (fleet SSOT). C = parry. + return this._utilityAction('block', 0.4, settings.drc?.parryStamina ?? 4, () => { + this.character.playParry?.() || this.character.requestOneShot?.('block'); + try { + playParrySfx({ magic: this._isMagicParryContext() }); + } catch (_) {} + this.onToast('Block (E)'); + }); + case 'heavy': + // Heavy = same weapon skill path for now (class ability later) + return this.useWeaponSkillF(); + case 'kick': + return this._utilityAction('kick', 0.9, 6, () => { + this.character.playWeaponAttack?.(); + this.onToast('Kick (V)'); + }); + case 'heal': + return this._utilityAction('heal', 4.0, 0, () => { + this.stamina = Math.min(this.maxStamina, this.stamina + 35); + try { + playHealSfx(); + } catch (_) {} + _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)); + this.vfx?.deploy?.('moon_beam', { + origin: this.character.position.clone(), + forward: _fwd.clone(), + intensity: 0.8 + }); + this.onToast('Heal tonic (J)'); + }); + case 'bomb': + return this._utilityAction('bomb', 5.0, 10, () => { + _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)); + const origin = this.character.position.clone().addScaledVector(_fwd, 2.5); + // Lab: brief self-burn so soft burn loop is audible (status-driven, not impact SFX) + try { + this.statuses?.applyHit?.({ + target: { id: 'player', kind: 'player' }, + skill: { + id: 'bomb', + element: 'fire', + damage: 8, + statuses: [{ id: 'burn', durationSec: 4.0, magnitude: 3 }] + }, + hit: { element: 'fire' }, + character: this.character, + applyToPlayer: true + }); + } catch (_) {} + this.vfx?.deploy?.('inferno', { + origin, + forward: _fwd.clone(), + intensity: 1.1 + }); + this.onToast('Bomb (H) · burning'); + }); + case 'mode': + this.toggleSession(); + return true; + default: + return false; + } + } + + /** + * @param {string} id + * @param {number} cooldown + * @param {number} staminaCost + * @param {() => void} fn + */ + _utilityAction(id, cooldown, staminaCost, fn) { + if (!this._cdMax) this._cdMax = new Map(); + this._cdMax.set(id, cooldown); + const readyAt = this._cdUntil.get(id) || 0; + if (this.elapsed < readyAt) { + this.onToast(`${id} CD`); + return false; + } + if (this.stamina < staminaCost) { + this.onToast('Low stamina'); + return false; + } + this.stamina -= staminaCost; + this._cdUntil.set(id, this.elapsed + cooldown); + fn(); + return true; + } + + /** Map quick-action id → CD fraction for tight bar. */ + quickCd01(actionId) { + if (actionId === 'fskill' || actionId === 'interact' || actionId === 'primary') { + const fs = skillForFKey(); + return fs ? this.cooldown01(fs.id) : 0; + } + if (actionId?.startsWith('sig')) { + const slot = Number(actionId.slice(3)) - 1; + const skill = this.skills.find((s) => s.slot === slot); + return skill ? this.cooldown01(skill.id) : 0; + } + if (actionId === 'dodge') return this.cooldown01('dodge'); + return this.cooldown01(actionId); + } + + /** + * Edge-detect double-tap A/D/W for left/right/forward dodge (not while Ctrl). + * @param {Set} keys + */ + _pollDoubleTapDodge(keys) { + if (this._dodgeT > 0) return; + const windowMs = settings.drc?.doubleTapMs ?? 280; + const now = performance.now(); + const pairs = [ + ['KeyA', 'left'], + ['KeyD', 'right'], + ['KeyW', 'forward'] + ]; + for (const [code, dir] of pairs) { + const down = keys.has(code); + const was = this._keyWasDown.has(code); + if (down && !was) { + const last = this._lastTap[code] || 0; + if (now - last < windowMs && last > 0) { + this.dodge(dir); + this._lastTap[code] = 0; + } else { + this._lastTap[code] = now; + } + } + if (down) this._keyWasDown.add(code); + else this._keyWasDown.delete(code); + } + } + + /** + * Ctrl alone = roll (Ghost Rider). + * Ctrl+A/D/W/S = roll that direction; Ctrl tap with no dir = forward roll. + * Not active while Shift held (that channel is slide). + * @param {Set} keys + * @param {boolean} ctrlHeld + */ + _pollCtrlRoll(keys, ctrlHeld) { + if (this._dodgeT > 0) return; + // Shift+Ctrl = slide — never roll while sprinting + if (this._sprinting) { + this._rollKeyWas = { KeyA: false, KeyD: false, KeyW: false, KeyS: false, Control: false }; + return; + } + if (!ctrlHeld) { + this._rollKeyWas = { KeyA: false, KeyD: false, KeyW: false, KeyS: false, Control: false }; + return; + } + const pairs = [ + ['KeyA', 'left'], + ['KeyD', 'right'], + ['KeyW', 'forward'], + ['KeyS', 'back'] + ]; + let dirPressed = false; + for (const [code, dir] of pairs) { + const down = keys.has(code); + const was = !!this._rollKeyWas[code]; + if (down && !was) { + this.roll(dir); + dirPressed = true; + } + this._rollKeyWas[code] = down; + } + // Ctrl edge with no direction key → forward roll + const ctrlEdge = ctrlHeld && !this._ctrlWasDown; + if (ctrlEdge && !dirPressed) { + const anyDir = + keys.has('KeyA') || + keys.has('KeyD') || + keys.has('KeyW') || + keys.has('KeyS') || + keys.has('ArrowLeft') || + keys.has('ArrowRight') || + keys.has('ArrowUp') || + keys.has('ArrowDown'); + if (!anyDir) this.roll('forward'); + } + } + + /** + * Shift (sprint hold) + tap Ctrl → running slide. + * @param {Set} keys + * @param {boolean} ctrlHeld + */ + _pollSlide(keys, ctrlHeld) { + if (this._dodgeT > 0) return; + if (!this._sprinting) return; + const ctrlEdge = ctrlHeld && !this._ctrlWasDown; + if (ctrlEdge) this.slide(); + } + + /** + * World impulse basis from aim/facing (matches move). + * After A/D invert: left = +right basis of pre-invert, so use same + * inverted mapping as locomotion (A moves +ix → +right of pre-fix basis). + * For rolls we want semantic left/right of look: left = −right_vec. + */ + _mobilityBasis() { + if (this.aim?.valid && settings.aim?.enabled !== false) { + _fwd.copy(this.aim.forward); + } else { + const yaw = this.character.facing; + _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + } + const rx = _fwd.z; + const rz = -_fwd.x; + return { fx: _fwd.x, fz: _fwd.z, rx, rz }; + } + + /** + * @param {'left'|'right'|'forward'|'back'} dir + * @param {number} dist + * @param {number} dur + */ + _startMobilityImpulse(dir, dist, dur) { + const { fx, fz, rx, rz } = this._mobilityBasis(); + // Match inverted A/D feel: "left" follows corrected A (screen-left of look) + // Corrected A moves with +ix on inverted keys = +right of old basis = what + // players call left after fix. Use semantic: left = −right of aim. + let wx = 0; + let wz = 0; + if (dir === 'forward') { + wx = fx; + wz = fz; + } else if (dir === 'back') { + wx = -fx; + wz = -fz; + } else if (dir === 'left') { + // Match walk A after invert: A uses ix=+1 → +right of aim basis + wx = rx; + wz = rz; + } else { + // right matches walk D after invert + wx = -rx; + wz = -rz; + } + const speed = dist / Math.max(0.12, dur); + this._dodgeVel.set(wx * speed, 0, wz * speed); + this._dodgeT = dur; + this._dodgeDur = dur; + } + + /** + * Directional dodge (AA/DD/WW double-tap · X back). + * Uses motion-math (MM) distance profiles: AA/DD lateral = 3× baseline (7.2 m). + * Spawns wind-style mesh afterimages + full invuln for the dodge window. + * @param {'left'|'right'|'forward'|'back'} dir + */ + dodge(dir) { + const d = dir === 'left' || dir === 'right' || dir === 'forward' || dir === 'back' ? dir : 'back'; + const stam = settings.drc?.dodgeStamina ?? 10; + const cd = 0.75; + return this._utilityAction(`dodge_${d}`, cd, stam, () => { + this._cdUntil.set('dodge', this.elapsed + cd); + this._cdMax.set('dodge', cd); + + const dist = dodgeDistanceM(d, settings.drc || {}); + const dur = settings.drc?.dodgeDuration ?? 0.42; + this._startMobilityImpulse(d, dist, dur); + + // I-frames for entire MM dodge + afterimage window + const inv = settings.drc?.dodgeInvuln; + this.invuln = Math.max(this.invuln, inv > 0 ? inv : dur); + + // Path afterimage (trailing model copies) along the escape vector + const origin = this.character.root?.position?.clone?.() || this.character.position?.clone?.(); + if (origin && this._dodgeVel.lengthSq() > 1e-6) { + _fwd.set(this._dodgeVel.x, 0, this._dodgeVel.z).normalize(); + const src = this.character.model || this.character.root; + this.vfx?.afterimage?.(src, origin, _fwd, dist, { + count: settings.drc?.afterimage?.count ?? 6, + life: Math.max(settings.drc?.afterimage?.life ?? 0.45, dur) + }); + } + + const played = this.character.playDodge?.(d); + const lat = d === 'left' || d === 'right'; + const labels = { left: 'AA left', right: 'DD right', forward: 'WW forward', back: 'X back' }; + this.onToast( + `${labels[d] || d} dodge · ${dist.toFixed(1)}m MM` + + `${lat ? ' ×3' : ''}` + + `${played ? '' : ' (no clip)'}` + + ' · invuln' + ); + }); + } + + /** + * Ghost Rider roll (Ctrl+A left · Ctrl+D right). + * Impulse duration matches clip so feet travel with the tumble (no foot-slide deform). + * @param {'left'|'right'|'forward'|'back'} dir + */ + roll(dir) { + const d = dir === 'left' || dir === 'right' || dir === 'forward' || dir === 'back' ? dir : 'back'; + const stam = settings.drc?.rollStamina ?? 12; + const cd = 0.85; + return this._utilityAction(`roll_${d}`, cd, stam, () => { + this._cdUntil.set('roll', this.elapsed + cd); + this._cdMax.set('roll', cd); + const dist = settings.drc?.rollDistance ?? 3.0; + // Sync body travel to clip length (Ghost Rider ~0.77s) + const clipDur = + this.character.getRollClipDuration?.(d) ?? settings.drc?.rollDuration ?? 0.55; + const dur = Math.max(0.42, Math.min(1.1, clipDur * 0.98)); + const played = this.character.playRoll?.(d); + const useDur = + played && typeof played === 'object' && played.duration > 0 ? played.duration : dur; + this._startMobilityImpulse(d, dist, useDur); + // Side rolls keep aim facing (clip is lateral). F/B face the impulse. + if ((d === 'forward' || d === 'back') && this._dodgeVel.lengthSq() > 1e-6) { + this._yaw = Math.atan2(this._dodgeVel.x, this._dodgeVel.z); + this.character.setFacing?.(this._yaw); + } + const labels = { + left: 'Ctrl+A left roll', + right: 'Ctrl+D right roll', + forward: 'Ctrl+W roll', + back: 'Ctrl+S roll' + }; + const ok = played === true || played?.ok; + this.onToast(`${labels[d] || d}${ok ? ' · Ghost Rider' : ' (no clip)'}`); + }); + } + + /** + * Sprint slide (Shift+Ctrl). Uses prod running-slide bake. + */ + slide() { + const stam = settings.drc?.slideStamina ?? 14; + const cd = 1.1; + return this._utilityAction('slide', cd, stam, () => { + const dist = settings.drc?.slideDistance ?? 4.2; + const dur = settings.drc?.slideDuration ?? 0.72; + // Slide along current facing / sprint forward + if (this.aim?.valid && settings.aim?.enabled !== false) { + _fwd.copy(this.aim.forward); + } else { + const yaw = this.character.facing; + _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + } + // Prefer last move dir if still holding WASD + if (_move.lengthSq() > 1e-6) { + _fwd.set(_move.x, 0, _move.z).normalize(); + } + const speed = dist / Math.max(0.12, dur); + this._dodgeVel.set(_fwd.x * speed, 0, _fwd.z * speed); + this._dodgeT = dur; + this._dodgeDur = dur; + this._yaw = Math.atan2(_fwd.x, _fwd.z); + this.character.setFacing?.(this._yaw); + const played = this.character.playSlide?.(); + this.onToast(played ? 'Slide · running-slide' : 'Slide (no clip)'); + }); + } + + /** + * Staff / wand / magic pack → magical parry SFX; otherwise metal parry.wav. + * @returns {boolean} + */ + _isMagicParryContext() { + const pack = String(this.character?.animPackId || '').toLowerCase(); + if (pack.includes('magic') || pack.includes('staff') || pack.includes('wand')) return true; + try { + const w = typeof getEquippedWeapon === 'function' ? getEquippedWeapon() : null; + const blob = `${w?.id || ''} ${w?.name || ''} ${w?.category || ''} ${w?.type || ''}`.toLowerCase(); + if (/staff|wand|tome|rod|scepter|magic|arcane|spell/.test(blob)) return true; + } catch (_) {} + const el = String(this.abilities?.selected || settings?.element || '').toLowerCase(); + if (['fire', 'ice', 'storm', 'holy', 'arcane', 'nature', 'water', 'earth', 'wind'].includes(el)) { + // Path-cast elements imply magical frame for ward/parry when on magic kit only + if (pack.includes('magic')) return true; + } + return false; + } + + /** + * True if a world-space attack point intersects the equipped weapon cylinder + * (mesh-fit + 0.02 m pad). Used for defensive parry success vs incoming strike. + * @param {import('three').Vector3} attackPoint + * @param {number} [attackRadius=0.15] + */ + weaponVolumeBlocks(attackPoint, attackRadius = 0.15) { + const vol = this.character?.weaponVolume; + if (!vol || !attackPoint) return false; + return pointHitsWeaponVolume(vol, attackPoint, attackRadius); + } + + /** Parry with block/parry clip. SFX fires on **attempt** (key press), not only success. */ + parry() { + try { + playParrySfx({ magic: this._isMagicParryContext() }); + } catch (_) {} + const stam = settings.drc?.parryStamina ?? 8; + return this._utilityAction('parry', 0.65, stam, () => { + // Ensure mesh cylinder is fresh for defensive tests this window + try { + this.character.rebuildWeaponVolume?.({ debug: false }); + } catch { + /* optional */ + } + /** Parry active until — weaponVolumeBlocks(point) valid in this window */ + this._parryUntil = this.elapsed + (settings.drc?.parryWindowSec ?? 0.35); + this.character.playParry?.() || this.character.requestOneShot?.('block'); + _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)); + this.vfx?.deploy?.('arcane_swirl', { + origin: this.character.position.clone(), + forward: _fwd.clone(), + intensity: 0.75 + }); + this.onToast(this._isMagicParryContext() ? 'Magic parry (C)' : 'Parry (C)'); + }); + } + + /** + * Incoming attack vs weapon cylinder during parry window. + * Called from SkillStatusSystem.applyHit + _onProjectileHit (player contact). + * Success consumes the window (one clean block per C press). + * @param {import('three').Vector3} attackPoint + * @param {number} [attackRadius] + */ + tryParryBlock(attackPoint, attackRadius = 0.18) { + if (!this._parryUntil || this.elapsed > this._parryUntil) return false; + if (!this.weaponVolumeBlocks(attackPoint, attackRadius)) return false; + this._parryUntil = 0; + this.vfx?.deploy?.('arcane_swirl', { + origin: attackPoint.clone?.() || this.character.position.clone(), + forward: _fwd.set(0, 1, 0), + intensity: 1.1 + }); + this.onToast('Parried!'); + return true; + } + + /** + * Spend mana + stamina. Returns false and toasts if short. + * @param {number} mana + * @param {number} stamina + * @param {string} [label] + */ + _spendResources(mana, stamina, label = 'Cast') { + const needM = Math.max(0, mana | 0); + const needS = Math.max(0, stamina | 0); + if (this.mana < needM) { + this.onToast(`Low mana · need ${needM}`); + return false; + } + if (this.stamina < needS) { + this.onToast(`Low stamina · need ${needS}`); + return false; + } + this.mana -= needM; + this.stamina -= needS; + return true; + } + + /** + * Staff / combat path cast: classify stroke → aoe | spikes | wall | stream. + * Costs mana+stamina scaled by LMB hold + path length (cast intensity). + * @param {import('three').CatmullRomCurve3} curve + * @param {number} length + * @param {number} [holdSec] + * @returns {{ kind: string, element: string, intensity?: number }|null} + */ + castPathAbility(curve, length, holdSec = 0) { + if (!curve) return null; + if (!this.inCombat) { + this.onToast('Enter combat (Q) to cast'); + return null; + } + const sc = settings.staffCast || {}; + if (sc.enabled === false) { + const costs = pathCastCosts(holdSec, length, 'stream', this.abilities.selected); + if (!this._spendResources(costs.mana, costs.stamina, 'Cast')) return null; + this.lastCastIntensity = costs.intensity; + this.abilities.cast(curve); + return { kind: 'stream', element: this.abilities.selected, intensity: costs.intensity }; + } + + const aoeMax = sc.aoeMaxLength ?? 3.2; + const spikesMax = sc.spikesMaxLength ?? 9; + const wallHold = sc.wallHoldSec ?? 0.85; + + let kind = 'stream'; + if (length <= aoeMax || holdSec < 0.2) kind = 'aoe'; + else if (holdSec >= wallHold || length >= (sc.wallMinLength ?? 9)) kind = 'wall'; + else if (length <= spikesMax) kind = 'spikes'; + + let element = this.abilities.selected; + if (kind === 'aoe' && sc.aoeElement) element = sc.aoeElement; + if (kind === 'spikes' && sc.spikesElement) element = sc.spikesElement; + if (kind === 'wall' && sc.wallElement) element = sc.wallElement; + if (kind === 'stream' && sc.streamElement) element = sc.streamElement; + + const costs = pathCastCosts(holdSec, length, kind, element); + if (!this._spendResources(costs.mana, costs.stamina, 'Path cast')) return null; + this.lastCastIntensity = costs.intensity; + const intensity = costs.intensity; + // Cast time scales with hold (min 0.35s, max 1.6s) — bar + cast anim before release + const pathCastTime = MathUtils.clamp(0.35 + holdSec * 0.35 + length * 0.02, 0.35, 1.6); + const endPt = curve.getPoint(1); + const aimPt = { x: endPt.x, y: endPt.y + 0.2, z: endPt.z }; + const labels = { aoe: 'AOE place', spikes: 'Spikes', wall: 'Wall', stream: 'Stream' }; + + const releasePath = () => { + const sig = signatureForElement(element === 'arcane' ? 'arcane' : element); + if (intensity >= 2.4 && sig) { + this.vfx?.deploy?.(sig.impactEffectId, { + origin: this.character.position.clone(), + forward: _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)).clone(), + intensity: intensity * 0.85 + }); + } + const facing = _fwd.set(Math.sin(this.character.facing), 0, Math.cos(this.character.facing)).clone(); + const pathPose = { + origin: this.character.position.clone(), + forward: facing, + aim: curve.getPoint(1), + intensity + }; + if (kind === 'aoe') { + const end = curve.getPoint(1); + const mid = end.clone(); + mid.y += 0.4; + const start = end.clone().add(new Vector3(0.01, 0.8, 0.01)); + const short = new CatmullRomCurve3([start, mid, end], false, 'catmullrom', 0.5); + this.abilities.select(element); + this.abilities.cast(short, element); + this.vfx?.deployPresentation?.(element, { ...pathPose, aim: end.clone() }, { + pathKind: 'aoe', + meteor: element === 'fire' && intensity >= 2.2 + }); + } else { + this.abilities.select(element); + this.abilities.cast(curve, element); + this.vfx?.deployPresentation?.(element, pathPose, { + pathKind: kind, + meteor: kind === 'stream' && element === 'fire' && intensity >= 2.4, + volley: kind === 'stream' && (element === 'fire' || element === 'arcane'), + shield: kind === 'wall' && element === 'storm' + }); + } + const sigName = intensity >= 2.4 && sig ? ` · ${sig.label}` : ''; + this.onToast( + `Staff · ${labels[kind]} (${element}) · ×${intensity.toFixed(1)} · −${costs.mana}MP −${costs.stamina}STA${sigName}` + ); + }; + + if (this._cast) this._interruptCast('replaced', false); + this._beginCast({ + label: `${labels[kind]} · ${element}`, + duration: pathCastTime, + element, + interruptible: true, + aim: aimPt, + onComplete: releasePath + }); + return { kind, element, intensity, mana: costs.mana, stamina: costs.stamina, castTime: pathCastTime }; + } +} diff --git a/src/combat/SkillProjectileSystem.js b/src/combat/SkillProjectileSystem.js new file mode 100644 index 0000000..80309f1 --- /dev/null +++ b/src/combat/SkillProjectileSystem.js @@ -0,0 +1,1255 @@ +/** + * Mesh skill projectiles with SI scale, contact sphere, force / knockback. + * + * Extends lab VFX — does **not** start a second physics engine for heroes. + * Projectiles are kinematic; contact uses distance spheres vs targets. + * On hit: explode mesh, VfxDirector impact, optional knockback callback. + * + * Summons: public/models/vfx/summons/summon-fire-fist.glb · summon-ice-shard.glb + * (extracted from author multipack — never load whole fire__ice pack). + */ + +import { + AdditiveBlending, + Box3, + Color, + DoubleSide, + Group, + Mesh, + MeshBasicMaterial, + MeshStandardMaterial, + SphereGeometry, + Vector3 +} from 'three'; +import { mmToM } from './motionMath.js'; +import { SUMMON_MESH_BY_ELEMENT, STAFF_CHARGE_MESH } from './skillDelivery.js'; +import { sharedGltfLoader } from '../loaders/gltfPipeline.js'; +import { + applyElementalOrbMaterials, + staffOrbForElement, + staffOrbWarmUrls, + STAFF_CHARGE, + STAFF_ORB_DIAMETER_M +} from '../vfx/staffOrbVfx.js'; +import { + ARROW_SYSTEMS, + EARTH_EMERGE_CHARGE, + EARTH_ROCK_DIAMETER_M, + EARTH_ROCK_MESHES, + FREEZE_NOVA, + WATER_BUBBLE, + createWaterBubbleMaterial, + pickEarthRocks, + resolveArrowEndEvent +} from '../vfx/elementAttackVfx.js'; +import { + PISTOL_BULLET, + isLivingTarget +} from '../vfx/pistolBulletVfx.js'; + +const _v = new Vector3(); +const _box = new Box3(); +const _bubbleGeo = new SphereGeometry(0.5, 10, 10); +const _trailGeo = new SphereGeometry(0.5, 6, 6); + +/** + * @typedef {object} ProjectileHit + * @property {Vector3} point + * @property {Vector3} forward + * @property {number} force + * @property {number} knockbackMm + * @property {number} knockupVy + * @property {number} aoe + * @property {string} [element] + * @property {object|null} [target] + * @property {boolean} [freeze] + * @property {number} [freezeSec] + * @property {string} [endEvent] + */ + +export class SkillProjectileSystem { + /** + * @param {{ + * scene: import('three').Scene, + * vfx?: import('../vfx/VfxDirector.js').VfxDirector|null, + * onHit?: (hit: ProjectileHit) => void + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.vfx = opts.vfx || null; + this.onHit = opts.onHit || (() => {}); + /** @type {object[]} */ + this._live = []; + /** @type {Map} */ + this._templates = new Map(); + /** Shared fleet GLTF (one Draco pool + Meshopt + KTX2 if bound) */ + this._loader = null; + this._placeholderGeo = new SphereGeometry(0.28, 12, 12); + /** @type {{ mesh: Group|Mesh, element: string, age: number, baseScale: number }|null} */ + this._charge = null; + } + + _ensureLoader() { + // Never new DRACOLoader here — thrash WASM workers vs AssetLoader + if (!this._loader) this._loader = sharedGltfLoader(); + return this._loader; + } + + /** + * Prefetch summon meshes. + * @param {string[]} [urls] + */ + async warm(urls) { + const list = + urls || + [ + ...Object.values(SUMMON_MESH_BY_ELEMENT).filter(Boolean), + ...staffOrbWarmUrls(), + ...EARTH_ROCK_MESHES, + ARROW_SYSTEMS.path.mesh, + ARROW_SYSTEMS.loft.mesh, + EARTH_EMERGE_CHARGE, + PISTOL_BULLET.meshUrl + ]; + await Promise.all(list.map((u) => this._loadTemplate(u).catch(() => null))); + } + + /** + * @param {string} url + * @returns {Promise} + */ + async _loadTemplate(url) { + if (!url) return null; + if (this._templates.has(url)) return this._templates.get(url); + const loader = this._ensureLoader(); + const gltf = await loader.loadAsync(url); + const root = gltf.scene || gltf.scenes?.[0]; + if (!root) return null; + // Normalize max dimension if author scale slipped + _box.setFromObject(root); + const size = new Vector3(); + _box.getSize(size); + const maxDim = Math.max(size.x, size.y, size.z, 1e-4); + if (maxDim > 1.2 || maxDim < 0.15) { + const s = 0.55 / maxDim; + root.scale.multiplyScalar(s); + } + root.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.receiveShadow = false; + } + }); + this._templates.set(url, root); + return root; + } + + /** + * Spawn a skill projectile. + * @param {{ + * origin: Vector3, + * target: Vector3, + * forward?: Vector3, + * element?: string, + * meshUrl?: string|null, + * speed?: number, + * gravity?: number, + * contactRadius?: number, + * life?: number, + * force?: number, + * knockbackMm?: number, + * knockupVy?: number, + * aoe?: number, + * size?: number, + * color?: string|number, + * targets?: { point: Vector3, mesh?: object, id?: string }[], + * explodeOnHit?: boolean + * }} opts + */ + async spawn(opts) { + const origin = opts.origin.clone(); + const target = opts.target.clone(); + let forward = opts.forward?.clone?.(); + if (!forward || forward.lengthSq() < 1e-8) { + forward = target.clone().sub(origin); + if (forward.lengthSq() < 1e-8) forward.set(0, 0, 1); + else forward.normalize(); + } else forward.normalize(); + + const speed = opts.speed ?? 14; + const orbDef = staffOrbForElement(opts.element); + const url = + opts.meshUrl || + orbDef.path || + SUMMON_MESH_BY_ELEMENT[opts.element || ''] || + SUMMON_MESH_BY_ELEMENT.fire; + + let mesh = null; + if (url) { + try { + const tpl = await this._loadTemplate(url); + if (tpl) { + mesh = tpl.clone(true); + // Staff orbs / charge: elemental materials (gd_orbs bake + runtime tint) + if (/\/orbs\/orb-|staff-charge|kamehameha-charge/.test(url) || opts.useOrbMaterials !== false) { + applyElementalOrbMaterials(mesh, opts.element, { + additive: opts.additive === true, + intensity: opts.intensity ?? 1 + }); + } else { + mesh.traverse((o) => { + if (o.isMesh && o.material) { + o.material = o.material.clone(); + if (opts.color && o.material.color) { + o.material.color = new Color(opts.color); + } + if (opts.element === 'holy' && o.material.emissive) { + o.material.emissive = new Color('#ffe9a8'); + o.material.emissiveIntensity = 0.55; + } + } + }); + } + } + } catch (e) { + console.warn('[SkillProjectile] mesh load', url, e); + } + } + if (!mesh) { + mesh = new Mesh( + this._placeholderGeo, + new MeshStandardMaterial({ + color: opts.color || orbDef.color || (opts.element === 'ice' ? '#88d4ff' : '#ff6a33'), + emissive: opts.element === 'holy' ? '#ffe9a8' : orbDef.emissive || '#331100', + emissiveIntensity: 0.55, + roughness: 0.35, + metalness: 0.12 + }) + ); + } + + // Orbs baked ~0.45 m; size is SI diameter multiplier + const baseDiam = /\/orbs\/orb-/.test(url || '') ? STAFF_ORB_DIAMETER_M : 0.55; + const scale = opts.size ?? baseDiam; + mesh.scale.setScalar(scale / baseDiam); + mesh.position.copy(origin); + this.scene.add(mesh); + + const vel = forward.clone().multiplyScalar(speed); + // Drop shots (over target): aim velocity toward target, not only forward + if (opts.gravity && opts.gravity < 0) { + const dist = origin.distanceTo(target); + const t = Math.max(0.25, dist / Math.max(4, speed)); + vel.copy(target).sub(origin).multiplyScalar(1 / t); + // leave room for gravity arc + vel.y += 0.5 * Math.abs(opts.gravity) * t * 0.35; + } + + const proj = { + mesh, + vel, + gravity: opts.gravity ?? 0, + contactRadius: opts.contactRadius ?? 0.35, + life: opts.life ?? 2.5, + age: 0, + force: opts.force ?? 8, + knockbackMm: opts.knockbackMm ?? 180, + knockupVy: opts.knockupVy ?? 2.2, + aoe: opts.aoe ?? 1.2, + element: opts.element || 'arcane', + explodeOnHit: opts.explodeOnHit !== false, + targets: opts.targets || [], + hit: false + }; + this._live.push(proj); + return proj; + } + + /** + * Small elemental charge shell at staff tip / hand (kamehameha_charging bake). + * One live charge at a time — managed by cast bar. + * @param {{ + * origin: Vector3, + * element?: string, + * size?: number, + * meshUrl?: string|null + * }} opts + */ + async spawnCharge(opts) { + this.clearCharge(); + const url = opts.meshUrl || STAFF_CHARGE_MESH || STAFF_CHARGE.path; + let mesh = null; + try { + const tpl = await this._loadTemplate(url); + if (tpl) { + mesh = tpl.clone(true); + applyElementalOrbMaterials(mesh, opts.element, { + additive: true, + intensity: 1.1 + }); + } + } catch (e) { + console.warn('[SkillProjectile] charge mesh', url, e); + } + if (!mesh) { + mesh = new Mesh( + this._placeholderGeo, + new MeshStandardMaterial({ + color: staffOrbForElement(opts.element).color, + emissive: staffOrbForElement(opts.element).emissive, + emissiveIntensity: 0.9, + transparent: true, + opacity: 0.75, + depthWrite: false + }) + ); + } + const size = opts.size ?? STAFF_CHARGE.diameterM ?? 0.35; + mesh.scale.setScalar(size / 0.35); + mesh.position.copy(opts.origin); + mesh.userData.staffCharge = true; + this.scene.add(mesh); + this._charge = { + mesh, + element: opts.element || 'arcane', + age: 0, + baseScale: size / 0.35 + }; + return this._charge; + } + + /** + * Follow cast origin while channeling. + * @param {Vector3} origin + * @param {number} [dt] + * @param {number} [progress01] + */ + updateCharge(origin, dt = 0, progress01 = 0) { + if (!this._charge?.mesh) return; + this._charge.age += dt; + this._charge.mesh.position.copy(origin); + const pulse = + 1 + 0.12 * Math.sin(this._charge.age * Math.PI * 2 * (STAFF_CHARGE.pulseHz || 3.2)); + const grow = 0.75 + 0.35 * Math.min(1, progress01); + this._charge.mesh.scale.setScalar(this._charge.baseScale * pulse * grow); + this._charge.mesh.rotation.y += dt * 4.5; + this._charge.mesh.rotation.x += dt * 1.2; + } + + clearCharge() { + if (!this._charge?.mesh) { + this._charge = null; + return; + } + this.scene.remove(this._charge.mesh); + this._charge.mesh.traverse?.((o) => { + if (o.material && o.isMesh) { + try { + o.material.dispose?.(); + } catch { + /* */ + } + } + }); + this._charge = null; + } + + /** + * Instant ring / nova (no travel) at point — still applies contact force. + * @param {object} opts same as spawn + radius + */ + pulse(opts) { + const point = opts.origin?.clone?.() || opts.target?.clone?.() || new Vector3(); + const aoe = opts.aoe ?? 1.5; + const vfxId = + opts.freeze || opts.element === 'ice' || opts.element === 'frost' + ? 'frost_wave' + : opts.element === 'holy' + ? 'moon_beam' + : 'inferno'; + this.vfx?.deploy?.(vfxId, { + origin: point, + intensity: opts.intensity ?? 1.1 + }); + for (const t of opts.targets || []) { + if (!t?.point) continue; + const d = point.distanceTo(t.point); + if (d <= aoe + 0.5) { + const fwd = _v.subVectors(t.point, point); + fwd.y = 0; + if (fwd.lengthSq() < 1e-6) fwd.set(0, 0, 1); + else fwd.normalize(); + this.onHit({ + point: t.point.clone(), + forward: fwd, + force: opts.force ?? 8, + knockbackMm: opts.knockbackMm ?? 180, + knockupVy: opts.knockupVy ?? 2.5, + aoe, + element: opts.element, + target: t, + freeze: !!opts.freeze, + freezeSec: opts.freezeSec ?? FREEZE_NOVA.freezeSec + }); + } + } + } + + /** + * Freeze AOE — expands from caster in all directions; freezes hostiles in radius. + * @param {{ + * origin: Vector3, + * radiusM?: number, + * expandSec?: number, + * freezeSec?: number, + * targets?: object[], + * element?: string + * }} opts + */ + spawnFreezeNova(opts) { + const origin = opts.origin.clone(); + origin.y = Math.max(0.05, origin.y); + const radius = opts.radiusM ?? FREEZE_NOVA.radiusM; + const expandSec = opts.expandSec ?? FREEZE_NOVA.expandSec; + const freezeSec = opts.freezeSec ?? FREEZE_NOVA.freezeSec; + + this.vfx?.deploy?.('frost_wave', { origin: origin.clone(), intensity: 1.25 }); + this.vfx?.deploy?.('ice_lightning_burst', { + origin: origin.clone(), + intensity: 0.85 + }); + + // Expanding bubble ring (water/ice animation feel from bubbles_2 concept) + const ring = new Group(); + ring.position.copy(origin); + const mat = createWaterBubbleMaterial({ color: FREEZE_NOVA.color, opacity: 0.5 }); + const n = FREEZE_NOVA.bubbleCount; + for (let i = 0; i < n; i++) { + const a = (i / n) * Math.PI * 2; + const m = new Mesh(_bubbleGeo, mat); + m.scale.setScalar(WATER_BUBBLE.diameterM); + m.position.set(Math.cos(a) * 0.35, 0.15 + Math.random() * 0.2, Math.sin(a) * 0.35); + ring.add(m); + } + this.scene.add(ring); + + this._live.push({ + mesh: ring, + vel: new Vector3(), + gravity: 0, + contactRadius: 0.1, + life: expandSec + 0.35, + age: 0, + force: 4, + knockbackMm: 40, + knockupVy: 0.3, + aoe: radius, + element: opts.element || 'ice', + explodeOnHit: false, + targets: opts.targets || [], + hit: false, + kind: 'freeze_nova', + freeze: true, + freezeSec, + expandSec, + radiusM: radius, + origin: origin.clone(), + hitIds: new Set() + }); + return true; + } + + /** + * Water bubble field along stream / impact (procedural — not whole bubbles_2 pack). + * @param {{ origin: Vector3, target: Vector3, forward?: Vector3, count?: number, speed?: number, targets?: object[] }} opts + */ + spawnBubbleStream(opts) { + const origin = opts.origin.clone(); + const target = opts.target.clone(); + let forward = opts.forward?.clone?.(); + if (!forward || forward.lengthSq() < 1e-8) { + forward = target.clone().sub(origin); + if (forward.lengthSq() < 1e-8) forward.set(0, 0, 1); + else forward.normalize(); + } else forward.normalize(); + + const count = opts.count ?? WATER_BUBBLE.countTravel; + const mat = createWaterBubbleMaterial(); + for (let i = 0; i < count; i++) { + const mesh = new Mesh(_bubbleGeo, mat); + const s = WATER_BUBBLE.diameterM * (0.7 + Math.random() * 0.6); + mesh.scale.setScalar(s); + const side = new Vector3(-forward.z, 0, forward.x).normalize(); + mesh.position + .copy(origin) + .addScaledVector(forward, -0.1 + i * 0.08) + .addScaledVector(side, (Math.random() - 0.5) * 0.35) + .add(new Vector3(0, (Math.random() - 0.2) * 0.25, 0)); + this.scene.add(mesh); + const jitter = side + .clone() + .multiplyScalar((Math.random() - 0.5) * 1.2) + .add(new Vector3(0, Math.random() * 0.8, 0)); + const vel = forward + .clone() + .multiplyScalar((opts.speed ?? 11) * (0.85 + Math.random() * 0.3)) + .add(jitter); + this._live.push({ + mesh, + vel, + gravity: -1.5, + contactRadius: 0.25, + life: 1.6 + Math.random() * 0.4, + age: 0, + force: 3, + knockbackMm: 60, + knockupVy: 0.5, + aoe: 0.6, + element: 'ice', + explodeOnHit: false, + targets: opts.targets || [], + hit: false, + kind: 'bubble' + }); + } + this.vfx?.deploy?.('moon_beam', { origin, intensity: 0.7 }); + } + + /** + * Flintlock / handgun bullet — Styloo bullet1, high speed, short trail (20%). + * Living hit → blood; terrain/aim → micro explosion (no blood). + * @param {{ + * origin: Vector3, + * target: Vector3, + * forward?: Vector3, + * targets?: object[], + * speed?: number, + * meshUrl?: string + * }} opts + */ + async spawnBullet(opts) { + const origin = opts.origin.clone(); + const target = opts.target.clone(); + let forward = opts.forward?.clone?.(); + if (!forward || forward.lengthSq() < 1e-8) { + forward = target.clone().sub(origin); + if (forward.lengthSq() < 1e-8) forward.set(0, 0, 1); + else forward.normalize(); + } else forward.normalize(); + + const speed = opts.speed ?? PISTOL_BULLET.speed; + const url = opts.meshUrl || PISTOL_BULLET.meshUrl; + + // Muzzle flash (short) + this.vfx?.deploy?.('inferno', { + origin: origin.clone(), + forward: forward.clone(), + intensity: 0.35 + }); + + let mesh = null; + try { + const tpl = await this._loadTemplate(url); + if (tpl) { + mesh = tpl.clone(true); + mesh.traverse((o) => { + if (o.isMesh && o.material) o.material = o.material.clone(); + }); + // SI: normalize max dim to bullet length + _box.setFromObject(mesh); + const size = new Vector3(); + _box.getSize(size); + const maxDim = Math.max(size.x, size.y, size.z, 1e-4); + const targetLen = PISTOL_BULLET.lengthM; + mesh.scale.multiplyScalar(targetLen / maxDim); + } + } catch (e) { + console.warn('[SkillProjectile] bullet mesh', url, e); + } + if (!mesh) { + mesh = new Mesh( + this._placeholderGeo, + new MeshStandardMaterial({ + color: 0xc0a060, + metalness: 0.7, + roughness: 0.35, + emissive: 0x331100, + emissiveIntensity: 0.25 + }) + ); + mesh.scale.setScalar(PISTOL_BULLET.lengthM / 0.55); + } + + mesh.position.copy(origin); + mesh.lookAt(origin.clone().add(forward)); + this.scene.add(mesh); + + // Short trail ribbon (20% default magic trail feel) — additive sparks + const trail = new Group(); + trail.name = 'bulletTrail'; + const trailMat = new MeshBasicMaterial({ + color: PISTOL_BULLET.trailColor, + transparent: true, + opacity: 0.55, + depthWrite: false, + blending: AdditiveBlending, + side: DoubleSide + }); + const segs = 5; + for (let i = 0; i < segs; i++) { + const s = new Mesh(_trailGeo, trailMat.clone()); + const t = (i + 1) / segs; + // Trail length ≈ 20% of 1 m default → 0.2 m total + const back = t * 0.2 * (PISTOL_BULLET.trailLengthFrac / 0.2); + s.scale.setScalar(PISTOL_BULLET.trailWidthM * (1.1 - t * 0.7)); + s.position.copy(forward).multiplyScalar(-back * 0.2); + trail.add(s); + } + mesh.add(trail); + + const vel = forward.clone().multiplyScalar(speed); + this._live.push({ + mesh, + vel, + gravity: PISTOL_BULLET.gravity, + contactRadius: PISTOL_BULLET.contactRadius, + life: PISTOL_BULLET.life, + age: 0, + force: PISTOL_BULLET.force, + knockbackMm: PISTOL_BULLET.knockbackMm, + knockupVy: PISTOL_BULLET.knockupVy, + aoe: PISTOL_BULLET.aoe, + element: 'physical', + explodeOnHit: true, + targets: opts.targets || [], + hit: false, + kind: 'bullet', + trail + }); + return true; + } + + /** + * Bullet impact: living → red liquid blood; terrain/props → micro boom (no blood). + * @param {object} p live projectile + * @param {object|null} target + */ + _bulletImpact(p, target) { + const at = p.mesh?.position?.clone?.() || new Vector3(); + const living = isLivingTarget(target); + if (living) { + // Red liquid-like blood splatter (no terrain blood) + this.vfx?.deploy?.('inferno', { + origin: at, + intensity: 0.55 + }); + const blood = new Group(); + const mat = new MeshStandardMaterial({ + color: 0x8b0000, + emissive: 0x440000, + emissiveIntensity: 0.4, + transparent: true, + opacity: 0.85, + roughness: 0.55, + metalness: 0.05 + }); + for (let i = 0; i < 10; i++) { + const drop = new Mesh(_bubbleGeo, mat); + const s = 0.04 + Math.random() * 0.07; + drop.scale.setScalar(s); + drop.position.set( + (Math.random() - 0.5) * 0.25, + Math.random() * 0.15, + (Math.random() - 0.5) * 0.25 + ); + blood.add(drop); + this._live.push({ + mesh: drop, + vel: new Vector3( + (Math.random() - 0.5) * 2.5, + 1.5 + Math.random() * 2.2, + (Math.random() - 0.5) * 2.5 + ), + gravity: -12, + contactRadius: 0, + life: 0.55 + Math.random() * 0.35, + age: 0, + force: 0, + knockbackMm: 0, + knockupVy: 0, + aoe: 0, + element: 'physical', + explodeOnHit: false, + targets: [], + hit: false, + kind: 'blood_drop' + }); + this.scene.add(drop); + } + // parent group not needed — drops are independent + void blood; + } else { + // Terrain / prop / aim — micro explosion, no blood + this.vfx?.deploy?.('inferno', { + origin: at, + intensity: 0.4 + }); + const flash = new Mesh( + _bubbleGeo, + new MeshBasicMaterial({ + color: 0xffaa55, + transparent: true, + opacity: 0.7, + depthWrite: false, + blending: AdditiveBlending + }) + ); + flash.position.copy(at); + flash.scale.setScalar(0.12); + this.scene.add(flash); + this._live.push({ + mesh: flash, + vel: new Vector3(), + gravity: 0, + contactRadius: 0, + life: 0.12, + age: 0, + force: 0, + knockbackMm: 0, + knockupVy: 0, + aoe: 0, + element: 'physical', + explodeOnHit: false, + targets: [], + hit: false, + kind: 'muzzle_micro' + }); + } + } + + /** + * Earth rocks: pull from below terrain beside caster, then linear or aimed path. + * @param {{ + * casterPos: Vector3, + * target: Vector3, + * forward?: Vector3, + * rockCount?: number, + * aimMode?: 'linear'|'aimed', + * targets?: object[], + * speed?: number + * }} opts + */ + async spawnEarthRocks(opts) { + const caster = opts.casterPos.clone(); + const target = opts.target.clone(); + let forward = opts.forward?.clone?.(); + if (!forward || forward.lengthSq() < 1e-8) { + forward = target.clone().sub(caster); + forward.y = 0; + if (forward.lengthSq() < 1e-8) forward.set(0, 0, 1); + else forward.normalize(); + } else { + forward.y = 0; + if (forward.lengthSq() > 1e-8) forward.normalize(); + else forward.set(0, 0, 1); + } + + const count = opts.rockCount ?? 1; + const urls = pickEarthRocks(count); + const side = new Vector3(-forward.z, 0, forward.x); + const aimMode = opts.aimMode || 'linear'; + const speed = opts.speed ?? 13; + + // Emerge charge tell at feet (kamehameha charge shell, earth tint) + try { + const chargeTpl = await this._loadTemplate(EARTH_EMERGE_CHARGE); + if (chargeTpl) { + const ch = chargeTpl.clone(true); + applyElementalOrbMaterials(ch, 'nature', { additive: true, intensity: 0.9 }); + ch.position.set(caster.x, 0.05, caster.z); + ch.scale.setScalar(0.9); + this.scene.add(ch); + this._live.push({ + mesh: ch, + vel: new Vector3(), + gravity: 0, + contactRadius: 0, + life: 0.45, + age: 0, + force: 0, + knockbackMm: 0, + knockupVy: 0, + aoe: 0, + element: 'nature', + explodeOnHit: false, + targets: [], + hit: false, + kind: 'emerge_tell' + }); + } + } catch { + /* optional */ + } + + this.vfx?.deploy?.('earth_surge', { + origin: caster.clone().setY(0.05), + intensity: 1.0 + }); + + for (let i = 0; i < urls.length; i++) { + const url = urls[i]; + const lateral = (i - (urls.length - 1) / 2) * 0.55; + // Spawn underground next to caster + const emerge = caster + .clone() + .addScaledVector(side, lateral) + .addScaledVector(forward, 0.4 + Math.random() * 0.3); + emerge.y = -0.85; + + let mesh = null; + try { + const tpl = await this._loadTemplate(url); + if (tpl) { + mesh = tpl.clone(true); + mesh.traverse((o) => { + if (o.isMesh && o.material) o.material = o.material.clone(); + }); + } + } catch (e) { + console.warn('[SkillProjectile] rock', url, e); + } + if (!mesh) { + mesh = new Mesh( + this._placeholderGeo, + new MeshStandardMaterial({ color: 0x6a5a48, roughness: 0.9 }) + ); + } + mesh.scale.setScalar(1); + mesh.position.copy(emerge); + this.scene.add(mesh); + + // Aimed = fly toward soft-lock/aim; linear = forward path + const dest = + aimMode === 'aimed' + ? target.clone().add(new Vector3(lateral * 0.3, 0.4, 0)) + : caster + .clone() + .addScaledVector(forward, 12) + .addScaledVector(side, lateral * 0.5) + .setY(caster.y + 1.0); + + this._live.push({ + mesh, + vel: new Vector3(), + gravity: 0, + contactRadius: EARTH_ROCK_DIAMETER_M * 0.55, + life: 3.2, + age: 0, + force: 10, + knockbackMm: 220, + knockupVy: 2.8, + aoe: 1.1, + element: 'nature', + explodeOnHit: true, + targets: opts.targets || [], + hit: false, + kind: 'earth_rock', + phase: 'emerge', + emergeDur: 0.28 + i * 0.05, + emergeFrom: emerge.clone(), + emergeTo: emerge.clone().setY(0.45 + Math.random() * 0.15), + flyTarget: dest, + flySpeed: speed * (0.92 + Math.random() * 0.16), + aimMode + }); + } + } + + /** + * Dual arrow systems: + * - path: linear attack path; distance sets end event location + * - loft: throw / place / trap / summon (higher arc) + * @param {{ + * origin: Vector3, + * target: Vector3, + * system?: 'path'|'loft', + * endEvent?: string, + * distanceM?: number, + * targets?: object[], + * size?: number + * }} opts + */ + async spawnArrow(opts) { + const system = opts.system === 'loft' ? 'loft' : 'path'; + const def = ARROW_SYSTEMS[system]; + const endEvent = resolveArrowEndEvent(opts.endEvent, system); + const origin = opts.origin.clone(); + let target = opts.target.clone(); + const dist = + opts.distanceM ?? + Math.max(2, Math.min(28, origin.distanceTo(target))); + const dir = target.clone().sub(origin); + if (dir.lengthSq() < 1e-8) dir.set(0, 0, 1); + else dir.normalize(); + // Distance determines where event / end / aoe happens + target = origin.clone().addScaledVector(dir, dist); + if (system === 'loft') target.y = Math.max(0.1, target.y); + + let mesh = null; + try { + const tpl = await this._loadTemplate(def.mesh); + if (tpl) { + mesh = tpl.clone(true); + mesh.traverse((o) => { + if (o.isMesh && o.material) o.material = o.material.clone(); + }); + } + } catch (e) { + console.warn('[SkillProjectile] arrow', def.mesh, e); + } + if (!mesh) { + mesh = new Mesh( + this._placeholderGeo, + new MeshStandardMaterial({ + color: system === 'loft' ? 0xff8833 : 0x55bbff, + emissive: system === 'loft' ? 0x662200 : 0x114488, + emissiveIntensity: 0.5 + }) + ); + } + + // Path: size can scale slightly with distance (skill reach feel) + let sizeMul = opts.size ?? 1; + if (def.sizeScalesWithDistance) { + sizeMul *= 0.85 + Math.min(0.45, dist / 40); + } + mesh.scale.setScalar(sizeMul); + mesh.position.copy(origin); + this.scene.add(mesh); + + const speed = system === 'loft' ? 10 : 18; + const travelT = Math.max(0.2, dist / speed); + const vel = dir.clone().multiplyScalar(speed); + if (system === 'loft') { + // loft arc so curved arrow reads as throw + vel.y += Math.abs(def.gravity || 12) * travelT * 0.45 * (def.loft || 0.4); + } + + this._live.push({ + mesh, + vel, + gravity: def.gravity ?? 0, + contactRadius: 0.35 * sizeMul, + life: travelT + 0.35, + age: 0, + force: system === 'loft' ? 6 : 9, + knockbackMm: system === 'loft' ? 100 : 160, + knockupVy: system === 'loft' ? 1.2 : 1.8, + aoe: endEvent === 'aoe' ? Math.min(3.5, 1.2 + dist * 0.06) : 0.9, + element: 'arcane', + explodeOnHit: endEvent === 'explode' || endEvent === 'impact', + targets: opts.targets || [], + hit: false, + kind: 'arrow', + arrowSystem: system, + endEvent, + endPoint: target.clone(), + distanceM: dist, + endFired: false + }); + } + + /** + * @param {number} dt + */ + update(dt) { + for (let i = this._live.length - 1; i >= 0; i--) { + const p = this._live[i]; + p.age += dt; + if (p.hit || p.age >= p.life) { + if (p.kind === 'arrow' && !p.endFired) this._fireArrowEnd(p); + if (p.kind === 'freeze_nova' && !p.hit) this._finishFreezeNova(p); + this._destroy(i, p.hit); + continue; + } + + // ── Freeze expanding ring ── + if (p.kind === 'freeze_nova') { + const t = Math.min(1, p.age / Math.max(0.05, p.expandSec)); + const r = p.radiusM * t; + p.mesh.scale.setScalar(Math.max(0.2, r / 0.5)); + p.mesh.rotation.y += dt * 2.5; + for (const tgt of p.targets || []) { + if (!tgt?.point || !tgt.id) continue; + if (p.hitIds.has(tgt.id)) continue; + const d = Math.hypot(tgt.point.x - p.origin.x, tgt.point.z - p.origin.z); + if (d <= r + 0.4) { + p.hitIds.add(tgt.id); + const fwd = _v.subVectors(tgt.point, p.origin); + fwd.y = 0; + if (fwd.lengthSq() < 1e-6) fwd.set(0, 0, 1); + else fwd.normalize(); + this.onHit({ + point: tgt.point.clone(), + forward: fwd, + force: 3, + knockbackMm: 30, + knockupVy: 0.2, + aoe: p.radiusM, + element: 'ice', + target: tgt, + freeze: true, + freezeSec: p.freezeSec + }); + } + } + continue; + } + + // ── Earth rock emerge → fly ── + if (p.kind === 'earth_rock') { + if (p.phase === 'emerge') { + const u = Math.min(1, p.age / p.emergeDur); + p.mesh.position.lerpVectors(p.emergeFrom, p.emergeTo, u); + p.mesh.rotation.x += dt * 4; + p.mesh.rotation.z += dt * 2; + if (u >= 1) { + p.phase = 'fly'; + const dir = p.flyTarget.clone().sub(p.mesh.position); + if (dir.lengthSq() < 1e-6) dir.set(0, 0, 1); + else dir.normalize(); + p.vel.copy(dir).multiplyScalar(p.flySpeed); + // slight upward for linear + if (p.aimMode === 'linear') p.vel.y += 1.2; + else p.vel.y += 2.0; + p.gravity = -9; + } + continue; + } + } + + if (p.kind === 'emerge_tell') { + p.mesh.scale.setScalar(0.9 + p.age * 1.5); + p.mesh.rotation.y += dt * 6; + continue; + } + + // Integrate + p.vel.y += (p.gravity || 0) * dt; + p.mesh.position.addScaledVector(p.vel, dt); + // Face velocity + if (p.vel.lengthSq() > 1e-4 && p.kind !== 'bubble') { + p.mesh.lookAt(p.mesh.position.clone().add(p.vel)); + } + if (p.kind === 'bubble') { + p.mesh.scale.multiplyScalar(1 + dt * 0.15); + } + + // Arrow: fire end event at distance (even without target mesh hit) + if (p.kind === 'arrow' && !p.endFired && p.endPoint) { + if (p.mesh.position.distanceTo(p.endPoint) < 0.55 || p.age >= p.life - 0.05) { + this._fireArrowEnd(p); + p.hit = true; + this._destroy(i, true); + continue; + } + } + + // Contact — prefer living targets over aim for bullets + const order = + p.kind === 'bullet' + ? [...(p.targets || [])].sort((a, b) => { + const la = isLivingTarget(a) ? 0 : 1; + const lb = isLivingTarget(b) ? 0 : 1; + return la - lb; + }) + : p.targets || []; + for (const t of order) { + if (!t?.point) continue; + // Aim soft-point only if no living within range (bullets) + if (p.kind === 'bullet' && t.kind === 'aim' && order.some((x) => isLivingTarget(x))) { + const nearLiving = order.find( + (x) => + isLivingTarget(x) && + x.point && + p.mesh.position.distanceTo(x.point) <= p.contactRadius + 0.55 + ); + if (nearLiving) continue; + } + const d = p.mesh.position.distanceTo(t.point); + const pad = p.kind === 'bullet' ? 0.25 : 0.45; + if (d <= p.contactRadius + pad) { + p.hit = true; + const fwd = p.vel.lengthSq() > 1e-4 ? p.vel.clone().normalize() : new Vector3(0, 0, 1); + if (p.kind === 'arrow' && !p.endFired) this._fireArrowEnd(p, t); + else if (p.kind === 'bullet') { + this._bulletImpact(p, t); + this.onHit({ + point: p.mesh.position.clone(), + forward: fwd, + force: p.force, + knockbackMm: p.knockbackMm, + knockupVy: p.knockupVy, + aoe: p.aoe, + element: p.element, + target: t, + impactKind: isLivingTarget(t) ? 'blood' : 'micro_explode' + }); + } else { + this.onHit({ + point: p.mesh.position.clone(), + forward: fwd, + force: p.force, + knockbackMm: p.knockbackMm, + knockupVy: p.knockupVy, + aoe: p.aoe, + element: p.element, + target: t, + freeze: p.freeze, + freezeSec: p.freezeSec, + endEvent: p.endEvent + }); + if (p.explodeOnHit) this._explode(p); + } + this._destroy(i, true); + break; + } + } + // Ground hit for drop shots / bullets + if (p.gravity < 0 && p.mesh.position.y <= 0.08 && p.kind !== 'emerge_tell' && p.kind !== 'blood_drop') { + p.hit = true; + if (p.kind === 'arrow' && !p.endFired) this._fireArrowEnd(p); + else if (p.kind === 'bullet') { + this._bulletImpact(p, { kind: 'terrain', id: 'ground' }); + this.onHit({ + point: p.mesh.position.clone(), + forward: new Vector3(0, -1, 0), + force: p.force, + knockbackMm: p.knockbackMm, + knockupVy: p.knockupVy, + aoe: p.aoe, + element: p.element, + target: { kind: 'terrain', id: 'ground' }, + impactKind: 'micro_explode' + }); + } else { + this.onHit({ + point: p.mesh.position.clone(), + forward: new Vector3(0, -1, 0), + force: p.force, + knockbackMm: p.knockbackMm, + knockupVy: p.knockupVy, + aoe: p.aoe, + element: p.element, + target: null, + endEvent: p.endEvent + }); + if (p.explodeOnHit) this._explode(p); + } + this._destroy(i, true); + } + } + } + + _finishFreezeNova(p) { + this.vfx?.deploy?.('frost_wave', { + origin: p.origin.clone(), + intensity: 0.9 + }); + } + + /** + * Arrow end events: explode / aoe / blink / return / throw / trap / place / summon + * Distance already baked into endPoint. + */ + _fireArrowEnd(p, target = null) { + if (p.endFired) return; + p.endFired = true; + const at = p.mesh?.position?.clone?.() || p.endPoint?.clone?.() || new Vector3(); + const ev = p.endEvent || 'impact'; + const fwd = + p.vel?.lengthSq?.() > 1e-4 ? p.vel.clone().normalize() : new Vector3(0, 0, 1); + + if (ev === 'explode' || ev === 'impact') { + this.vfx?.deploy?.('inferno', { origin: at, intensity: 1.1 }); + } else if (ev === 'aoe') { + this.vfx?.deploy?.('inferno', { origin: at, intensity: 1.2 }); + this.pulse({ + origin: at, + aoe: p.aoe || 2.2, + element: 'arcane', + targets: p.targets, + force: p.force + }); + } else if (ev === 'blink') { + // Teleport-style: flash at end; host may move character via onHit + this.vfx?.deploy?.('arcane_swirl', { origin: at, intensity: 1.15 }); + this.vfx?.deploy?.('moon_beam', { origin: at, intensity: 0.8 }); + } else if (ev === 'return') { + this.vfx?.deploy?.('chain_lightning', { origin: at, intensity: 0.9 }); + } else if (ev === 'trap' || ev === 'place_device') { + this.vfx?.deploy?.('earth_surge', { origin: at, intensity: 0.85 }); + } else if (ev === 'summon') { + this.vfx?.deploy?.('arcane_swirl', { origin: at, intensity: 1.0 }); + } else if (ev === 'throw') { + this.vfx?.deploy?.('inferno', { origin: at, intensity: 0.75 }); + } + + this.onHit({ + point: at, + forward: fwd, + force: p.force, + knockbackMm: p.knockbackMm, + knockupVy: p.knockupVy, + aoe: p.aoe, + element: p.element, + target, + endEvent: ev + }); + } + + _explode(p) { + const id = + p.element === 'ice' || p.element === 'frost' || p.element === 'water' + ? 'frost_wave' + : p.element === 'holy' + ? 'moon_beam' + : 'inferno'; + this.vfx?.deploy?.(id, { + origin: p.mesh.position.clone(), + intensity: 1.15, + forward: p.vel.clone().normalize() + }); + } + + _destroy(index, _hit) { + const p = this._live[index]; + if (!p) return; + this.scene.remove(p.mesh); + p.mesh.traverse?.((o) => { + if (o.geometry && o.geometry !== this._placeholderGeo) { + /* shared template geos — do not dispose */ + } + if (o.material && o.isMesh) { + // cloned mats ok to dispose + try { + o.material.dispose?.(); + } catch { + /* */ + } + } + }); + this._live.splice(index, 1); + } + + clear() { + while (this._live.length) this._destroy(0, false); + } + + dispose() { + this.clear(); + this._placeholderGeo.dispose(); + this._templates.clear(); + } +} diff --git a/src/combat/castResources.js b/src/combat/castResources.js new file mode 100644 index 0000000..72f8ef6 --- /dev/null +++ b/src/combat/castResources.js @@ -0,0 +1,103 @@ +import { MathUtils } from 'three'; +import { settings } from '../config/settings.js'; + +/** + * Spell resource costs — mana + stamina for Warlords lab / production controller. + * + * **Intensity** scales with LMB hold time and path length (bigger spell = bigger cost). + * Use for path cast, digit skills, and T0 base attacks. + * + * @see docs/PRODUCTION_CONTROLLER_SSOT.md + */ + +/** + * Cast intensity 1..3 from hold + path length. + * @param {number} holdSec LMB hold seconds + * @param {number} [lengthM] path length metres + * @returns {number} + */ +export function castIntensity(holdSec = 0, lengthM = 0) { + const cfg = settings.drc?.resources || {}; + const holdW = cfg.holdWeight ?? 0.42; + const lenW = cfg.lengthWeight ?? 0.055; + const minI = cfg.intensityMin ?? 1; + const maxI = cfg.intensityMax ?? 3; + const hold = Math.max(0, Number(holdSec) || 0); + const len = Math.max(0, Number(lengthM) || 0); + // Base 1 + hold ramp + long-path tax after 2 m + return MathUtils.clamp(1 + hold * holdW + Math.max(0, len - 2) * lenW, minI, maxI); +} + +/** + * Scaled mana + stamina from base costs and intensity. + * @param {{ manaCost?: number, staminaCost?: number, mana?: number, stamina?: number }} base + * @param {number} intensity 1..3 + * @returns {{ mana: number, stamina: number, intensity: number }} + */ +export function scaleResourceCost(base = {}, intensity = 1) { + const i = MathUtils.clamp(intensity, 1, 3); + const mana0 = Number(base.manaCost ?? base.mana ?? 0) || 0; + const sta0 = Number(base.staminaCost ?? base.stamina ?? 0) || 0; + // Mana tracks intensity hard; stamina rises slower (channel effort) + const mana = Math.ceil(mana0 * i); + const stamina = Math.ceil(sta0 * (0.55 + 0.45 * i)); + return { mana, stamina, intensity: i }; +} + +/** + * Default path-cast base costs by placement kind (before intensity). + * @param {'aoe'|'spikes'|'wall'|'stream'} kind + * @param {string} [element] + */ +export function pathCastBaseCost(kind = 'stream', element = 'fire') { + const table = settings.drc?.pathCastCosts || {}; + const byKind = table[kind] || table.stream || { mana: 10, stamina: 8 }; + // Earth wall / fire stream slightly pricier + let mul = 1; + if (kind === 'wall') mul = 1.25; + if (kind === 'stream' && element === 'fire') mul = 1.1; + if (kind === 'aoe') mul = 0.9; + return { + mana: Math.ceil((byKind.mana ?? 10) * mul), + stamina: Math.ceil((byKind.stamina ?? 8) * mul) + }; +} + +/** + * Full cost for a path cast stroke. + * @param {number} holdSec + * @param {number} lengthM + * @param {'aoe'|'spikes'|'wall'|'stream'} kind + * @param {string} [element] + */ +export function pathCastCosts(holdSec, lengthM, kind, element) { + const base = pathCastBaseCost(kind, element); + const intensity = castIntensity(holdSec, lengthM); + return { ...scaleResourceCost(base, intensity), kind, element }; +} + +/** + * Full cost for a hotbar / T0 skill (optional hold intensity for charged cast). + * @param {{ manaCost?: number, staminaCost?: number }} skill + * @param {number} [holdSec] + * @param {number} [lengthM] + */ +export function skillCastCosts(skill, holdSec = 0, lengthM = 0) { + const intensity = holdSec > 0.05 || lengthM > 0.5 ? castIntensity(holdSec, lengthM) : 1; + return scaleResourceCost( + { + manaCost: skill?.manaCost ?? skill?.mana ?? defaultManaForSkill(skill), + staminaCost: skill?.staminaCost ?? skill?.stamina ?? 8 + }, + intensity + ); +} + +/** Infer mana when skill only has stamina (legacy kit). */ +export function defaultManaForSkill(skill) { + if (!skill) return 8; + if (skill.manaCost != null) return skill.manaCost; + if (skill.style === 'melee' || skill.style === 'ranged') return 0; + // Spells: mana ≈ 0.75 × stamina baseline + return Math.max(4, Math.ceil((skill.staminaCost ?? 10) * 0.75)); +} diff --git a/src/combat/castingSpellKit.js b/src/combat/castingSpellKit.js new file mode 100644 index 0000000..ff73009 --- /dev/null +++ b/src/combat/castingSpellKit.js @@ -0,0 +1,500 @@ +/** + * Casting lab → 10 learnable spells → WEAPON_SKILLS / staffs. + * + * Each spell is grounded in real lab systems (not invented FX): + * - Element Ability scripts: FireAbility · WaterAbility · EarthAbility · WindAbility + * - Path modes: stream · aoe · spikes · wall (settings.staffCast + pathCastClassify) + * - VFX ids: vfxCatalog + CASTING_ELEMENT_PHASE_VFX + * - Anim: magic pack cast clip (weaponAnimPack staff → magic) + * - Catalog ids: info…/WEAPON_SKILLS.html · master-weaponSkills.json STAFF + * + * Browse: https://info.grudge-studio.com/WEAPON_SKILLS.html + * Live lab: https://casting-abilities-threejs.vercel.app/ + */ + +import { CASTING_ELEMENT_PHASE_VFX } from './elementWeaponSkills.js'; +import { STAFF_SIGNATURE_SKILLS } from './staffSignatureSkills.js'; +import { abilityKeyForElement } from '../config/settings.js'; + +/** @typedef {'fire'|'storm'|'ice'|'nature'|'holy'|'arcane'} SpellElement */ +/** @typedef {'stream'|'aoe'|'spikes'|'wall'} PathMode */ + +/** + * @typedef {object} CastingSpell + * @property {string} id lab id (casting_*) + * @property {string} catalogSkillId master-weaponSkills STAFF id when known + * @property {string} label + * @property {string} description + * @property {SpellElement} element + * @property {PathMode} pathMode + * @property {'spell'} style + * @property {'cast'} animRole + * @property {string} animPack magic + * @property {string} castClip baked role path + * @property {string} abilityClass FireAbility|WaterAbility|EarthAbility|WindAbility|arcane + * @property {string} castEffectId + * @property {string} travelEffectId + * @property {string} impactEffectId + * @property {string} staffWeaponId + * @property {number} rangeM + * @property {number} cooldown + * @property {number} castDuration + * @property {number} staminaCost + * @property {number} [manaCost] default ~0.75× stamina if omitted + * @property {number} slot hotbar 0–9 (lab kit) + * @property {'primary'|'secondary'|'ability'|'ultimate'} slotType + * @property {string} learnFrom short agent note + * @property {string[]} assets textures/shaders/scripts to study + */ + +const F = CASTING_ELEMENT_PHASE_VFX.fire; +const S = CASTING_ELEMENT_PHASE_VFX.storm; +const I = CASTING_ELEMENT_PHASE_VFX.ice; +const N = CASTING_ELEMENT_PHASE_VFX.nature; +const H = CASTING_ELEMENT_PHASE_VFX.holy; +const R = CASTING_ELEMENT_PHASE_VFX.arcane; +/** @deprecated legacy aliases used while core spells migrate */ +const W = I; +const E = N; +const A = S; + +const CAST_CLIP = 'magic/standing 1h cast spell 01'; + +/** Default mana from stamina when author omits manaCost */ +export function manaFromStam(staminaCost) { + return Math.max(4, Math.ceil((staminaCost || 10) * 0.75)); +} + +/** + * Core ten + staff signatures (Inferno, Blizzard, Warp, Quake, Tempest). + * Order: fire → ice → nature → storm → holy/arcane, then signatures. + * + * @type {readonly CastingSpell[]} + */ +const CASTING_SPELL_CORE = [ + { + id: 'casting_fire_bolt', + catalogSkillId: 'staff_fire_bolt', + label: 'Fire Bolt', + description: 'Volumetric fireball along a drawn path — FireAbility travel + detonation.', + element: 'fire', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'FireAbility', + castEffectId: F.cast, + travelEffectId: F.travel, + impactEffectId: F.impact, + staffWeaponId: F.staffWeaponId, + rangeM: 16, + cooldown: 1.1, + castDuration: 0.85, + staminaCost: 12, + manaCost: 10, + slot: 0, + slotType: 'primary', + learnFrom: 'FireAbility.js · VolumetricFireMaterial · settings.fire', + assets: [ + 'src/abilities/FireAbility.js', + 'src/materials/VolumetricFireMaterial.js', + 'src/config/settings.js#fire', + 'vfx:fire_hand|fireball|inferno' + ] + }, + { + id: 'casting_flame_wave', + catalogSkillId: 'staff_flame_wave', + label: 'Flame Wave', + description: 'Short-path AOE place — fire impact burst at aim (staffCast aoe).', + element: 'fire', + pathMode: 'aoe', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'FireAbility', + castEffectId: F.cast, + travelEffectId: F.travel, + impactEffectId: 'inferno', + staffWeaponId: F.staffWeaponId, + rangeM: 10, + cooldown: 3.0, + castDuration: 0.9, + staminaCost: 16, + manaCost: 12, + slot: 1, + slotType: 'secondary', + learnFrom: 'pathCastClassify aoe · deployElementImpact fire', + assets: [ + 'src/combat/pathCastClassify.js', + 'src/config/settings.js#staffCast', + 'vfx:inferno|fire_aura' + ] + }, + { + id: 'casting_frost_bolt', + catalogSkillId: 'staff_frost_bolt', + label: 'Frost Bolt', + description: 'Water stream along path — WaterAbility ribbons + frost impact.', + element: 'ice', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WaterAbility', + castEffectId: I.cast, + travelEffectId: I.travel, + impactEffectId: I.impact, + staffWeaponId: I.staffWeaponId, + rangeM: 14, + cooldown: 1.2, + castDuration: 0.9, + staminaCost: 12, + slot: 2, + slotType: 'primary', + learnFrom: 'WaterAbility.js · settings.water spray/foam', + assets: [ + 'src/abilities/WaterAbility.js', + 'src/config/settings.js#water', + 'vfx:moon_beam|frost_wave' + ] + }, + { + id: 'casting_ice_nova', + catalogSkillId: 'staff_ice_nova', + label: 'Ice Nova', + description: 'Medium path spikes — frost ground wave along stroke (staffCast spikes).', + element: 'ice', + pathMode: 'spikes', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WaterAbility', + castEffectId: I.cast, + travelEffectId: I.travel, + impactEffectId: 'frost_wave', + staffWeaponId: I.staffWeaponId, + rangeM: 12, + cooldown: 3.5, + castDuration: 0.95, + staminaCost: 16, + slot: 3, + slotType: 'secondary', + learnFrom: 'staffCast spikesElement water · frost_wave', + assets: [ + 'src/config/settings.js#staffCast.spikes', + 'vfx:frost_wave|ice_lightning_burst' + ] + }, + { + id: 'casting_earth_spike', + catalogSkillId: 'staff_earthquake', + label: 'Earth Spike', + description: 'Earth rise along path — EarthAbility + earth_surge (nature staff line).', + element: 'nature', + pathMode: 'spikes', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'EarthAbility', + castEffectId: E.cast, + travelEffectId: E.travel, + impactEffectId: E.impact, + staffWeaponId: E.staffWeaponId, + rangeM: 12, + cooldown: 1.4, + castDuration: 1.0, + staminaCost: 16, + slot: 4, + slotType: 'ability', + learnFrom: 'EarthAbility.js · settings.earth · nature staff', + assets: [ + 'src/abilities/EarthAbility.js', + 'src/config/settings.js#earth', + 'vfx:earth_surge', + 'catalog:t0_staff_vine_lash' + ] + }, + { + id: 'casting_stone_wall', + catalogSkillId: 'staff_natures_fury', + label: 'Stone Wall', + description: 'Long path / hold → wall barrier (staffCast wall + earth).', + element: 'nature', + pathMode: 'wall', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'EarthAbility', + castEffectId: E.cast, + travelEffectId: E.travel, + impactEffectId: 'earth_surge', + staffWeaponId: E.staffWeaponId, + rangeM: 14, + cooldown: 6.0, + castDuration: 1.1, + staminaCost: 20, + slot: 5, + slotType: 'ability', + learnFrom: 'staffCast wallMinLength · wallHoldSec', + assets: [ + 'src/combat/pathCastClassify.js', + 'src/config/settings.js#staffCast.wall', + 'vfx:earth_surge' + ] + }, + { + id: 'casting_wind_tempest', + catalogSkillId: 'staff_storm_call', + label: 'Wind Tempest', + description: 'Wind stream + lightning travel — WindAbility + chain arcs.', + element: 'storm', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WindAbility', + castEffectId: A.cast, + travelEffectId: A.travel, + impactEffectId: A.impact, + staffWeaponId: A.staffWeaponId, + rangeM: 14, + cooldown: 2.5, + castDuration: 0.95, + staminaCost: 14, + slot: 6, + slotType: 'ultimate', + learnFrom: 'WindAbility.js · wind residual afterimage palette', + assets: [ + 'src/abilities/WindAbility.js', + 'src/config/settings.js#wind', + 'src/vfx/DodgeAfterimage.js', + 'vfx:chain_lightning|ice_lightning_burst' + ] + }, + { + id: 'casting_gale_nova', + catalogSkillId: 'staff_thunder_cataclysm', + label: 'Gale Nova', + description: 'Wind AOE place — lightning burst at endpoint.', + element: 'storm', + pathMode: 'aoe', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WindAbility', + castEffectId: A.cast, + travelEffectId: A.travel, + impactEffectId: 'ice_lightning_burst', + staffWeaponId: A.staffWeaponId, + rangeM: 10, + cooldown: 5.0, + castDuration: 1.0, + staminaCost: 18, + slot: 7, + slotType: 'ultimate', + learnFrom: 'Wind aoe place · ice_lightning_burst sandbox V', + assets: ['vfx:ice_lightning_burst|chain_lightning', 'src/vfx/vfxCatalog.js'] + }, + { + id: 'casting_holy_light', + catalogSkillId: 'staff_holy_light', + label: 'Holy Light', + description: 'Holy cast tell + moon beam — Holy Staff light line.', + element: 'holy', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WindAbility', + castEffectId: H.cast, + travelEffectId: H.travel, + impactEffectId: H.impact, + staffWeaponId: H.staffWeaponId, + rangeM: 14, + cooldown: 1.3, + castDuration: 0.9, + staminaCost: 12, + slot: 8, + slotType: 'primary', + learnFrom: 'holy phase VFX · moon_beam (sandbox B)', + assets: [ + 'src/combat/elementWeaponSkills.js#holy', + 'vfx:arcane_swirl|moon_beam' + ] + }, + { + id: 'casting_meteor_strike', + catalogSkillId: 'staff_meteor_strike', + label: 'Meteor Strike', + description: 'Long fire stream — high intensity FireAbility + inferno impact.', + element: 'fire', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'FireAbility', + castEffectId: F.cast, + travelEffectId: F.travel, + impactEffectId: 'inferno', + staffWeaponId: F.staffWeaponId, + rangeM: 20, + cooldown: 8.0, + castDuration: 1.2, + staminaCost: 24, + slot: 9, + slotType: 'ability', + learnFrom: 'FireAbility full path · intensity knobs settings.effect', + assets: [ + 'src/abilities/FireAbility.js', + 'src/vfx/effectPrefab.js', + 'vfx:fireball|inferno', + 'settings.effect intensity/aoe/speed/size' + ] + } +]; + +/** Full learnable kit: 10 core + 5 staff signatures */ +export const CASTING_SPELL_KIT = Object.freeze([ + ...CASTING_SPELL_CORE, + ...STAFF_SIGNATURE_SKILLS +]); + +/** @type {Map} */ +const BY_ID = new Map(CASTING_SPELL_KIT.map((s) => [s.id, s])); +/** @type {Map} */ +const BY_CATALOG = new Map(CASTING_SPELL_KIT.map((s) => [s.catalogSkillId, s])); + +export function spellById(id) { + return BY_ID.get(id) || BY_CATALOG.get(id) || null; +} + +export function spellBySlot(slot) { + return CASTING_SPELL_KIT.find((s) => s.slot === slot) || null; +} + +/** First four for classic DRC 1–4 bar (fire bolt, flame wave, frost bolt, ice nova). */ +export function defaultBarSpells() { + return CASTING_SPELL_KIT.filter((s) => s.slot < 4); +} + +/** Full ten for kit panel / export. */ +export function allCastingSpells() { + return CASTING_SPELL_KIT.slice(); +} + +/** + * Fleet / DRC-shaped skill from kit spell. + * @param {CastingSpell} spell + */ +export function toDrcSkill(spell) { + if (!spell) return null; + return { + id: spell.id, + label: spell.label, + slot: spell.slot % 4, + kitSlot: spell.slot, + style: 'spell', + element: spell.element, + /** Product element → ability pool (fire|water|earth|wind) */ + abilityElement: abilityKeyForElement(spell.element), + pathMode: spell.pathMode, + animRole: spell.animRole, + animPack: spell.animPack, + castClip: spell.castClip, + rangeM: spell.rangeM, + cooldown: spell.cooldown, + castDuration: spell.castDuration, + staminaCost: spell.staminaCost, + manaCost: spell.manaCost ?? manaFromStam(spell.staminaCost), + castEffectId: spell.castEffectId, + travelEffectId: spell.travelEffectId, + impactEffectId: spell.impactEffectId, + attachToHand: true, + weaponId: spell.staffWeaponId, + catalogSkillId: spell.catalogSkillId, + abilityClass: spell.abilityClass, + hint: `${spell.label} · ${spell.pathMode} · MP${spell.manaCost ?? manaFromStam(spell.staminaCost)}/STA${spell.staminaCost} · → ${spell.catalogSkillId}` + }; +} + +/** DRC bar skills (slots 0–3) from kit. */ +export function kitHotbarSkills(page = 0) { + const start = page * 4; + return CASTING_SPELL_KIT.slice(start, start + 4).map(toDrcSkill); +} + +/** + * Prefab bind blob for master-weaponSkills enrichment / WEAPON_SKILLS page. + * @param {CastingSpell} spell + */ +export function spellPrefabBind(spell) { + return { + castingLab: true, + castingSpellId: spell.id, + abilityClass: spell.abilityClass, + pathMode: spell.pathMode, + element: spell.element, + animPack: spell.animPack, + animationClip: spell.castClip, + animRole: spell.animRole, + castEffectId: spell.castEffectId, + travelEffectId: spell.travelEffectId, + impactEffectId: spell.impactEffectId, + staffWeaponId: spell.staffWeaponId, + learnFrom: spell.learnFrom, + assets: spell.assets, + source: 'CastingAbilitiesThreeJS', + liveLab: 'https://casting-abilities-threejs.vercel.app/' + }; +} + +/** + * Export payload for ObjectStore / Warlords import. + */ +export function exportSpellKitJson() { + return { + version: '1.0.0', + generated: new Date().toISOString(), + title: 'Casting lab 10-spell kit → staff / WEAPON_SKILLS', + liveLab: 'https://casting-abilities-threejs.vercel.app/', + weaponSkillsHtml: 'https://info.grudge-studio.com/WEAPON_SKILLS.html', + masterSkills: 'https://info.grudge-studio.com/api/v1/master-weaponSkills.json', + rules: { + animPack: 'magic for all staff spells', + animRole: 'cast', + spaceIsJump: true, + residualIsAttackFrame: true, + orbsNotWholeFireballGlb: true, + pathModes: ['stream', 'aoe', 'spikes', 'wall'] + }, + spells: CASTING_SPELL_KIT.map((s) => ({ + ...s, + prefab: spellPrefabBind(s), + fleet: toDrcSkill(s) + })), + catalogIdMap: Object.fromEntries( + CASTING_SPELL_KIT.map((s) => [s.catalogSkillId, s.id]) + ) + }; +} + +/** + * Pattern for enriching any weapon skill (not only staff): + * lab pack + anim role + VFX from damage type — used by catalog flatten. + */ +export const WEAPON_STYLE_SPELL_PATTERN = Object.freeze({ + spell: { animPack: 'magic', animRole: 'cast', defaultVfx: 'arcane_swirl' }, + melee: { animPack: 'sword_shield', animRole: 'attack', defaultVfx: 'getsuga_slash' }, + ranged: { animPack: 'longbow', animRole: 'attack', defaultVfx: 'getsuga_slash' } +}); diff --git a/src/combat/drcSkills.js b/src/combat/drcSkills.js new file mode 100644 index 0000000..8bb94a4 --- /dev/null +++ b/src/combat/drcSkills.js @@ -0,0 +1,383 @@ +/** + * DRC weapon skills — fleet-shaped (FleetWeaponSkill) for Warlords migration. + * Elements fire/water/earth/wind use Casting path ability + cast/travel/impact VFX. + * Default bar = first 4 of CASTING_SPELL_KIT (10 learnable spells → WEAPON_SKILLS STAFF). + * + * @see castingSpellKit.js · elementWeaponSkills.js · WEAPON_SKILLS.html + */ + +import { ARCANE_WEAPON_SKILLS, CASTING_ELEMENT_PHASE_VFX } from './elementWeaponSkills.js'; +import { kitHotbarSkills, toDrcSkill, CASTING_SPELL_KIT } from './castingSpellKit.js'; +import { + t0ApprenticeWandHotbar, + allT0WandSkills, + setT0WandSlot3, + getT0WandSlot3, + T0_WAND_SLOT3_OPTIONS, + toDrcT0 +} from './t0ApprenticeWand.js'; +import { + equippedWeaponHotbar, + getEquippedWeapon, + ensureWeaponCatalog +} from './equippedWeaponRuntime.js'; +import { + hotbarForWeapon, + T0_STARTER_WEAPON_IDS, + getEquippableWeaponsCache, + loadEquippableWeapons +} from '../api/t0WeaponCatalog.js'; +import { getSkillBinding } from './skillBindings.js'; +import { bindFromCatalogSkill, staffBindFor } from './staffWeaponSkillsBind.js'; + +/** @typedef {'melee'|'spell'|'ranged'} SkillStyle */ + +/** + * @typedef {object} DrcWeaponSkill + * @property {string} id + * @property {string} label + * @property {number} slot 0..3 + * @property {SkillStyle} style + * @property {string} [element] fire|storm|ice|nature|holy|arcane + * @property {string} animRole idle|cast|attack|block + * @property {number} rangeM + * @property {number} cooldown + * @property {number} castDuration + * @property {number} staminaCost + * @property {string} [castEffectId] + * @property {string} [impactEffectId] + * @property {string} [travelEffectId] + * @property {boolean} [attachToHand] + * @property {string} hint + */ + +const F = CASTING_ELEMENT_PHASE_VFX.fire; +const W = CASTING_ELEMENT_PHASE_VFX.ice; +const E = CASTING_ELEMENT_PHASE_VFX.nature; +const A = CASTING_ELEMENT_PHASE_VFX.storm; + +/** + * Melee residual (Getsuga-class) — standard attack frame (F when no interact target). + * Not Space. Profile knobs: settings.residual. + * Open SSOT: meleeStrikeFx.ts · MELEE_SLASH_FX.md + */ +/** @type {DrcWeaponSkill} */ +export const DRC_MELEE_STRIKE = { + id: 'drc_melee_strike', + label: 'Blade Residual', + slot: -1, // F key — not digit bar + style: 'melee', + animRole: 'attack', + rangeM: 3.2, + cooldown: 0.55, + castDuration: 0.45, + staminaCost: 8, + castEffectId: 'getsuga_slash', + impactEffectId: 'getsuga_slash', + attachToHand: true, + weaponId: 'sword', + hint: 'F fallback — attack anim + residual from weapon tip (edit settings.residual)' +}; + +/** + * Default combat bar = kit page 0 (spells 1–4 of 10). + * F = interact / attack. Pages: setSkillKitPage(0|1|2). + */ +/** @type {DrcWeaponSkill[]} */ +export const DRC_WEAPON_SKILLS = kitHotbarSkills(0); + +/** Full 10 as DRC skills (kit browse / bind). */ +export const DRC_SPELL_KIT_ALL = CASTING_SPELL_KIT.map(toDrcSkill); + +/** Purple arcane tree (optional bar: ?arcane=1 or setActiveSkillTree('arcane')). */ +export const DRC_ARCANE_SKILLS = ARCANE_WEAPON_SKILLS; + +/** @deprecated aliases for old element labels in toasts/docs */ +export const DRC_LEGACY_ELEMENT_SKILLS = [ + { + id: 'drc_fire_bolt', + label: 'Fire Bolt', + slot: 0, + style: 'spell', + element: 'fire', + animRole: 'cast', + rangeM: 16, + cooldown: 1.1, + castDuration: 0.85, + staminaCost: 12, + castEffectId: F.cast, + travelEffectId: F.travel, + impactEffectId: F.impact, + attachToHand: true, + weaponId: F.staffWeaponId, + pathMode: 'stream', + abilityElement: 'fire', + catalogSkillId: 'staff_fire_bolt', + hint: 'legacy → casting_fire_bolt' + }, + { + id: 'drc_water_lash', + label: 'Water Lash', + slot: 1, + style: 'spell', + element: 'ice', + animRole: 'cast', + rangeM: 14, + cooldown: 1.2, + castDuration: 0.9, + staminaCost: 12, + castEffectId: W.cast, + travelEffectId: W.travel, + impactEffectId: W.impact, + attachToHand: true, + weaponId: W.staffWeaponId, + pathMode: 'stream', + abilityelement: 'ice', + catalogSkillId: 'staff_frost_bolt', + hint: 'legacy → casting_frost_bolt' + }, + { + id: 'drc_earth_spike', + label: 'Earth Spike', + slot: 2, + style: 'spell', + element: 'nature', + animRole: 'cast', + rangeM: 12, + cooldown: 1.4, + castDuration: 1.0, + staminaCost: 16, + castEffectId: E.cast, + travelEffectId: E.travel, + impactEffectId: E.impact, + attachToHand: true, + weaponId: E.staffWeaponId, + pathMode: 'spikes', + abilityelement: 'nature', + catalogSkillId: 'staff_earthquake', + hint: 'legacy → casting_earth_spike' + }, + { + id: 'drc_wind_tempest', + label: 'Wind Tempest', + slot: 3, + style: 'spell', + element: 'storm', + animRole: 'cast', + rangeM: 14, + cooldown: 2.5, + castDuration: 0.95, + staminaCost: 14, + castEffectId: A.cast, + travelEffectId: A.travel, + impactEffectId: A.impact, + attachToHand: true, + weaponId: A.staffWeaponId, + pathMode: 'stream', + abilityelement: 'storm', + catalogSkillId: 'staff_storm_call', + hint: 'legacy → casting_wind_tempest' + } +]; + +/** Active hotbar: kit | equipped catalog | t0 starters | arcane | legacy */ +let _activeTree = 'kit'; +let _kitPage = 0; +/** @type {DrcWeaponSkill[]} */ +let _kitBar = kitHotbarSkills(0); +/** Slot-3 choice when using catalog starter without full equip */ +let _catalogSlot3 = { + [T0_STARTER_WEAPON_IDS.apprenticeWand]: 't0_wand_frost_spark', + [T0_STARTER_WEAPON_IDS.saplingStaff]: 't0_staff_vine_lash' +}; + +export function setActiveSkillTree(tree) { + if (tree === 'arcane') _activeTree = 'arcane'; + else if (tree === 'legacy' || tree === 'elements') _activeTree = 'legacy'; + else if (tree === 'wand' || tree === 't0_wand' || tree === 'apprentice_wand') + _activeTree = 'wand'; + else if (tree === 'sapling' || tree === 't0_nature' || tree === 'nature_staff') + _activeTree = 'sapling'; + else if (tree === 'equipped' || tree === 'weapon') _activeTree = 'equipped'; + else _activeTree = 'kit'; +} + +/** + * Choose slot-3 for catalog T0 starter trees (wand / sapling). + * @param {'t0-wand'|'t0-nature-staff'|string} weaponId + * @param {string} skillId + */ +export function setCatalogStarterSlot3(weaponId, skillId) { + if (!weaponId || !skillId) return; + _catalogSlot3[weaponId] = skillId; + if (weaponId === T0_STARTER_WEAPON_IDS.apprenticeWand) setT0WandSlot3(skillId); +} + +export function getCatalogStarterSlot3(weaponId) { + return _catalogSlot3[weaponId] || null; +} + +/** Kit page 0 = spells 1–4, 1 = 5–8, 2 = 9–10 (+ pads). */ +export function setSkillKitPage(page = 0) { + _kitPage = Math.max(0, Math.min(2, Number(page) || 0)); + _kitBar = kitHotbarSkills(_kitPage); + _kitBar = _kitBar.map((s, i) => ({ ...s, slot: i })); + if (_activeTree === 'kit') return _kitBar; + return getActiveSkills(); +} + +export function getSkillKitPage() { + return _kitPage; +} + +/** + * Hotbar from live t0-weapons.json (WEAPON_SKILLS.html). + * @param {string} weaponId t0-wand | t0-nature-staff + */ +function hotbarFromCachedStarter(weaponId) { + const equipped = getEquippedWeapon?.(); + if (equipped?.id === weaponId) return equippedWeaponHotbar(); + const cache = getEquippableWeaponsCache(); + if (!cache?.byId) { + void ensureWeaponCatalog?.().catch(() => {}); + return null; + } + const w = cache.byId.get(weaponId); + if (!w) return null; + const s3 = _catalogSlot3[weaponId] || w.defaultSlot3Id; + return hotbarForWeapon(w, s3); +} + +export function getActiveSkills() { + if (_activeTree === 'arcane') return DRC_ARCANE_SKILLS; + if (_activeTree === 'legacy') return DRC_LEGACY_ELEMENT_SKILLS; + if (_activeTree === 'wand') { + // Live catalog first; local t0ApprenticeWand only if cache cold + const bar = hotbarFromCachedStarter(T0_STARTER_WEAPON_IDS.apprenticeWand); + return bar?.length ? bar : t0ApprenticeWandHotbar(); + } + if (_activeTree === 'sapling') { + const bar = hotbarFromCachedStarter(T0_STARTER_WEAPON_IDS.saplingStaff); + // Sapling has no local fork — empty until catalog warms (equip Weapon tab) + return bar?.length ? bar : []; + } + if (_activeTree === 'equipped') { + const bar = equippedWeaponHotbar(); + if (bar.length) return bar; + return _kitBar; + } + return _kitBar; +} + +export function getActiveSkillTree() { + return _activeTree; +} + +export function skillBySlot(slot) { + return getActiveSkills().find((s) => s.slot === slot) || null; +} + +export function skillById(id) { + if (id === DRC_MELEE_STRIKE.id) return DRC_MELEE_STRIKE; + const t0 = allT0WandSkills().find((s) => s.id === id || s.catalogSkillId === id); + if (t0) return toDrcT0(t0); + return ( + DRC_SPELL_KIT_ALL.find((s) => s.id === id) || + DRC_SPELL_KIT_ALL.find((s) => s.catalogSkillId === id) || + DRC_LEGACY_ELEMENT_SKILLS.find((s) => s.id === id) || + DRC_ARCANE_SKILLS.find((s) => s.id === id) || + null + ); +} + +export { + CASTING_SPELL_KIT, + setT0WandSlot3, + getT0WandSlot3, + T0_WAND_SLOT3_OPTIONS, + allT0WandSkills, + t0ApprenticeWandHotbar +}; + +/** + * @deprecated Residual profile only — not the F key product skill. + * F = weapon skill (see skillForFKey). Kept for sword residual VFX profiles. + */ +export function getMeleeStrikeSkill() { + return DRC_MELEE_STRIKE; +} + +/** + * F key → weapon skill (not class ability, not residual default). + * + * Priority: + * 1. Showcase / saved bind `f` + * 2. Equipped weapon hotbar slot 0 (starter attack / primary) + * 3. Active tree slot 0 + * + * Class abilities stay deferred — strengthen weapon skill + prefab pattern first. + * @returns {DrcWeaponSkill|null} + */ +export function skillForFKey() { + // 1) Showcase / saved F bind → catalog skill as weapon skill + const bind = getSkillBinding('f'); + if (bind?.skillId) { + const fromId = skillById(bind.skillId); + const staffB = + bindFromCatalogSkill({ + id: bind.skillId, + name: bind.name, + damageType: bind.damageType, + cooldown: bind.cooldown + }) || staffBindFor(bind.skillId); + if (fromId || staffB) { + return { + ...(fromId || {}), + id: bind.skillId, + label: bind.name || fromId?.label || staffB?.name || bind.skillId, + slot: -1, + hotkey: 'f', + isWeaponPrimary: true, + style: fromId?.style || (bind.labPack === 'magic' ? 'spell' : 'spell'), + element: fromId?.element || staffB?.element, + abilityElement: fromId?.abilityElement || staffB?.element, + castDuration: fromId?.castDuration || staffB?.castDuration || 0.5, + cooldown: bind.cooldown ?? fromId?.cooldown ?? staffB?.cooldown ?? 1, + animRole: fromId?.animRole || 'cast', + rangeM: fromId?.rangeM || staffB?.rangeM || 14, + castEffectId: fromId?.castEffectId || staffB?.castEffectId, + travelEffectId: fromId?.travelEffectId || staffB?.travelEffectId, + impactEffectId: fromId?.impactEffectId || staffB?.impactEffectId, + pathMode: fromId?.pathMode || staffB?.pathMode || 'stream', + catalogSkillId: bind.skillId, + staminaCost: fromId?.staminaCost ?? 10, + manaCost: fromId?.manaCost, + weaponTypeId: bind.weaponTypeId + }; + } + } + // 2) Equipped weapon primary (slot 0) — T0 starter attack / weapon prefab + const equipped = equippedWeaponHotbar(); + if (equipped?.length) { + const primary = equipped.find((s) => s.slot === 0) || equipped[0]; + if (primary) return { ...primary, slot: -1, hotkey: 'f', isWeaponPrimary: true }; + } + // 3) Active tree first skill (kit page weapon skills) + const bar = getActiveSkills(); + const s0 = bar.find((s) => s.slot === 0) || bar[0]; + if (s0) return { ...s0, slot: -1, hotkey: 'f', isWeaponPrimary: true }; + return null; +} + +/** + * Minimal readiness check (fleet assessWeaponSkillReadiness analogue). + * @param {DrcWeaponSkill} skill + */ +export function assessSkill(skill) { + const missing = []; + if (!skill?.id) missing.push('id'); + if (skill.style === 'spell' && !skill.element) missing.push('element'); + if (!skill.animRole) missing.push('animRole'); + if (!(skill.cooldown >= 0)) missing.push('cooldown'); + return { ok: missing.length === 0, missing }; +} diff --git a/src/combat/elementPresentation.js b/src/combat/elementPresentation.js new file mode 100644 index 0000000..4759bb0 --- /dev/null +++ b/src/combat/elementPresentation.js @@ -0,0 +1,161 @@ +/** + * Creative element presentation recipes — how product ELEMENTS reuse + * existing Ability pools + VfxDirector primitives (no second engine). + * + * Ideas encoded here (ship iteratively): + * - Fire volley of micro projectiles + sky meteor shards + * - Ice ground-flood (earth move timing) then erupt swallow + * - Nature green earth + vine lashes + heal aura + * - Storm: narrow fast chain lightning + wind residual; shield on wall + * - Arcane purple + void black + * - Holy radiance beams + * - Cross-shader spikes (earth motion + water/fire/air beauty) + * + * Knobs: settings.presentation · deploy: VfxDirector.deployPresentation + * @see docs/ELEMENT_PRESENTATION_SSOT.md + */ + +import { CASTING_ELEMENT_PHASE_VFX, normalizeElement } from './elementWeaponSkills.js'; + +/** + * @typedef {'volley'|'meteor'|'groundFlood'|'vineLash'|'shieldAura'|'lightning'|'voidBolt'|'radiance'|'hybridSpike'} PresentationStyle + */ + +/** + * @typedef {object} ElementPresentation + * @property {string} element + * @property {PresentationStyle} style + * @property {string} abilityKey fire|water|earth|wind + * @property {number} color primary + * @property {number} [colorB] secondary (void black, leaf green, …) + * @property {string} castEffectId + * @property {string} travelEffectId + * @property {string} impactEffectId + * @property {boolean} [multiShot] + * @property {boolean} [meteor] + * @property {boolean} [groundFlood] + * @property {boolean} [shield] + * @property {boolean} [healAura] + * @property {boolean} [microFirst] + * @property {boolean} [lightning] + * @property {boolean} [chain] + * @property {string} learn note for agents + */ + +/** @type {Record} */ +export const ELEMENT_PRESENTATION = { + fire: { + element: 'fire', + style: 'volley', + abilityKey: 'fire', + color: 0xff6a1e, + colorB: 0xffc14a, + castEffectId: 'fire_hand', + travelEffectId: 'fireball', + impactEffectId: 'inferno', + multiShot: true, + meteor: true, + microFirst: true, + learn: + 'Volley of small fireballs (cheap) + optional sky meteor shards; not one fat volume only' + }, + storm: { + element: 'storm', + style: 'lightning', + abilityKey: 'wind', + color: 0x9fdcff, + colorB: 0xeef9ff, + castEffectId: 'arcane_swirl', + travelEffectId: 'chain_lightning', + impactEffectId: 'ice_lightning_burst', + lightning: true, + chain: true, + shield: true, // wall / guard only — see deployPresentation pathKind + learn: + 'Narrow fast electric bolts + chain hops; wind silk residual; WindAbility path; shield on wall' + }, + ice: { + element: 'ice', + style: 'groundFlood', + abilityKey: 'water', + color: 0x5fd6ff, + colorB: 0xb8ecff, + castEffectId: 'arcane_swirl', + travelEffectId: 'moon_beam', + impactEffectId: 'frost_wave', + groundFlood: true, + learn: + 'WaterAbility on ground like earth pave, then erupt / frost_wave swallow + knockback feel' + }, + nature: { + element: 'nature', + style: 'vineLash', + abilityKey: 'earth', + color: 0x4ecf6a, + colorB: 0x2d6b3a, + castEffectId: 'earth_surge', + travelEffectId: 'earth_surge', + impactEffectId: 'earth_surge', + healAura: true, + learn: + 'Catalog nature school: EarthAbility + RockMaterial (green). Vine lash / earth_surge. No invented trap skills.' + }, + holy: { + element: 'holy', + style: 'radiance', + abilityKey: 'wind', + color: 0xffe08a, + colorB: 0xfff6d8, + castEffectId: 'moon_beam', + travelEffectId: 'moon_beam', + impactEffectId: 'moon_beam', + healAura: true, + learn: 'Moon beam radiance + soft heal aura ring' + }, + arcane: { + element: 'arcane', + style: 'voidBolt', + abilityKey: 'wind', + color: 0xb070ff, + colorB: 0x1a0a28, + castEffectId: 'arcane_swirl', + travelEffectId: 'chain_lightning', + impactEffectId: 'inferno', + multiShot: true, + microFirst: true, + learn: 'Purple + void-black textures; micro first bullet then larger void bolts' + } +}; + +/** + * Hybrid spike beauty: earth motion + optional water/fire/air overlay colors. + * Used when pathCast kind === spikes. + */ +export const HYBRID_SPIKE_OVERLAY = { + fire: { beauty: 'inferno', color: 0xff6a1e, note: 'magma spikes' }, + ice: { beauty: 'frost_wave', color: 0x5fd6ff, note: 'ice spears' }, + storm: { beauty: 'ice_lightning_burst', color: 0x9fdcff, note: 'air-cut spikes' }, + nature: { beauty: 'earth_surge', color: 0x4ecf6a, note: 'vine spears' }, + holy: { beauty: 'moon_beam', color: 0xffe08a, note: 'light pillars' }, + arcane: { beauty: 'arcane_swirl', color: 0xb070ff, note: 'void spines' } +}; + +/** @param {string} element */ +export function presentationFor(element) { + const el = normalizeElement(element); + return ELEMENT_PRESENTATION[el] || ELEMENT_PRESENTATION.fire; +} + +/** Phase VFX + presentation merged for skill fire. */ +export function presentationPoseVfx(element) { + const el = normalizeElement(element); + const phase = CASTING_ELEMENT_PHASE_VFX[el] || CASTING_ELEMENT_PHASE_VFX.fire; + const pres = presentationFor(el); + return { + ...phase, + ...pres, + cast: pres.castEffectId || phase.cast, + travel: pres.travelEffectId || phase.travel, + impact: pres.impactEffectId || phase.impact + }; +} diff --git a/src/combat/elementWeaponSkills.js b/src/combat/elementWeaponSkills.js new file mode 100644 index 0000000..a303379 --- /dev/null +++ b/src/combat/elementWeaponSkills.js @@ -0,0 +1,340 @@ +/** + * Element → Warlords staff skill migration (mirrors gameopen castingElementSkills). + * Keep effect ids in sync with vfxCatalog + fleet FleetWeaponSkill cast/travel/impact. + * + * Product ELEMENTS (settings.ELEMENTS): + * fire · storm · ice · nature · holy · arcane + * + * Path-cast ability pools (AbilityManager): + * fire→FireAbility · ice→WaterAbility · nature→EarthAbility + * storm|holy|arcane→WindAbility + * + * HUD labels: ELEMENT_META in settings.js + */ + +import { ELEMENT_META, ELEMENTS, abilityKeyForElement } from '../config/settings.js'; + +/** @typedef {'fire'|'storm'|'ice'|'nature'|'holy'|'arcane'} CastingElement */ + +/** + * @typedef {object} ElementPhaseVfx + * @property {CastingElement} element + * @property {string} cast + * @property {string} travel + * @property {string} impact + * @property {number} color + * @property {string} staffWeaponId + * @property {string} staffLabel + * @property {string} castClip + * @property {string} abilityKey fire|water|earth|wind pool + */ + +/** @type {Record} */ +export const CASTING_ELEMENT_PHASE_VFX = { + fire: { + element: 'fire', + cast: 'fire_hand', + travel: 'fireball', + impact: 'inferno', + color: 0xff6a1e, + staffWeaponId: ELEMENT_META.fire?.staffWeaponId || 'staffFire', + staffLabel: ELEMENT_META.fire?.staffLabel || 'Fire Staff', + castClip: 'magic/standing 1h cast spell 01', + abilityKey: 'fire', + /** Individual gd_orbs projectile for staff normal (slot 1) */ + projectileOrb: 'orb-fire', + projectileMesh: './models/vfx/orbs/orb-fire.glb', + chargeMesh: './models/vfx/charge/staff-charge.glb' + }, + storm: { + element: 'storm', + cast: 'arcane_swirl', + travel: 'chain_lightning', + impact: 'ice_lightning_burst', + color: 0x9fdcff, + staffWeaponId: ELEMENT_META.storm?.staffWeaponId || 'staffStorm', + staffLabel: ELEMENT_META.storm?.staffLabel || 'Storm Staff', + castClip: 'magic/standing 1h cast spell 01', + abilityKey: 'wind', + projectileOrb: 'orb-storm', + projectileMesh: './models/vfx/orbs/orb-storm.glb', + chargeMesh: './models/vfx/charge/staff-charge.glb' + }, + ice: { + element: 'ice', + cast: 'arcane_swirl', + travel: 'moon_beam', + impact: 'frost_wave', + color: 0x5fd6ff, + staffWeaponId: ELEMENT_META.ice?.staffWeaponId || 'staffIce', + staffLabel: ELEMENT_META.ice?.staffLabel || 'Ice Staff', + castClip: 'magic/standing 1h cast spell 01', + abilityKey: 'water', + projectileOrb: 'orb-ice', + projectileMesh: './models/vfx/orbs/orb-ice.glb', + chargeMesh: './models/vfx/charge/staff-charge.glb' + }, + nature: { + element: 'nature', + cast: 'earth_surge', + travel: 'earth_surge', + impact: 'earth_surge', + color: 0x6bbf4a, + staffWeaponId: ELEMENT_META.nature?.staffWeaponId || 'staffNature', + staffLabel: ELEMENT_META.nature?.staffLabel || 'Nature Staff', + castClip: 'magic/standing 1h cast spell 01', + abilityKey: 'earth', + projectileOrb: 'orb-nature', + projectileMesh: './models/vfx/orbs/orb-nature.glb', + chargeMesh: './models/vfx/charge/staff-charge.glb' + }, + holy: { + element: 'holy', + cast: 'arcane_swirl', + travel: 'moon_beam', + impact: 'moon_beam', + color: 0xffe08a, + staffWeaponId: ELEMENT_META.holy?.staffWeaponId || 'staffHoly', + staffLabel: ELEMENT_META.holy?.staffLabel || 'Holy Staff', + castClip: 'magic/standing 1h cast spell 01', + abilityKey: 'wind', + projectileOrb: 'orb-holy', + projectileMesh: './models/vfx/orbs/orb-holy.glb', + chargeMesh: './models/vfx/charge/staff-charge.glb' + }, + arcane: { + element: 'arcane', + cast: 'arcane_swirl', + travel: 'chain_lightning', + impact: 'inferno', + color: 0xb070ff, + staffWeaponId: ELEMENT_META.arcane?.staffWeaponId || 'staffArcane', + staffLabel: ELEMENT_META.arcane?.staffLabel || 'Arcane Staff', + castClip: 'magic/standing 1h cast spell 01', + abilityKey: 'wind', + projectileOrb: 'orb-arcane', + projectileMesh: './models/vfx/orbs/orb-arcane.glb', + chargeMesh: './models/vfx/charge/staff-charge.glb' + } +}; + +/** Legacy product aliases → canonical CastingElement */ +const LEGACY_ELEMENT = { + water: 'ice', + frost: 'ice', + earth: 'nature', + wind: 'storm', + lightning: 'storm' +}; + +/** @param {string} element */ +export function normalizeElement(element) { + if (!element) return 'fire'; + if (CASTING_ELEMENT_PHASE_VFX[element]) return element; + return LEGACY_ELEMENT[element] || element; +} + +/** Element id → staff weapon id (equip / catalog). */ +export function staffWeaponIdForElement(element) { + const el = normalizeElement(element); + return CASTING_ELEMENT_PHASE_VFX[el]?.staffWeaponId || null; +} + +/** + * Arcane skill tree (purple + explosive + storm-like) for Warlords staff. + * Slots 0–3 = DRC / fleet hotbar. + */ +export const ARCANE_WEAPON_SKILLS = [ + { + id: 'arcane_bolt', + weaponId: 'staffArcane', + slot: 0, + label: 'Arcane Bolt', + style: 'spell', + element: 'arcane', + animRole: 'cast', + rangeM: 14, + cooldown: 1.0, + castDuration: 0.85, + staminaCost: 10, + castEffectId: 'arcane_swirl', + travelEffectId: 'chain_lightning', + impactEffectId: 'arcane_swirl', + attachToHand: true, + hint: '1 — purple bolt + swirl' + }, + { + id: 'arcane_gale', + weaponId: 'staffArcane', + slot: 1, + label: 'Arcane Gale', + style: 'spell', + element: 'arcane', + animRole: 'cast', + rangeM: 12, + cooldown: 3.0, + castDuration: 0.95, + staminaCost: 14, + castEffectId: 'arcane_swirl', + travelEffectId: 'chain_lightning', + impactEffectId: 'ice_lightning_burst', + attachToHand: true, + hint: '2 — storm + purple burst' + }, + { + id: 'void_burst', + weaponId: 'staffArcane', + slot: 2, + label: 'Void Burst', + style: 'spell', + element: 'arcane', + animRole: 'cast', + rangeM: 10, + cooldown: 6.0, + castDuration: 1.05, + staminaCost: 18, + castEffectId: 'arcane_swirl', + travelEffectId: 'fireball', + impactEffectId: 'inferno', + attachToHand: true, + hint: '3 — purple explosive' + }, + { + id: 'storm_arcane', + weaponId: 'staffArcane', + slot: 3, + label: 'Storm Arcane', + style: 'spell', + element: 'arcane', + animRole: 'cast', + rangeM: 16, + cooldown: 12.0, + castDuration: 1.15, + staminaCost: 24, + castEffectId: 'chain_lightning', + travelEffectId: 'chain_lightning', + impactEffectId: 'inferno', + attachToHand: true, + hint: '4 — storm + detonation' + } +]; + +/** Holy skill tree — light bolts / beams. */ +export const HOLY_WEAPON_SKILLS = [ + { + id: 'holy_bolt', + weaponId: 'staffHoly', + slot: 0, + label: 'Holy Bolt', + style: 'spell', + element: 'holy', + animRole: 'cast', + rangeM: 14, + cooldown: 1.0, + castDuration: 0.85, + staminaCost: 10, + castEffectId: 'arcane_swirl', + travelEffectId: 'moon_beam', + impactEffectId: 'moon_beam', + attachToHand: true, + hint: '1 — holy bolt' + }, + { + id: 'holy_radiance', + weaponId: 'staffHoly', + slot: 1, + label: 'Radiance', + style: 'spell', + element: 'holy', + animRole: 'cast', + rangeM: 10, + cooldown: 3.5, + castDuration: 0.95, + staminaCost: 14, + castEffectId: 'moon_beam', + travelEffectId: 'moon_beam', + impactEffectId: 'moon_beam', + attachToHand: true, + hint: '2 — radiance AOE' + }, + { + id: 'holy_smite', + weaponId: 'staffHoly', + slot: 2, + label: 'Smite', + style: 'spell', + element: 'holy', + animRole: 'cast', + rangeM: 12, + cooldown: 6.0, + castDuration: 1.05, + staminaCost: 18, + castEffectId: 'arcane_swirl', + travelEffectId: 'moon_beam', + impactEffectId: 'ice_lightning_burst', + attachToHand: true, + hint: '3 — smite' + }, + { + id: 'holy_judgment', + weaponId: 'staffHoly', + slot: 3, + label: 'Judgment', + style: 'spell', + element: 'holy', + animRole: 'cast', + rangeM: 16, + cooldown: 12.0, + castDuration: 1.15, + staminaCost: 22, + castEffectId: 'moon_beam', + travelEffectId: 'moon_beam', + impactEffectId: 'inferno', + attachToHand: true, + hint: '4 — judgment' + } +]; + +/** Element hotbar (4 skills) for a Casting element → Warlords staff kit. */ +export function elementHotbarSkills(element) { + const el = normalizeElement(element); + const phase = CASTING_ELEMENT_PHASE_VFX[el] || CASTING_ELEMENT_PHASE_VFX.arcane; + if (el === 'arcane') return ARCANE_WEAPON_SKILLS.slice(); + if (el === 'holy') return HOLY_WEAPON_SKILLS.slice(); + const labels = { + fire: ['Fire Bolt', 'Flame Wave', 'Meteor Path', 'Inferno'], + storm: ['Lightning Bolt', 'Chain Lightning', 'Storm Shield', 'Tempest'], + ice: ['Ice Lash', 'Frost Wave', 'Moon Beam', 'Blizzard Shell'], + nature: ['Vine Spike', 'Quake Surge', 'Stone Path', "Nature's Fury"] + }; + const names = labels[el] || labels.fire; + return names.map((label, slot) => ({ + id: `${el}_skill_${slot}`, + weaponId: phase.staffWeaponId, + slot, + label, + style: 'spell', + element: el, + animRole: 'cast', + rangeM: [14, 10, 16, 8][slot], + cooldown: [1.1, 3.0, 6.0, 12.0][slot], + castDuration: 0.85 + slot * 0.1, + staminaCost: 12 + slot * 4, + castEffectId: phase.cast, + travelEffectId: phase.travel, + impactEffectId: phase.impact, + attachToHand: true, + abilityElement: abilityKeyForElement(el), + hint: `${slot + 1} — ${label}` + })); +} + +/** All product element trees for export / Warlords seed. */ +export function allElementWeaponSkillTrees() { + /** @type {Record>} */ + const out = {}; + for (const el of ELEMENTS) { + out[el] = elementHotbarSkills(el); + } + return out; +} diff --git a/src/combat/elementalLinearCast.js b/src/combat/elementalLinearCast.js new file mode 100644 index 0000000..02e9677 --- /dev/null +++ b/src/combat/elementalLinearCast.js @@ -0,0 +1,305 @@ +/** + * Elemental skills × linear spell casting — one learning map (Casting lab). + * + * Merges what we learned from LinearAbilityCastingThreeJS with staff elemental + * skills so DRC digit/F/LMB release can drive: + * 1. Linear skillshots (line/zone, constant m/s, MOBA aim, procedural FX) + * 2. Path-cast Fire/Water/Earth/Wind Ability pools (drawn stroke) + * 3. Mesh delivery (orbs, rocks, freeze nova, bubbles, arrows) + * + * Does **not** invent skill rows or a third AbilityManager. + * + * @see docs/ELEMENTAL_LINEAR_CAST_SSOT.md + * @see docs/LINEAR_SKILLSHOT_SSOT.md + * @see src/skillshot/LinearSkillBridge.js + * @see src/combat/elementWeaponSkills.js + * @see src/vfx/elementAttackVfx.js + */ + +import { PRODUCT_TO_LINEAR, CastShape, castShapeOf } from '../skillshot/LinearSkillBridge.js'; +import { CASTING_ELEMENT_PHASE_VFX, normalizeElement } from './elementWeaponSkills.js'; +import { inferElementAttackKind } from '../vfx/elementAttackVfx.js'; +import { isStaffNormalAttack } from '../vfx/staffOrbVfx.js'; + +/** + * Product element → linear skillshot id (from LinearAbilityCasting learning). + * ice→ice LINE · storm→thunder LINE · fire→meteor LINE · holy→beam LINE + * arcane→snare ZONE · nature→glacier ZONE + */ +export { PRODUCT_TO_LINEAR }; + +/** + * Learned cast layers — which systems fire for a skill release. + * @typedef {'linear_line'|'linear_zone'|'path_ability'|'mesh_projectile'|'freeze_nova'|'earth_rocks'|'water_bubbles'|'arrow_path'|'arrow_loft'|'buff'} CastLayer + */ + +/** + * @typedef {object} ElementalLinearPlan + * @property {string} element product element + * @property {string|null} linearId ice|thunder|meteor|beam|snare|glacier + * @property {'line'|'zone'|null} linearShape + * @property {CastLayer[]} layers ordered deploy layers + * @property {boolean} useLinear fire LinearSkillBridge.castToward + * @property {boolean} usePathAbility Fire/Water/Earth/Wind Ability curve + * @property {boolean} useMeshDelivery SkillProjectileSystem (orbs/rocks/…) + * @property {string|null} variantHint effectVariants id + * @property {number} intensity 0.25..2 + * @property {object|null} meshKind from inferElementAttackKind + * @property {string} learn short agent note + */ + +/** + * Decide cast plan from catalog skill + combat context. + * Focus combat stream primaries → linear line/zone + mesh. + * Path stroke / wall / spikes → path Ability primary. + * Freeze / earth multi → mesh kinds learned in elementAttackVfx. + * + * @param {object} skill + * @param {{ focusCombat?: boolean, pathDrawn?: boolean, intensity?: number }} [ctx] + * @returns {ElementalLinearPlan} + */ +export function planElementalLinearCast(skill, ctx = {}) { + const el = normalizeElement(skill?.element || skill?.abilityElement || 'arcane'); + const phase = CASTING_ELEMENT_PHASE_VFX[el] || CASTING_ELEMENT_PHASE_VFX.arcane; + const linearId = PRODUCT_TO_LINEAR[el] || null; + const linearShape = linearId + ? castShapeOf(linearId) === CastShape.ZONE + ? 'zone' + : 'line' + : null; + + const meshKind = inferElementAttackKind(skill); + const pathMode = skill?.pathMode || 'stream'; + const blob = `${skill?.id || ''} ${skill?.label || ''} ${skill?.catalogSkillId || ''}`.toLowerCase(); + const intensity = Math.max(0.25, Math.min(2, ctx.intensity ?? 1)); + + const layers = /** @type {CastLayer[]} */ ([]); + + // Buffs never fire travel systems + if (skill?.isFocus || skill?.isWard || skill?.skillKind === 'buff') { + return { + element: el, + linearId: null, + linearShape: null, + layers: ['buff'], + useLinear: false, + usePathAbility: false, + useMeshDelivery: false, + variantHint: null, + intensity, + meshKind: null, + learn: 'Buff / ward — cast tell only' + }; + } + + // Explicit mesh kinds first (freeze, multi rock, arrow) + if (meshKind?.kind === 'freeze_nova') { + layers.push('freeze_nova'); + return pack(el, linearId, linearShape, layers, { + useLinear: false, + usePathAbility: pathMode === 'aoe' || pathMode === 'spikes', + useMeshDelivery: true, + variantHint: 'aoe_frost', + intensity, + meshKind, + learn: 'Freeze nova from self — mesh expand + frost VFX; path Ability optional flood' + }); + } + if (meshKind?.kind === 'earth_rocks') { + layers.push('earth_rocks'); + // Glacier zone for heavy nature aoe; linear glacier for stream optional + const heavy = (meshKind.rockCount || 1) >= 3; + if (heavy && linearId === 'glacier') { + layers.push('linear_zone'); + } + return pack(el, linearId, linearShape, layers, { + useLinear: heavy && linearId === 'glacier', + usePathAbility: pathMode === 'spikes' || pathMode === 'wall', + useMeshDelivery: true, + variantHint: heavy ? 'aoe_glacier' : null, + intensity, + meshKind, + learn: 'Earth rocks emerge + fly; multi/quake can add glacier zone' + }); + } + if (meshKind?.kind === 'arrow_path' || meshKind?.kind === 'arrow_loft') { + layers.push(meshKind.kind); + return pack(el, linearId, linearShape, layers, { + useLinear: false, + usePathAbility: false, + useMeshDelivery: true, + variantHint: null, + intensity, + meshKind, + learn: 'Arrow systems — path distance end events or loft throw/trap/summon' + }); + } + if (meshKind?.kind === 'water_bubbles') { + layers.push('water_bubbles'); + } + + // Drawn path stroke → path Ability is primary (staffCast learning) + if (ctx.pathDrawn) { + layers.push('path_ability'); + if (isStaffNormalAttack(skill) || pathMode === 'stream') layers.push('mesh_projectile'); + return pack(el, linearId, linearShape, layers, { + useLinear: false, + usePathAbility: true, + useMeshDelivery: true, + variantHint: null, + intensity, + meshKind, + learn: 'Path stroke → Fire/Water/Earth/Wind Ability + mesh travel' + }); + } + + // Focus combat / digit skills: linear skillshot for stream/line elements + const wantLinear = + ctx.focusCombat !== false && + (pathMode === 'stream' || + skill?.slotType === 'primary' || + skill?.isWeaponPrimary || + isStaffNormalAttack(skill) || + /bolt|spark|ping|practice|beam|chain|tempest|inferno|meteor/.test(blob)); + + // Zone elements (snare/glacier) for aoe pathMode + const wantZone = + linearShape === 'zone' && + (pathMode === 'aoe' || /nova|zone|snare|glacier|field/.test(blob)); + + if (wantZone && linearId) { + layers.push('linear_zone'); + } else if (wantLinear && linearId && linearShape === 'line') { + layers.push('linear_line'); + } else if (wantLinear && linearId && linearShape === 'zone' && pathMode === 'stream') { + // nature primary: rocks mesh + optional light glacier + layers.push('mesh_projectile'); + } + + // Always keep path Ability beauty for elemental presentation (volley/flood/…) + // except pure mesh-only arrows + if (pathMode === 'wall' || pathMode === 'spikes' || pathMode === 'aoe') { + layers.push('path_ability'); + } else if (!layers.includes('linear_line') && !layers.includes('linear_zone')) { + layers.push('path_ability'); + } else { + // Linear primary + soft path curve for continuity with staff presentation + layers.push('path_ability'); + } + + // Staff normal / stream → mesh orbs (or rocks/bubbles already queued) + if ( + !layers.includes('earth_rocks') && + !layers.includes('freeze_nova') && + (isStaffNormalAttack(skill) || pathMode === 'stream' || layers.includes('water_bubbles')) + ) { + layers.push('mesh_projectile'); + } + + // Dedup layers + const uniq = [...new Set(layers)]; + const useLinear = uniq.some((l) => l === 'linear_line' || l === 'linear_zone'); + + return pack(el, linearId, linearShape, uniq, { + useLinear, + usePathAbility: uniq.includes('path_ability'), + useMeshDelivery: + uniq.includes('mesh_projectile') || + uniq.includes('water_bubbles') || + uniq.includes('earth_rocks') || + uniq.includes('freeze_nova'), + variantHint: variantForElement(el, pathMode), + intensity, + meshKind, + learn: `Element ${el} → linear ${linearId || '—'} (${linearShape || 'none'}) + path Ability + mesh; phase VFX ${phase.cast}/${phase.travel}/${phase.impact}` + }); +} + +/** + * @param {string} el + * @param {string} pathMode + */ +function variantForElement(el, pathMode) { + if (el === 'storm') return pathMode === 'aoe' ? 'arc_storm' : 'arc_bolt'; + if (el === 'fire') return pathMode === 'aoe' || pathMode === 'stream' ? null : null; + if (el === 'ice') return pathMode === 'aoe' ? 'aoe_frost' : null; + if (el === 'arcane') return pathMode === 'aoe' ? 'aoe_snare' : null; + if (el === 'nature') return pathMode === 'aoe' ? 'aoe_glacier' : null; + return null; +} + +function pack(el, linearId, linearShape, layers, rest) { + return { + element: el, + linearId, + linearShape, + layers, + ...rest + }; +} + +/** + * Fire planned linear skillshot from combat pose (no re-arm MOBA UI). + * Skips linear CD gate for weapon-skill driven casts (weapon CD already spent). + * + * @param {import('../skillshot/LinearSkillBridge.js').LinearSkillBridge|null} linear + * @param {ElementalLinearPlan} plan + * @param {{ origin: import('three').Vector3, aim: import('three').Vector3, feet?: import('three').Vector3 }} pose + * @returns {boolean} + */ +export function fireLinearFromPlan(linear, plan, pose) { + if (!linear || !plan?.useLinear || !plan.linearId) return false; + const feet = pose.feet || pose.origin; + const aim = pose.aim; + if (!feet || !aim) return false; + + linear.applyIntensity?.(plan.intensity); + if (plan.variantHint) { + try { + linear.applyVariant?.(plan.variantHint, plan.linearId); + } catch { + /* optional */ + } + } + + // Bypass arm UI — combat skill already committed cast + // Temporarily clear CD so digit skills aren't double-blocked by linear pool + const prevCd = linear.cooldowns?.get?.(plan.linearId) ?? 0; + linear.cooldowns?.set?.(plan.linearId, 0); + linear.select(plan.linearId); + const ab = linear.castToward(feet, aim, plan.linearId); + // Restore a short cosmetic CD so Alt+Shift spam still rates + if (prevCd > 0) linear.cooldowns?.set?.(plan.linearId, Math.min(prevCd, 0.35)); + return !!ab; +} + +/** + * Agent learning summary table (runtime-readable). + */ +export const ELEMENTAL_LINEAR_LEARNED = Object.freeze({ + practices: [ + 'settings SSOT sampled every frame (linearSettings + settings.js)', + 'pooled abilities, no alloc on cast', + 'phase machine travel → impact → fade', + 'constant m/s line front', + 'MOBA line/zone aim (Alt+Shift arm) + combat castToward', + 'procedural geometry + GLSL materials', + 'ground marks + fissures', + 'element → PRODUCT_TO_LINEAR map', + 'staff normal slot1 + focus LMB shared', + 'orbs / rocks / freeze / arrows as mesh layers' + ], + elementMap: { ...PRODUCT_TO_LINEAR }, + layers: [ + 'linear_line', + 'linear_zone', + 'path_ability', + 'mesh_projectile', + 'freeze_nova', + 'earth_rocks', + 'water_bubbles', + 'arrow_path', + 'arrow_loft', + 'buff' + ] +}); diff --git a/src/combat/equippedWeaponRuntime.js b/src/combat/equippedWeaponRuntime.js new file mode 100644 index 0000000..3fafffe --- /dev/null +++ b/src/combat/equippedWeaponRuntime.js @@ -0,0 +1,387 @@ +/** + * Equipped weapon runtime — skills + assets follow the item, not a free skill tree. + * + * Dual loadout (paperdoll): + * - Set A: mainHand + offHand + * - Set B: weapon2 + offHand2 + * Combat **Q** (tap) swaps active set → mesh, anim pack, hotbar skills. + * + * Equip t0-sword / t0-wand / … → anim pack · mesh slot · 3-slot hotbar · icon · 3D attach + */ + +import { + loadEquippableWeapons, + hotbarForWeapon, + exportWarlordsWeaponPrefab, + warmProductionOverrides, + clearEquippableWeaponsCache +} from '../api/t0WeaponCatalog.js'; +import { attachWeaponModel, clearWeaponAttach } from '../character/WeaponMeshAttach.js'; +import { loadEquipMap, saveEquipMap } from '../ui/mainPanelSlots.js'; +import { normalizeHoldKind } from '../character/weaponHoldPose.js'; + +/** @type {import('../api/t0WeaponCatalog.js').EquippableWeapon|null} */ +let _equipped = null; +/** @type {string|null} */ +let _slot3Id = null; +/** @type {import('three').Object3D|null} */ +let _attach = null; +/** @type {Awaited>|null} */ +let _catalog = null; + +/** + * Active weapon set: 0 = Weapon 1 (mainHand) · 1 = Weapon 2 (weapon2) + * @type {0|1} + */ +let _loadoutIndex = 0; + +/** Per-set slot3 skill choice */ +let _slot3BySet = /** @type {Record} */ ({ 0: null, 1: null }); + +const LS_LOADOUT = 'casting.weaponLoadoutIndex.v1'; + +export function getEquippedWeapon() { + return _equipped; +} + +export function getActiveLoadoutIndex() { + return _loadoutIndex; +} + +export function getEquippedSlot3Id() { + return ( + _slot3BySet[_loadoutIndex] || + _slot3Id || + _equipped?.defaultSlot3Id || + null + ); +} + +/** + * Paperdoll slot id for active set main hand. + * @param {0|1} [idx] + */ +export function mainSlotForLoadout(idx = _loadoutIndex) { + return idx === 1 ? 'weapon2' : 'mainHand'; +} + +/** + * @param {0|1} [idx] + */ +export function offSlotForLoadout(idx = _loadoutIndex) { + return idx === 1 ? 'offHand2' : 'offHand'; +} + +export async function ensureWeaponCatalog() { + if (_catalog) return _catalog; + _catalog = await loadEquippableWeapons(); + // DO merge already runs inside loadEquippableWeapons (t0 + remote equip mirror) + return _catalog; +} + +/** + * Force re-load catalog + re-merge Cloudflare DO equipWeaponById mirror. + * Used after Casting promote / Multiverse push. + */ +export async function refreshWeaponCatalogFromDo() { + clearEquippableWeaponsCache(); + _catalog = null; + _catalog = await loadEquippableWeapons(); + return _catalog; +} + +export function listEquippableWeapons() { + return _catalog?.weapons || []; +} + +/** + * Hotbar for currently equipped weapon (used by drcSkills tree `equipped`). + */ +export function equippedWeaponHotbar() { + if (!_equipped) return []; + return hotbarForWeapon(_equipped, getEquippedSlot3Id()); +} + +/** + * Choose slot-3 skill on equipped weapon. + * @param {string} skillId + */ +export function setEquippedSlot3(skillId) { + if (!_equipped) return null; + const ok = _equipped.slot3Options.some((s) => s.id === skillId); + if (ok) { + _slot3Id = skillId; + _slot3BySet[_loadoutIndex] = skillId; + } + return getEquippedSlot3Id(); +} + +/** + * Resolve paperdoll weapon item id for a set index. + * @param {0|1} idx + */ +export function getLoadoutWeaponId(idx) { + const map = loadEquipMap(); + const mainId = map[mainSlotForLoadout(idx)]?.id || null; + return mainId; +} + +/** + * Swap active weapon set (0 ↔ 1). Re-applies mesh, anim pack, skills. + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * onToast?: (s: string) => void, + * onSkills?: () => void + * }} ctx + * @param {0|1} [forceIndex] optional force set + */ +export async function swapWeaponLoadout(ctx, forceIndex) { + const character = ctx.character; + const toast = ctx.onToast || (() => {}); + const map = loadEquipMap(); + const a = map.mainHand?.id; + const b = map.weapon2?.id; + if (!a && !b) { + toast('No dual weapons — equip Weapon 1 and Weapon 2'); + return { ok: false, index: _loadoutIndex }; + } + // Only one weapon → stay / equip that one + if (a && !b) { + await equipWeaponById(a, ctx); + _loadoutIndex = 0; + localStorage.setItem(LS_LOADOUT, '0'); + ctx.onSkills?.(); + toast(`Weapon 1 · ${_equipped?.name || a}`); + return { ok: true, index: 0, weapon: _equipped }; + } + if (!a && b) { + await equipWeaponById(b, ctx); + _loadoutIndex = 1; + localStorage.setItem(LS_LOADOUT, '1'); + ctx.onSkills?.(); + toast(`Weapon 2 · ${_equipped?.name || b}`); + return { ok: true, index: 1, weapon: _equipped }; + } + + const next = + forceIndex === 0 || forceIndex === 1 + ? forceIndex + : /** @type {0|1} */ (_loadoutIndex === 0 ? 1 : 0); + const id = getLoadoutWeaponId(next); + if (!id) { + toast('Empty weapon set'); + return { ok: false, index: _loadoutIndex }; + } + _loadoutIndex = next; + localStorage.setItem(LS_LOADOUT, String(next)); + _slot3Id = _slot3BySet[next] || null; + await equipWeaponById(id, { ...ctx, skipPaperdollWrite: true, quiet: true }); + // Keep paperdoll: active set is logical only — items stay in their slots + ctx.onSkills?.(); + toast( + `Weapon ${next + 1} · ${_equipped?.name || id} · ${_equipped?.animPack || ''} · skills swapped` + ); + return { ok: true, index: next, weapon: _equipped }; +} + +/** Restore loadout index after page reload (call after equip map ready). */ +export function restoreLoadoutIndex() { + const raw = localStorage.getItem(LS_LOADOUT); + _loadoutIndex = raw === '1' ? 1 : 0; + return _loadoutIndex; +} + +/** + * Equip by weapon id (t0-sword, t0-wand, …). + * @param {string} weaponId + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * onToast?: (s: string) => void + * }} ctx + */ +export async function equipWeaponById(weaponId, ctx) { + const cat = await ensureWeaponCatalog(); + const weapon = cat.byId.get(weaponId); + if (!weapon) throw new Error(`Unknown weapon ${weaponId}`); + return equipWeapon(weapon, ctx); +} + +/** + * @param {import('../api/t0WeaponCatalog.js').EquippableWeapon} weapon + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * onToast?: (s: string) => void + * }} ctx + */ +export async function equipWeapon(weapon, ctx) { + const character = ctx.character; + const toast = ctx.onToast || (() => {}); + + _equipped = weapon; + if (!_slot3BySet[_loadoutIndex]) { + _slot3Id = weapon.defaultSlot3Id; + _slot3BySet[_loadoutIndex] = weapon.defaultSlot3Id; + } else { + _slot3Id = _slot3BySet[_loadoutIndex]; + } + + // If equipping into a specific paperdoll set (from Inventory), set index + if (ctx.weaponSet === 0 || ctx.weaponSet === 1) { + _loadoutIndex = ctx.weaponSet; + localStorage.setItem(LS_LOADOUT, String(_loadoutIndex)); + } + + // 1) Kit mesh_ids exclusive weapon slot + const slot = weapon.meshSlot; + if (character.equipment) { + const WEAPON_SLOTS = ['sword', 'axe', 'hammer', 'spear', 'staff', 'bow', 'shield', 'pistol']; + for (const w of WEAPON_SLOTS) { + if (w !== slot) character.equipment.setSlot?.(w, null); + } + // Prefer variant A / first available. Kit often has no pistol mesh_ids — skip set then. + const summary = character.equipment.getCatalogSummary?.() || {}; + const variants = summary[slot]?.variants || []; + if (variants.length) { + const pick = + variants.find((v) => v === 'A' || v === '_default') || variants[0] || 'A'; + character.equipment.setSlot?.(slot, pick); + } + character._reGroundAfterEquip?.(); + character.ik?.setBones?.(character.equipment.findBones?.()); + } + + // 2) Anim pack for weapon + await character.setAnimPack?.(weapon.animPack); + await character._bindPack?.('combat_mobility'); + + // 3) 3D catalog model on hand (prefab model — for Warlords prefab QA) + const bones = character.equipment?.findBones?.() || character.bones || {}; + const hand = bones.rHand || character.bones?.rHand; + clearWeaponAttach(hand); + _attach = null; + if (weapon.modelUrl) { + const wt = String(weapon.weaponType || ''); + const id = String(weapon.id || ''); + let profile = 'melee'; + if (/WAND/i.test(wt) || id === 't0-wand') profile = 'wand'; + else if (/STAFF|TOME|NATURE/i.test(wt) || /staff|sapling|tome/i.test(id)) profile = 'staff'; + else if (/PISTOL|HANDGUN/i.test(wt) || /pistol|handgun/i.test(id)) profile = 'pistol'; + else if (/GUN|RIFLE/i.test(wt) || /rifle|gun/i.test(id)) profile = 'pistol'; + else if (/BOW|CROSSBOW/i.test(wt) || /bow|crossbow/i.test(id)) profile = 'bow'; + else if (/SHIELD/i.test(wt)) profile = 'shield'; + const maxLengthM = + profile === 'wand' + ? 0.95 + : profile === 'staff' + ? 1.25 + : profile === 'pistol' + ? 0.45 + : profile === 'bow' + ? 1.35 + : /SPEAR/i.test(wt) || /spear/i.test(id) + ? 1.9 + : /GREAT|2H|GREATAXE|WARHAMMER/i.test(wt) || /great|2h|greataxe|hammer2h/i.test(id) + ? 1.75 + : /DAGGER/i.test(wt) || /dagger/i.test(id) + ? 0.55 + : /TOOL/i.test(wt) || /tool/i.test(id) + ? 0.9 + : 1.2; + _attach = await attachWeaponModel(hand, weapon.modelUrl, { + profile, + maxLengthM + }); + // Character keeps pointer for getWeaponTip / reload pose + if (character) { + character.weaponAttach = _attach; + // Hold-pose kind SSOT (main-panel + play equip share applyWeaponHoldPose) + character.weaponHoldKind = normalizeHoldKind( + weapon.weaponType || weapon.kind || weapon.id || profile + ); + character.syncWeaponAttach?.(); + // Oriented cylinder from weapon mesh (+0.02 m pad) → tip / residual / parry + try { + character.rebuildWeaponVolume?.({ debug: false }); + } catch { + /* optional */ + } + // Lab mesh appearance (color / scale / rotate) if saved + try { + const { applyWeaponAppearance } = await import('../equipment/meshAppearance.js'); + applyWeaponAppearance(character, weapon.id); + } catch { + /* optional */ + } + } + } else if (character) { + character.weaponHoldKind = normalizeHoldKind(weapon.weaponType || weapon.kind || weapon.id); + try { + character.rebuildWeaponVolume?.({ debug: false }); + } catch { + /* optional */ + } + } + + // 4) Warm production skill overrides (public/skills/production/.json) before hotbar compile + try { + const ids = [ + weapon.slot1?.id, + weapon.slot2?.id, + weapon.defaultSlot3Id, + ...(weapon.slot3Options || []).map((s) => s?.id) + ].filter(Boolean); + await warmProductionOverrides(ids); + } catch { + /* optional overrides */ + } + + // 5) Skill bar — caller sets setActiveSkillTree('equipped') + drc.skills refresh + + if (!ctx.quiet) { + toast( + `Equipped ${weapon.name} · ${weapon.weaponType} · ${weapon.animPack} · skills ${weapon.slot1.name} / ${weapon.slot2.name}` + ); + } + + return { + weapon, + hotbar: equippedWeaponHotbar(), + attach: _attach, + prefabExport: exportWarlordsWeaponPrefab(weapon, { slot3Id: _slot3Id }) + }; +} + +export function getWeaponAttach() { + return _attach; +} + +export function unequipWeapon(ctx) { + const character = ctx?.character; + const bones = character?.equipment?.findBones?.() || character?.bones || {}; + clearWeaponAttach(bones.rHand || character?.bones?.rHand); + _attach = null; + if (character) character.weaponAttach = null; + _equipped = null; + _slot3Id = null; + ctx?.onToast?.('Unequipped'); +} + +/** + * Download / copy Warlords prefab JSON for current equip. + */ +export function exportEquippedPrefab() { + if (!_equipped) return null; + return exportWarlordsWeaponPrefab(_equipped, { slot3Id: getEquippedSlot3Id() }); +} + +export function downloadEquippedPrefab() { + const data = exportEquippedPrefab(); + if (!data) return false; + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `${data.id}.warlords-weapon-prefab.json`; + a.click(); + URL.revokeObjectURL(a.href); + return true; +} diff --git a/src/combat/flintlockChamber.js b/src/combat/flintlockChamber.js new file mode 100644 index 0000000..05e4e5c --- /dev/null +++ b/src/combat/flintlockChamber.js @@ -0,0 +1,220 @@ +/** + * Flintlock chamber / load state (Warlords T0 gun production). + * + * Classic single-load: empty until reload, one powder load fires Practice / Burst / Suppress. + * When empty, digit **1** is **Reload** (not Practice Shot). + * + * Aligns Open Danger skill slots (weapon-live-packs): + * pistol_shot · pistol_double · pistol_fan · pistol_reload + * + * @see config/pistolAnimSsot.js · docs/PISTOL_FLINTLOCK_SSOT.md + * @see gameopen/content/anims/weapon-live-packs.json → pistol.skillSlots + */ + +/** Open Danger / weapon-live-packs skill slot ids (review SSOT) */ +export const OPEN_PISTOL_SKILL_SLOTS = Object.freeze({ + pistol_shot: { + role: 'attack', + clip: 'pistol/gunplay', + castingId: 't0_gun_practice_shot', + label: 'Practice Shot', + needsLoad: true + }, + pistol_double: { + role: 'attack', + clip: 'pistol/gunplay', + castingId: 't0_gun_burst_fire', + label: 'Burst Fire', + needsLoad: true, + multiHit: 3 + }, + pistol_fan: { + role: 'skill2', + clip: 'pistol/charged-pistol', + castingId: 't0_gun_suppressing_shot', + label: 'Suppressing Shot', + needsLoad: true + }, + pistol_reload: { + role: 'reload', + clip: 'pistol/reload', + castingId: 't0_gun_reload', + label: 'Reload', + needsLoad: false + }, + /** Melee review — not T0 bar default */ + pistol_whip: { + role: 'skill3', + clip: 'pistol/pistol-whip', + castingId: null, + label: 'Pistol Whip', + needsLoad: false + }, + pistol_charged: { + role: 'skill2', + clip: 'pistol/charged-pistol', + castingId: null, + label: 'Charged Pistol', + needsLoad: true + } +}); + +/** Review matrix for Showcase / lab toast */ +export const FLINTLOCK_ANIM_REVIEW = Object.freeze([ + { role: 'idle', clip: 'pistol/idle', use: 'loco' }, + { role: 'walk', clip: 'pistol/walk-forward', use: 'loco' }, + { role: 'run', clip: 'pistol/run-forward', use: 'loco' }, + { role: 'attack', clip: 'pistol/gunplay', use: 'fire · Practice Shot' }, + { role: 'reload', clip: 'pistol/reload', use: 'empty · key 1 · powder load' }, + { role: 'draw', clip: 'pistol/drawing-gun', use: 'draw / reload fallback' }, + { role: 'skill2', clip: 'pistol/charged-pistol', use: 'power / fan review' }, + { role: 'skill3', clip: 'pistol/pistol-whip', use: 'melee whip review' }, + { role: 'jump', clip: 'pistol/pistol-jump', use: 'jump' } +]); + +/** + * @typedef {object} ChamberState + * @property {number} loaded 0 or 1 (single chamber) + * @property {number} capacity always 1 for T0 flintlock + * @property {boolean} reloading + * @property {number} reloadEndsAt elapsed time + */ + +/** + * @param {{ capacity?: number }} [opts] + */ +export function createFlintlockChamber(opts = {}) { + const capacity = Math.max(1, Math.min(1, Number(opts.capacity) || 1)); // T0: single load + /** @type {ChamberState} */ + const state = { + loaded: capacity, // start loaded for playtest + capacity, + reloading: false, + reloadEndsAt: 0 + }; + + return { + get state() { + return state; + }, + isLoaded() { + return state.loaded > 0 && !state.reloading; + }, + isEmpty() { + return state.loaded <= 0; + }, + isReloading() { + return state.reloading; + }, + /** @returns {boolean} true if a shot was consumed */ + consume() { + if (state.loaded <= 0 || state.reloading) return false; + state.loaded = 0; + return true; + }, + /** + * Begin reload — call completeReload when anim finishes. + * @param {number} durationSec + * @param {number} elapsed + */ + beginReload(durationSec, elapsed) { + if (state.reloading) return false; + if (state.loaded >= state.capacity) return false; + state.reloading = true; + state.reloadEndsAt = elapsed + Math.max(0.2, durationSec || 0.92); + return true; + }, + /** Fill chamber after reload anim */ + completeReload() { + state.reloading = false; + state.loaded = state.capacity; + state.reloadEndsAt = 0; + return true; + }, + cancelReload() { + state.reloading = false; + state.reloadEndsAt = 0; + }, + /** Tick auto-complete if elapsed past end (failsafe) */ + tick(elapsed) { + if (state.reloading && elapsed >= state.reloadEndsAt) { + this.completeReload(); + return true; + } + return false; + }, + label() { + if (state.reloading) return 'Reloading…'; + if (state.loaded > 0) return 'Loaded'; + return 'Empty'; + }, + /** HUD / hotbar slot1 presentation */ + slot1Presentation() { + if (state.reloading) { + return { id: 't0_gun_reload', label: 'Reloading…', isReload: true, empty: true }; + } + if (state.loaded <= 0) { + return { id: 't0_gun_reload', label: 'Reload', isReload: true, empty: true }; + } + return { + id: 't0_gun_practice_shot', + label: 'Practice Shot', + isReload: false, + empty: false + }; + } + }; +} + +/** + * Skill needs a powder load (bullet skills). + * @param {object} skill + */ +export function skillNeedsLoad(skill) { + if (!skill) return false; + if (skill.isWard || skill.isFocus || skill.skillKind === 'buff') return false; + if (skill.isReload || skill.skillKind === 'reload' || /reload/i.test(`${skill.id} ${skill.label}`)) { + return false; + } + if (skill.useBulletProjectile || skill.projectileKind === 'bullet' || skill.projectile === 'bullet') { + return true; + } + if (/t0_gun_(practice|burst|suppress)/i.test(String(skill.id || ''))) return true; + return false; +} + +/** + * Is this the flintlock / t0-gun context? + * @param {object} [skill] + * @param {object} [weapon] + * @param {string} [animPack] + */ +export function isFlintlockContext(skill, weapon, animPack) { + if (animPack === 'pistol') return true; + if (weapon?.id === 't0-gun' || weapon?.meshSlot === 'pistol') return true; + const blob = `${skill?.id || ''} ${skill?.weaponId || ''} ${skill?.catalogSkillId || ''}`.toLowerCase(); + return /t0-gun|t0_gun|flint|pistol/.test(blob); +} + +/** Synthetic reload skill for empty slot-1 */ +export function makeReloadSkillDef() { + return { + id: 't0_gun_reload', + catalogSkillId: 't0_gun_reload', + label: 'Reload', + description: 'Powder and ball — reload the flintlock', + damage: 0, + cooldown: 0.5, + castDuration: 0, + staminaCost: 4, + manaCost: 0, + style: 'ranged', + skillKind: 'reload', + isReload: true, + animRole: 'reload', + animPack: 'pistol', + effects: ['Reload'], + rangeM: 0, + hitFrameDelay: 0.45 + }; +} diff --git a/src/combat/hitReaction.js b/src/combat/hitReaction.js new file mode 100644 index 0000000..ed67bfe --- /dev/null +++ b/src/combat/hitReaction.js @@ -0,0 +1,63 @@ +/** + * Hit reactions — knockback impulse + knocked-up clip. + * + * Clip: public/anims/baked/reactions/knocked-up.json (Mixamo → rematch Bip001) + * Physics: horizontal MM impulse + optional vy kick on PhysicsWorld / kinematic. + * + * @see docs/SKILL_DELIVERY_SSOT.md + */ + +import { Vector3 } from 'three'; +import { mmToM } from './motionMath.js'; + +const _fwd = new Vector3(); + +/** + * Apply knockback to lab hero (or future NPC with same shape). + * + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * physics?: import('../physics/PhysicsWorld.js').PhysicsWorld|null, + * drc?: import('./DrcCombatController.js').DrcCombatController|null + * }} ctx + * @param {{ + * forward: Vector3, + * knockbackMm?: number, + * knockupVy?: number, + * playAnim?: boolean + * }} hit + */ +export function applyKnockback(ctx, hit) { + if (!ctx?.character) return false; + const mm = hit.knockbackMm ?? 180; + const dist = mmToM(mm); + const dur = 0.35; + _fwd.copy(hit.forward || new Vector3(0, 0, 1)); + _fwd.y = 0; + if (_fwd.lengthSq() < 1e-8) _fwd.set(0, 0, 1); + else _fwd.normalize(); + + // Horizontal slide via DRC dodge impulse channel (same CCT path) + if (ctx.drc && typeof ctx.drc._dodgeVel !== 'undefined') { + const speed = dist / Math.max(0.12, dur); + ctx.drc._dodgeVel.set(_fwd.x * speed, 0, _fwd.z * speed); + ctx.drc._dodgeT = dur; + ctx.drc._dodgeDur = dur; + } else { + // Fallback: direct root nudge + ctx.character.root.position.x += _fwd.x * dist * 0.35; + ctx.character.root.position.z += _fwd.z * dist * 0.35; + } + + // Vertical kick + const vy = hit.knockupVy ?? 2.2; + if (vy > 0.05) { + if (ctx.physics?.ready) ctx.physics.jump(vy); + else if (ctx.drc) ctx.drc._kinVy = Math.max(ctx.drc._kinVy || 0, vy); + } + + if (hit.playAnim !== false) { + ctx.character.playHitReaction?.() || ctx.character.requestOneShot?.('hitReact'); + } + return true; +} diff --git a/src/combat/motionMath.js b/src/combat/motionMath.js new file mode 100644 index 0000000..d1af916 --- /dev/null +++ b/src/combat/motionMath.js @@ -0,0 +1,63 @@ +/** + * Motion-math (MM) — fleet high-level displacement descriptors. + * + * Same contract as Animator Studio: 100 MM = 1 m of body travel. + * Dodge / roll / attack profiles speak in MM; runtime converts with mmToM. + * + * @see Documents/animator animator/src/three/Studio.ts MM_TO_M + */ + +/** 100 motion-math units = 1 metre. */ +export const MM_TO_M = 0.01; + +/** + * @param {number} mm + * @returns {number} metres + */ +export function mmToM(mm) { + return (Number(mm) || 0) * MM_TO_M; +} + +/** + * @param {number} m metres + * @returns {number} MM units + */ +export function mToMm(m) { + return (Number(m) || 0) / MM_TO_M; +} + +/** + * Dodge profiles in MM (peak body travel along dodge axis). + * Lateral AA/DD is 3× the walk-dodge baseline so escapes clear attack ranges. + * + * Baseline walk dodge was ~2.4 m = 240 MM; lateral escape = 720 MM = 7.2 m. + */ +export const DODGE_MM = Object.freeze({ + /** AA / DD escape */ + lateral: 720, + /** WW forward */ + forward: 240, + /** X / S back */ + back: 240 +}); + +/** + * Resolve dodge travel distance (metres) for a direction. + * @param {'left'|'right'|'forward'|'back'} dir + * @param {{ dodgeDistance?: number, dodgeLateralMul?: number, dodgeMm?: { lateral?: number, forward?: number, back?: number } }} [cfg] + * @returns {number} + */ +export function dodgeDistanceM(dir, cfg = {}) { + const baseM = cfg.dodgeDistance ?? 2.4; + const baseMm = mToMm(baseM); + const mm = cfg.dodgeMm || {}; + if (dir === 'left' || dir === 'right') { + // Prefer explicit MM; else 3× baseline metres + const latMm = mm.lateral ?? DODGE_MM.lateral ?? baseMm * (cfg.dodgeLateralMul ?? 3); + return mmToM(latMm); + } + if (dir === 'forward') { + return mmToM(mm.forward ?? DODGE_MM.forward ?? baseMm); + } + return mmToM(mm.back ?? DODGE_MM.back ?? baseMm); +} diff --git a/src/combat/pathCastClassify.js b/src/combat/pathCastClassify.js new file mode 100644 index 0000000..133c571 --- /dev/null +++ b/src/combat/pathCastClassify.js @@ -0,0 +1,22 @@ +/** + * Classify LMB path strokes for staff casting / placement. + * Used by PathDrawer end + DrcCombatController.castPathAbility. + */ + +/** + * @param {number} length metres + * @param {number} holdSec seconds held + * @param {{ aoeMaxLength?: number, spikesMaxLength?: number, wallMinLength?: number, wallHoldSec?: number }} [cfg] + * @returns {'aoe'|'spikes'|'wall'|'stream'} + */ +export function classifyPathCast(length, holdSec = 0, cfg = {}) { + const aoeMax = cfg.aoeMaxLength ?? 3.2; + const spikesMax = cfg.spikesMaxLength ?? 9; + const wallMin = cfg.wallMinLength ?? 9; + const wallHold = cfg.wallHoldSec ?? 0.85; + + if (length <= aoeMax && holdSec < wallHold) return 'aoe'; + if (holdSec >= wallHold || length >= wallMin) return 'wall'; + if (length <= spikesMax) return 'spikes'; + return 'stream'; +} diff --git a/src/combat/playerActivity.js b/src/combat/playerActivity.js new file mode 100644 index 0000000..3a2fb8e --- /dev/null +++ b/src/combat/playerActivity.js @@ -0,0 +1,87 @@ +/** + * Player activity modes — Open Danger parity (combat / harvest). + * SSOT port of gameopen playerMode.ts for Casting Dev Island. + * + * Hold Q → mode radial (↑ combat · ↓ harvest) + * Tap Q · combat → dual weapon loadout swap (Weapon 1 ↔ Weapon 2 · skills · loco) + * Tap Q · harvest → return to combat + * Hold R (harvest) → tool radial + * Tap R (harvest) → draw last tool (default pick) — auto stow weapon on harvest enter + * F harvest → nearest node for tool in hand + * Dodge stays AA / DD / X — Q never dodges + * + * State machine: playerActivityMachine.js (XState) — mode / hand / loco / tool memory. + * + * @see gameopen/artifacts/animator/src/three/playerMode.ts + * @see playerActivityMachine.js + * @see equippedWeaponRuntime.js swapWeaponLoadout + */ + +/** @typedef {'combat'|'harvest'} ActivityMode */ +/** @typedef {'none'|'mode'|'tool'} RadialKind */ + +export const ACTIVITY_MODES = Object.freeze(['combat', 'harvest']); + +/** Default harvest tool when none remembered (product: pick). */ +export const DEFAULT_HARVEST_TOOL = 'pick'; + +export const MODE_LABEL = Object.freeze({ + combat: 'COMBAT', + harvest: 'HARVEST' +}); + +export const MODE_COLOR = Object.freeze({ + combat: '#ff7a7a', + harvest: '#7ee7a8' +}); + +/** Hold-Q wedges (vertical aim). */ +export const MODE_SWITCH_RADIAL = Object.freeze([ + { id: 'mode_combat', label: 'Combat', glyph: 'C', hint: 'Up', color: '#ff7a7a' }, + { id: 'mode_harvest', label: 'Harvest', glyph: 'H', hint: 'Down', color: '#7ee7a8' } +]); + +/** + * Hold-R harvest tools (equip intent). + * Maps to tool tags used by DevIslandHarvest / equipWeaponById. + * Default / first profession tool = pick (ore / rock). + */ +export const HARVEST_TOOL_RADIAL = Object.freeze([ + { id: 'pick', label: 'Pick', glyph: 'Pk', hint: 'ore', color: '#a0b0c8', weaponId: 't0-tool' }, + { id: 'hatchet', label: 'Hatchet', glyph: 'Ax', hint: 'wood', color: '#c98a3d', weaponId: 't0-axe1h' }, + { id: 'knife', label: 'Knife', glyph: 'Kn', hint: 'skin', color: '#e8a070', weaponId: 't0-dagger' }, + { id: 'hand', label: 'Hands', glyph: 'Hn', hint: 'herb', color: '#90d070', weaponId: null }, + { id: 'shovel', label: 'Shovel', glyph: 'Sh', hint: 'terrain', color: '#c4a070', weaponId: 't0-tool' }, + { id: 'back_slot', label: 'Back', glyph: 'Bk', hint: 'windsurf', color: '#b0c8ff', weaponId: null } +]); + +/** Hold duration (s) before radial opens. Tap under this = toggle / draw last. */ +export const RADIAL_HOLD_S = 0.18; + +/** + * @param {ActivityMode} mode + */ +export function nextActivityMode(mode) { + return mode === 'harvest' ? 'combat' : 'harvest'; +} + +/** + * Map tool radial id → harvest class preferences. + * @param {string} toolId + */ +export function toolPreferClasses(toolId) { + switch (toolId) { + case 'pick': + return ['ore', 'rock']; + case 'hatchet': + return ['wood']; + case 'knife': + return ['herb', 'fiber']; + case 'hand': + return ['herb', 'fiber', 'rock']; + case 'shovel': + return ['rock']; + default: + return null; + } +} diff --git a/src/combat/playerActivityMachine.js b/src/combat/playerActivityMachine.js new file mode 100644 index 0000000..9b0ea64 --- /dev/null +++ b/src/combat/playerActivityMachine.js @@ -0,0 +1,186 @@ +/** + * Player activity state machine (XState v5) — combat ↔ harvest, hand, loco. + * + * Owns pure state only. App listens for transitions and runs equip/stow side effects. + * Does not invent a second mode store — extends playerActivity.js SSOT. + * + * @see playerActivity.js + * @see docs/SESSION_STATE_SSOT.md + */ + +import { createMachine, createActor, assign } from 'xstate'; +import { HARVEST_TOOL_RADIAL } from './playerActivity.js'; + +/** @typedef {'combat'|'harvest'} ActivityMode */ +/** @typedef {'weapon'|'tool'|'empty'} HandKind */ +/** @typedef {'idle'|'walk'|'run'|'sprint'|'jump'|'harvest_swing'|'cast'|'attack'} LocoKind */ + +export const DEFAULT_TOOL_ID = 'pick'; + +/** + * @param {string} toolId + */ +export function resolveToolDef(toolId) { + return HARVEST_TOOL_RADIAL.find((t) => t.id === toolId) || HARVEST_TOOL_RADIAL[0]; +} + +/** + * Pure machine: activity × hand × tool memory × loco tag. + * Side effects (mesh equip) live in App actor subscription. + */ +export const playerActivityMachine = createMachine({ + id: 'playerActivity', + initial: 'combat', + types: { + /* runtime JS — types as comments for consumers */ + }, + context: { + /** Last combat weapon id (stashed when entering harvest) */ + combatWeaponId: null, + /** Equipped harvest tool id */ + toolId: DEFAULT_TOOL_ID, + /** Last non-back tool (R tap re-draws this; default pick) */ + lastToolId: DEFAULT_TOOL_ID, + /** What the hands present */ + hand: /** @type {HandKind} */ ('weapon'), + /** Coarse locomotion / action tag for anim gating */ + loco: /** @type {LocoKind} */ ('idle'), + /** Seconds since last mode switch (UI / cooldowns) */ + modeAge: 0 + }, + states: { + combat: { + entry: assign({ + hand: 'weapon', + modeAge: 0 + }), + on: { + ENTER_HARVEST: { + target: 'harvest', + actions: assign(({ context, event }) => { + const toolId = event.toolId || context.lastToolId || DEFAULT_TOOL_ID; + const def = resolveToolDef(toolId); + const hand = + toolId === 'hand' || !def?.weaponId ? (toolId === 'hand' ? 'empty' : 'tool') : 'tool'; + return { + combatWeaponId: + event.combatWeaponId !== undefined + ? event.combatWeaponId + : context.combatWeaponId, + toolId, + lastToolId: toolId === 'back_slot' ? context.lastToolId : toolId, + hand: toolId === 'hand' ? 'empty' : hand, + modeAge: 0 + }; + }) + }, + SET_COMBAT_WEAPON: { + actions: assign({ + combatWeaponId: ({ event }) => event.weaponId ?? null + }) + }, + SET_LOCO: { + actions: assign({ + loco: ({ event }) => event.loco || 'idle' + }) + }, + TICK: { + actions: assign({ + modeAge: ({ context, event }) => context.modeAge + (event.dt || 0) + }) + } + } + }, + harvest: { + entry: assign({ + modeAge: 0 + }), + on: { + ENTER_COMBAT: { + target: 'combat', + actions: assign(({ context, event }) => ({ + combatWeaponId: + event.combatWeaponId !== undefined + ? event.combatWeaponId + : context.combatWeaponId, + hand: 'weapon', + modeAge: 0 + })) + }, + /** Hold-R radial pick */ + SELECT_TOOL: { + actions: assign(({ context, event }) => { + const toolId = event.toolId || context.toolId || DEFAULT_TOOL_ID; + const hand = + toolId === 'hand' ? 'empty' : toolId === 'back_slot' ? context.hand : 'tool'; + return { + toolId, + lastToolId: toolId === 'back_slot' ? context.lastToolId : toolId, + hand + }; + }) + }, + /** + * Tap R (or re-draw): pull last used tool, default pick. + * Auto-stows nothing else — already in harvest. + */ + DRAW_LAST_TOOL: { + actions: assign(({ context }) => { + const toolId = context.lastToolId || DEFAULT_TOOL_ID; + return { + toolId, + hand: toolId === 'hand' ? 'empty' : 'tool' + }; + }) + }, + STOW_TOOL: { + actions: assign({ + hand: 'empty' + }) + }, + SET_LOCO: { + actions: assign({ + loco: ({ event }) => event.loco || 'idle' + }) + }, + TICK: { + actions: assign({ + modeAge: ({ context, event }) => context.modeAge + (event.dt || 0) + }) + } + } + } + } +}); + +/** + * Create + start actor. Returns control API for App. + * @param {{ + * onTransition?: (snap: import('xstate').SnapshotFrom) => void + * }} [opts] + */ +export function createPlayerActivityActor(opts = {}) { + const actor = createActor(playerActivityMachine); + if (typeof opts.onTransition === 'function') { + actor.subscribe((snap) => { + opts.onTransition(snap); + }); + } + actor.start(); + return actor; +} + +/** + * @param {import('xstate').SnapshotFrom} snap + * @returns {ActivityMode} + */ +export function activityFromSnap(snap) { + return snap?.value === 'harvest' ? 'harvest' : 'combat'; +} + +/** + * @param {import('xstate').SnapshotFrom} snap + */ +export function toolIdFromSnap(snap) { + return snap?.context?.toolId || DEFAULT_TOOL_ID; +} diff --git a/src/combat/skillBindings.js b/src/combat/skillBindings.js new file mode 100644 index 0000000..12f1bea --- /dev/null +++ b/src/combat/skillBindings.js @@ -0,0 +1,101 @@ +/** + * Hotkey / skill-slot bindings → master catalog skill ids. + * Persisted so showcase "save to hotkey" is true for combat 1–4 + F. + * + * Slots: + * 0–3 → Digit1–4 (weapon skill bar / DRC) + * f → F weapon skill (primary / Showcase bind) — NOT class ability yet + * + * Product: F = weapon skill prefab path (cast times, anims). Pickup/harvest + * are context priority before F skill; residual is not the F default. + */ + +const STORAGE_KEY = 'casting.lab.skillBindings.v1'; + +/** + * @typedef {object} SkillBind + * @property {string} skillId + * @property {string} [name] + * @property {string} [weaponTypeId] + * @property {string} [labPack] + * @property {string} [labSlot] + * @property {string} [iconUrl] + * @property {number} [cooldown] + * @property {string} [damageType] + */ + +/** + * @returns {Record} + */ +export function loadSkillBindings() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return emptyBindings(); + const parsed = JSON.parse(raw); + return { ...emptyBindings(), ...parsed }; + } catch { + return emptyBindings(); + } +} + +export function emptyBindings() { + return { 0: null, 1: null, 2: null, 3: null, f: null }; +} + +/** + * @param {Record} bindings + */ +export function saveSkillBindings(bindings) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(bindings)); +} + +/** + * @param {string|number} slot 0-3 or 'f' + * @param {SkillBind|null} bind + */ +export function setSkillBinding(slot, bind) { + const all = loadSkillBindings(); + const key = String(slot); + all[key] = bind + ? { + skillId: bind.skillId, + name: bind.name, + weaponTypeId: bind.weaponTypeId, + labPack: bind.labPack, + labSlot: bind.labSlot, + iconUrl: bind.iconUrl, + cooldown: bind.cooldown, + damageType: bind.damageType + } + : null; + saveSkillBindings(all); + return all; +} + +/** + * Apply four catalog skills to slots 0–3. + * @param {object[]} skills + */ +export function bindHotbarFromSkills(skills) { + const all = emptyBindings(); + for (let i = 0; i < 4; i++) { + const s = skills[i]; + if (!s) continue; + all[String(i)] = { + skillId: s.id, + name: s.name, + weaponTypeId: s.weaponTypeId, + labPack: s.labPack, + labSlot: s.labSlot, + iconUrl: s.iconUrl, + cooldown: s.cooldown, + damageType: s.damageType + }; + } + saveSkillBindings(all); + return all; +} + +export function getSkillBinding(slot) { + return loadSkillBindings()[String(slot)] || null; +} diff --git a/src/combat/skillDelivery.js b/src/combat/skillDelivery.js new file mode 100644 index 0000000..1966c26 --- /dev/null +++ b/src/combat/skillDelivery.js @@ -0,0 +1,451 @@ +/** + * Weapon skill **delivery patterns** — SSOT for how catalog skills deploy in space. + * + * Does not invent skill rows (those come from t0-weapons / master-weaponSkills). + * Maps catalog text + style + pathMode → spawn / aim / projectile rules. + * + * @see docs/SKILL_DELIVERY_SSOT.md + * @see docs/WEAPON_SKILLS_API_SSOT.md + */ + +import { Vector3 } from 'three'; + +/** + * Canonical delivery patterns (SI metres, caster-relative). + * @typedef {'weapon'|'caster_to_target'|'over_target'|'under_target'|'around_caster'|'around_target'|'at_location'|'toggle_aura'|'path_stream'|'path_aoe'|'path_spikes'|'path_wall'} SkillDeliveryPattern + */ + +/** @type {Record} */ +export const DELIVERY_META = Object.freeze({ + weapon: { + label: 'Weapon', + description: 'Physical residual / strike from weapon tip along aim (melee Getsuga-class).' + }, + caster_to_target: { + label: 'Caster → target', + description: 'Projectile or beam from cast origin to soft-lock / aim point.' + }, + over_target: { + label: 'Over target', + description: 'Spawn above target (or aim) and drop / rain (meteor, holy light).' + }, + under_target: { + label: 'Under target', + description: 'Erupt from ground under target / aim (spikes, roots, ice bloom).' + }, + around_caster: { + label: 'Around caster', + description: 'Ring / nova centered on caster feet (shield, flame nova).' + }, + around_target: { + label: 'Around target', + description: 'Ring / nova centered on soft-lock or aim point.' + }, + at_location: { + label: 'At location', + description: 'Place effect at ground aim / path endpoint (selected location).' + }, + toggle_aura: { + label: 'Toggle aura', + description: 'On/off aura on caster (ward, shield) — no travel projectile.' + }, + path_stream: { + label: 'Path stream', + description: 'Drawn path = stream travel (default long stroke).' + }, + path_aoe: { + label: 'Path AoE', + description: 'Short path / tap = ground AoE at endpoint.' + }, + path_spikes: { + label: 'Path spikes', + description: 'Medium stroke = line of eruptions under path.' + }, + path_wall: { + label: 'Path wall', + description: 'Long hold / long stroke = barrier along path.' + } +}); + +/** + * Infer delivery from catalog skill (id, name, effects, style, pathMode). + * Catalog-first — only heuristics when fields empty. + * + * @param {{ + * id?: string, + * label?: string, + * name?: string, + * style?: string, + * pathMode?: string|null, + * effects?: string[], + * description?: string, + * skillKind?: string, + * isWard?: boolean, + * isFocus?: boolean, + * presentation?: string|null + * }} skill + * @returns {SkillDeliveryPattern} + */ +/** + * Ground aim ring (world reticle) — only for skills that place / AoE / path on terrain. + * Screen-center HUD crosshair stays for focus combat; ground ring is separate. + * + * @param {SkillDeliveryPattern|string|null|undefined} pattern + * @returns {boolean} + */ +export function deliveryNeedsGroundMarker(pattern) { + if (!pattern) return false; + return ( + pattern === 'at_location' || + pattern === 'around_target' || + pattern === 'under_target' || + pattern === 'over_target' || + pattern === 'path_aoe' || + pattern === 'path_spikes' || + pattern === 'path_wall' || + pattern === 'path_stream' + ); +} + +/** + * @param {object|null|undefined} skill + * @returns {boolean} + */ +export function skillNeedsGroundMarker(skill) { + if (!skill) return false; + // Explicit flag from catalog / DRC + if (skill.needsGroundAim === true || skill.placement === true) return true; + if (skill.needsGroundAim === false) return false; + return deliveryNeedsGroundMarker(inferDeliveryPattern(skill)); +} + +export function inferDeliveryPattern(skill) { + if (!skill) return 'caster_to_target'; + const blob = [ + skill.id, + skill.label, + skill.name, + skill.description, + skill.pathMode, + skill.presentation, + skill.skillKind, + ...(skill.effects || []) + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + + // Explicit path modes from path cast + if (skill.pathMode === 'stream') return 'path_stream'; + if (skill.pathMode === 'aoe') return 'path_aoe'; + if (skill.pathMode === 'spikes') return 'path_spikes'; + if (skill.pathMode === 'wall') return 'path_wall'; + + // Buff / ward / toggle + if (skill.isWard || skill.isFocus || skill.skillKind === 'buff') return 'toggle_aura'; + if (/\b(ward|shield|aura|stance|guard|focus)\b/.test(blob) && (skill.damage === 0 || /buff|ward/.test(blob))) { + return 'toggle_aura'; + } + + // Melee weapon residual + if (skill.style === 'melee' || /\b(slash|thrust|sweep|strike|melee)\b/.test(blob)) { + if (/\b(sweep|aoe|arc|nova)\b/.test(blob)) return 'around_caster'; + return 'weapon'; + } + + // Spatial keywords + if (/\b(meteor|from above|sky|rain|comet|smite|holy light|radiant)\b/.test(blob)) { + return 'over_target'; + } + if (/\b(erupt|spike|root|under|ground|bloom|geyser|upheaval)\b/.test(blob)) { + return 'under_target'; + } + // Freeze / ice nova blast from self in all directions + if (/\b(freeze|absolute.?zero|ice.?nova|frost.?nova|glacial.?blast)\b/.test(blob)) { + return 'around_caster'; + } + if (/\b(nova|around self|self aoe|flame nova|ice nova)\b/.test(blob)) { + return 'around_caster'; + } + if (/\b(around target|ring|encircle)\b/.test(blob)) { + return 'around_target'; + } + if (/\b(place|mark|zone|field|at location|beacon)\b/.test(blob)) { + return 'at_location'; + } + + // Default spells: projectile caster → aim + if (skill.style === 'spell' || skill.style === 'ranged') return 'caster_to_target'; + return 'caster_to_target'; +} + +/** + * Resolve world spawn + aim points for a delivery pattern. + * + * @param {SkillDeliveryPattern} pattern + * @param {{ + * casterPos: Vector3, + * castOrigin: Vector3, + * aimPoint: Vector3, + * forward: Vector3, + * targetPoint?: Vector3|null, + * weaponTip?: Vector3|null, + * skyHeight?: number, + * groundY?: number + * }} pose + * @returns {{ + * origin: Vector3, + * target: Vector3, + * forward: Vector3, + * height: number, + * pattern: SkillDeliveryPattern + * }} + */ +export function resolveDeliveryPose(pattern, pose) { + const sky = pose.skyHeight ?? 8; + const groundY = pose.groundY ?? 0.05; + const origin = new Vector3(); + const target = new Vector3(); + const forward = new Vector3(); + + const aim = pose.aimPoint?.clone?.() || pose.casterPos.clone().add(pose.forward); + const tgt = pose.targetPoint?.clone?.() || aim; + const tip = pose.weaponTip || pose.castOrigin; + const cast = pose.castOrigin || pose.casterPos; + + switch (pattern) { + case 'weapon': + origin.copy(tip); + target.copy(aim); + break; + case 'caster_to_target': + origin.copy(cast); + target.copy(tgt); + break; + case 'over_target': + target.copy(tgt); + target.y = groundY; + origin.set(tgt.x, groundY + sky, tgt.z); + break; + case 'under_target': + origin.set(tgt.x, groundY - 0.15, tgt.z); + target.set(tgt.x, groundY + 1.2, tgt.z); + break; + case 'around_caster': + origin.copy(pose.casterPos); + origin.y = groundY; + target.copy(origin); + break; + case 'around_target': + origin.set(tgt.x, groundY, tgt.z); + target.copy(origin); + break; + case 'at_location': + origin.set(aim.x, groundY, aim.z); + target.copy(origin); + break; + case 'toggle_aura': + origin.copy(pose.casterPos); + origin.y = groundY + 0.1; + target.copy(origin); + break; + case 'path_stream': + case 'path_aoe': + case 'path_spikes': + case 'path_wall': + origin.copy(cast); + target.copy(aim); + break; + default: + origin.copy(cast); + target.copy(aim); + } + + forward.subVectors(target, origin); + if (forward.lengthSq() < 1e-6) { + forward.copy(pose.forward || new Vector3(0, 0, 1)); + } else { + forward.normalize(); + } + + return { + origin, + target, + forward, + height: Math.max(0.1, Math.abs(origin.y - target.y)), + pattern + }; +} + +/** + * Projectile / physics profile for a delivery (SI). + * @param {SkillDeliveryPattern} pattern + * @param {{ speed?: number, aoe?: number, size?: number, intensity?: number, element?: string }} [knobs] + */ +export function deliveryPhysicsProfile(pattern, knobs = {}) { + const speed = knobs.speed ?? 14; + const aoe = knobs.aoe ?? 1.2; + const size = knobs.size ?? 0.55; + const intensity = knobs.intensity ?? 1; + const base = { + speed, + aoe, + size, + intensity, + /** contact sphere radius m */ + contactRadius: Math.max(0.25, size * 0.55), + /** knockback horizontal MM (100 MM = 1 m) */ + knockbackMm: 180 * intensity, + /** upward kick m/s on hit */ + knockupVy: 2.2 * intensity, + /** impulse scale for rigid bodies */ + force: 8 * intensity, + gravity: 0, + life: 2.5, + explodeOnHit: true, + meshKey: null + }; + + switch (pattern) { + case 'weapon': + return { + ...base, + speed: speed * 0.9, + life: 0.45, + knockbackMm: 120 * intensity, + knockupVy: 0.8 * intensity, + meshKey: 'residual' + }; + case 'over_target': + return { + ...base, + speed: Math.max(6, speed * 0.7), + gravity: -18, + life: 2.2, + knockbackMm: 220 * intensity, + knockupVy: 3.5 * intensity, + meshKey: 'summon' + }; + case 'under_target': + return { + ...base, + speed: 4, + gravity: 0, + life: 0.9, + knockbackMm: 100 * intensity, + knockupVy: 4.5 * intensity, + meshKey: 'summon' + }; + case 'around_caster': + case 'around_target': + case 'at_location': + case 'toggle_aura': + return { + ...base, + speed: 0, + life: pattern === 'toggle_aura' ? 2.5 : 1.1, + knockbackMm: 160 * intensity, + knockupVy: 1.5 * intensity, + meshKey: pattern === 'toggle_aura' ? null : 'burst' + }; + case 'caster_to_target': + default: + return { + ...base, + gravity: knobs.element === 'holy' ? -2 : 0, + meshKey: 'summon', + life: 2.8 + }; + } +} + +/** + * Element → default summon mesh key (lab SI extracts). + * Never load whole multipack — only split summons. + * Staff stream normals prefer STAFF_ORB_MESH (gd_orbs split) over fists. + */ +export const SUMMON_MESH_BY_ELEMENT = Object.freeze({ + fire: './models/vfx/summons/summon-fire-fist.glb', + ice: './models/vfx/summons/summon-ice-shard.glb', + frost: './models/vfx/summons/summon-ice-shard.glb', + water: './models/vfx/summons/summon-ice-shard.glb', + holy: './models/vfx/summons/summon-fire-fist.glb', // tint gold at runtime until holy mesh + storm: './models/vfx/summons/summon-fire-fist.glb', + nature: './models/vfx/summons/summon-ice-shard.glb', + arcane: './models/vfx/summons/summon-fire-fist.glb', + physical: null +}); + +/** + * Staff normal-attack orbs (gd_orbs_pack split) — per color / element. + * Never load gd_orbs_pack_src.glb whole. + */ +export const STAFF_ORB_MESH_BY_ELEMENT = Object.freeze({ + fire: './models/vfx/orbs/orb-fire.glb', + ice: './models/vfx/orbs/orb-ice.glb', + frost: './models/vfx/orbs/orb-ice.glb', + water: './models/vfx/orbs/orb-ice.glb', + nature: './models/vfx/orbs/orb-nature.glb', + storm: './models/vfx/orbs/orb-storm.glb', + holy: './models/vfx/orbs/orb-holy.glb', + arcane: './models/vfx/orbs/orb-arcane.glb' +}); + +export const STAFF_CHARGE_MESH = './models/vfx/charge/staff-charge.glb'; + +/** + * Prefer staff orb mesh for stream / primary bolts; summons for heavy signatures. + * @param {object} skill + * @returns {string|null} + */ +export function resolveSkillProjectileMesh(skill) { + if (!skill) return null; + if (skill.projectileMeshUrl) return skill.projectileMeshUrl; + if (skill.summonMeshUrl) return skill.summonMeshUrl; + const el = skill.element || skill.abilityElement || ''; + const blob = `${skill.id || ''} ${skill.label || ''} ${skill.catalogSkillId || ''}`.toLowerCase(); + const useOrb = + skill.useOrbProjectile === true || + skill.pathMode === 'stream' || + skill.slotType === 'primary' || + skill.slot === 0 || + skill.slot === -1 || + skill.isWeaponPrimary || + /practice|bolt|spark|ping|normal|auto/.test(blob); + if (useOrb && STAFF_ORB_MESH_BY_ELEMENT[el]) { + return STAFF_ORB_MESH_BY_ELEMENT[el]; + } + return ( + SUMMON_MESH_BY_ELEMENT[el] || + SUMMON_MESH_BY_ELEMENT[skill.abilityElement] || + null + ); +} + +/** + * Attach delivery + physics profile onto a DRC skill (non-destructive). + * @param {object} skill + */ +export function enrichSkillDelivery(skill) { + if (!skill) return skill; + const pattern = skill.delivery || inferDeliveryPattern(skill); + const physics = deliveryPhysicsProfile(pattern, { + speed: skill.projectileSpeed, + aoe: skill.rangeM ? Math.min(2.5, skill.rangeM * 0.15) : undefined, + intensity: 1, + element: skill.element || skill.abilityElement + }); + const meshUrl = resolveSkillProjectileMesh(skill); + return { + ...skill, + delivery: pattern, + deliveryLabel: DELIVERY_META[pattern]?.label || pattern, + deliveryPhysics: physics, + summonMeshUrl: meshUrl, + projectileMeshUrl: meshUrl, + chargeMeshUrl: skill.chargeMeshUrl || STAFF_CHARGE_MESH, + useOrbProjectile: + skill.useOrbProjectile === true || + (meshUrl != null && /\/orbs\/orb-/.test(meshUrl)) + }; +} diff --git a/src/combat/skillStatusSystem.js b/src/combat/skillStatusSystem.js new file mode 100644 index 0000000..c3e6db6 --- /dev/null +++ b/src/combat/skillStatusSystem.js @@ -0,0 +1,379 @@ +/** + * Production status effects for weapon skill hits. + * + * Catalog `effects[]` strings + numeric damage/force map here — not a second + * combat engine. Applies to hostiles (CombatFocus targets) and lab self-tests. + * + * Statuses: push · freeze · stun · slow · burn · root · knockup · silence · shield_break + * + * @see docs/WEAPON_SKILL_PRODUCTION_SSOT.md + */ + +import { Color } from 'three'; +import { applyKnockback } from './hitReaction.js'; +import { mmToM } from './motionMath.js'; + +/** @typedef {'push'|'freeze'|'stun'|'slow'|'burn'|'root'|'knockup'|'silence'|'shield_break'|'heal'|'focus_buff'|'ward'} StatusId */ + +/** + * @typedef {object} StatusInstance + * @property {StatusId} id + * @property {number} until elapsed time end + * @property {number} [magnitude] + * @property {string} [sourceSkillId] + * @property {object|null} [target] + */ + +/** + * Parse catalog effect text → status specs (no invented skill rows). + * @param {string[]|string|undefined} effects + * @param {{ damage?: number, force?: number, knockbackMm?: number, element?: string }} [ctx] + * @returns {{ id: StatusId, durationSec: number, magnitude: number }[]} + */ +export function parseCatalogEffects(effects, ctx = {}) { + const list = Array.isArray(effects) + ? effects + : typeof effects === 'string' + ? [effects] + : []; + const blob = list.join(' ').toLowerCase(); + const out = []; + + const pushSec = (id, durationSec, magnitude = 1) => { + if (!out.find((x) => x.id === id)) out.push({ id, durationSec, magnitude }); + }; + + if (/freeze|frozen|absolute.?zero|ice.?nova|glacial/.test(blob)) { + pushSec('freeze', parseDuration(blob, 2.5), 1); + } + if (/\bstun\b|stunned|daze|knocked.?down|interrupt/.test(blob)) { + pushSec('stun', parseDuration(blob, 1.2), 1); + } + if (/slow|chill|frost.?spark|hinder|cripple/.test(blob)) { + pushSec('slow', parseDuration(blob, 2.0), parsePct(blob, 0.35)); + } + if (/burn|ignite|ember|inferno|hellstorm|dot|damage over/.test(blob)) { + pushSec('burn', parseDuration(blob, 3.0), Math.max(2, (ctx.damage || 10) * 0.15)); + } + if (/root|snare|entangle|vine.?lash|immobil/.test(blob)) { + pushSec('root', parseDuration(blob, 1.8), 1); + } + if (/silence|interrupt.?cast|anti.?magic/.test(blob)) { + pushSec('silence', parseDuration(blob, 2.0), 1); + } + if (/knock.?up|launch|airborne|uppercut/.test(blob)) { + pushSec('knockup', 0.5, ctx.knockupVy ?? 3.2); + } + if (/push|knock.?back|force|shove|slam|bash/.test(blob) || (ctx.force && ctx.force > 6)) { + pushSec('push', 0.35, ctx.knockbackMm ?? 180); + } + if (/shield.?break|guard.?break|armor.?break/.test(blob)) { + pushSec('shield_break', 0.1, 1); + } + if (/heal|sprout|radiant|restore/.test(blob) || (ctx.damage != null && ctx.damage < 0)) { + pushSec('heal', 0.1, Math.abs(ctx.damage || 12)); + } + if (/\+spell|\+dmg|next.?spell|focus|power.?stance|take.?aim/.test(blob)) { + pushSec('focus_buff', parseDuration(blob, 3), parsePct(blob, 0.35) + 1); + } + // Take Cover / Guard Stance / Nature Ward — self DR (catalog "-dmg taken 2s" etc.) + if ( + /ward|guard.?stance|defense|\+def|take.?cover|cover|−15%|-\d+%\s*damage|-\s*dmg\s*taken|dmg\s*taken|damage\s*taken/.test( + blob + ) + ) { + const mag = /-\d+%/.test(blob) ? parsePct(blob, 0.15) : parsePct(blob, 0.2); + pushSec('ward', parseDuration(blob, 2), mag); + } + // Suppressing Shot etc. — enemy attack / fire-rate slow (already caught by /slow/) + if (/slow\s*fire|fire\s*rate|suppress/.test(blob) && !out.find((x) => x.id === 'slow')) { + pushSec('slow', parseDuration(blob, 2.0), parsePct(blob, 0.35)); + } + + // Element defaults when catalog text empty but element known + if (!out.length && ctx.element) { + const el = String(ctx.element).toLowerCase(); + if (el === 'ice' || el === 'frost') pushSec('slow', 1.2, 0.25); + if (el === 'fire') pushSec('burn', 2.0, Math.max(2, (ctx.damage || 10) * 0.1)); + if (el === 'storm') pushSec('stun', 0.4, 0.5); + } + + return out; +} + +/** + * @param {string} blob + * @param {number} fallback + */ +function parseDuration(blob, fallback) { + const m = /(\d+(?:\.\d+)?)\s*s(?:ec)?/.exec(blob); + if (m) return Math.min(12, Math.max(0.2, Number(m[1]))); + return fallback; +} + +/** + * @param {string} blob + * @param {number} fallback 0..1 + */ +function parsePct(blob, fallback) { + const m = /(\d+)\s*%/.exec(blob); + if (m) return Math.min(0.9, Math.max(0.05, Number(m[1]) / 100)); + return fallback; +} + +/** + * Runtime status registry on combat host. + */ +export class SkillStatusSystem { + /** + * @param {{ + * onToast?: (s: string) => void, + * getElapsed?: () => number + * }} [opts] + */ + constructor(opts = {}) { + this.onToast = opts.onToast || (() => {}); + this.getElapsed = opts.getElapsed || (() => 0); + /** @type {Map} targetId → statuses */ + this._byTarget = new Map(); + /** @type {Map} targetId → mesh/ref */ + this._targets = new Map(); + } + + /** + * Apply hit package from a production skill. + * @param {{ + * target: { id?: string, point?: import('three').Vector3, mesh?: object, kind?: string }|null, + * skill?: object, + * hit?: object, + * character?: object, + * physics?: object, + * drc?: object, + * applyToPlayer?: boolean + * }} opts + */ + applyHit(opts) { + const skill = opts.skill || {}; + const hit = opts.hit || {}; + const target = opts.target; + const elapsed = this.getElapsed(); + const statuses = skill.statuses?.length + ? skill.statuses + : parseCatalogEffects(skill.effects, { + damage: skill.damage ?? hit.damage, + force: hit.force ?? skill.force, + knockbackMm: hit.knockbackMm ?? skill.knockbackMm, + knockupVy: hit.knockupVy ?? skill.knockupVy, + element: skill.element || hit.element + }); + + // Damage toast / number (hosts wire HP later) + const dmg = Number(skill.damage ?? hit.damage ?? 0); + // Self / player hits always register under 'player' so burn SFX / move mul stay consistent + const tid = opts.applyToPlayer + ? 'player' + : target?.id || target?.mesh?.uuid || 'aim'; + + /** + * Player defensive SSOT (P0): + * Spatial contact hits (point present) → invuln / weapon-volume parry cancel + * damage · status · knockback. Buffs (ward/heal) usually have no point — pass through. + * @see DrcCombatController.tryParryBlock · weaponVolumeBlocks + */ + const isPlayerHit = + !!opts.applyToPlayer || + target?.kind === 'self' || + target?.kind === 'player' || + target?.id === 'player'; + const attackPoint = hit.point || target?.point || null; + if (isPlayerHit && attackPoint && opts.drc) { + const drc = opts.drc; + if (drc.isInvincible || (Number(drc.invuln) || 0) > 0) { + this.onToast('Invincible'); + return { targetId: 'player', damage: 0, statuses: [], blocked: 'invuln' }; + } + const attackR = + Number(hit.contactRadius ?? hit.radius ?? hit.hitRadius ?? 0.18) || 0.18; + if (typeof drc.tryParryBlock === 'function' && drc.tryParryBlock(attackPoint, attackR)) { + // tryParryBlock already toasts "Parried!" + VFX + return { targetId: 'player', damage: 0, statuses: [], blocked: 'parry', damageWas: dmg }; + } + } + + if (target?.mesh) this._targets.set(tid, target); + if (opts.applyToPlayer) this._targets.set('player', target || { id: 'player', kind: 'player' }); + + const applied = []; + for (const st of statuses) { + const until = elapsed + (st.durationSec || 1); + const inst = { + id: st.id, + until, + magnitude: st.magnitude ?? 1, + sourceSkillId: skill.id, + target + }; + if (!this._byTarget.has(tid)) this._byTarget.set(tid, []); + // Replace same status id + const arr = this._byTarget.get(tid).filter((x) => x.id !== st.id); + arr.push(inst); + this._byTarget.set(tid, arr); + this._applyVisual(tid, inst); + applied.push(st.id); + + // Physics statuses + if (st.id === 'push' && (target?.kind === 'hostile' || opts.applyToPlayer)) { + applyKnockback( + { character: opts.character, physics: opts.physics, drc: opts.drc }, + { + forward: hit.forward, + knockbackMm: st.magnitude || hit.knockbackMm || 180, + knockupVy: hit.knockupVy ?? 0.8, + playAnim: true + } + ); + } + if (st.id === 'knockup') { + applyKnockback( + { character: opts.character, physics: opts.physics, drc: opts.drc }, + { + forward: hit.forward, + knockbackMm: 40, + knockupVy: st.magnitude || 3.2, + playAnim: true + } + ); + } + if (st.id === 'freeze' && target?.mesh) { + target.mesh.userData = target.mesh.userData || {}; + target.mesh.userData.frozenUntil = until; + target.mesh.userData.frozen = true; + target.mesh.userData.statusLocked = true; + } + if (st.id === 'stun' && target?.mesh) { + target.mesh.userData = target.mesh.userData || {}; + target.mesh.userData.stunnedUntil = until; + target.mesh.userData.statusLocked = true; + } + if (st.id === 'root' && target?.mesh) { + target.mesh.userData = target.mesh.userData || {}; + target.mesh.userData.rootedUntil = until; + target.mesh.userData.statusLocked = true; + } + if (st.id === 'slow' && target?.mesh) { + target.mesh.userData = target.mesh.userData || {}; + target.mesh.userData.slowUntil = until; + target.mesh.userData.slowMul = 1 - Math.min(0.85, st.magnitude || 0.35); + } + } + + // Direct push from hit physics even without status text + if ( + !applied.includes('push') && + hit.knockbackMm > 40 && + (opts.applyToPlayer || target?.kind === 'self') + ) { + applyKnockback( + { character: opts.character, physics: opts.physics, drc: opts.drc }, + { + forward: hit.forward, + knockbackMm: hit.knockbackMm, + knockupVy: hit.knockupVy, + playAnim: true + } + ); + applied.push('push'); + } + + const tags = [ + dmg ? `${dmg} dmg` : null, + ...applied.map((a) => a.toUpperCase()), + skill.element || hit.element || null + ].filter(Boolean); + if (tags.length) this.onToast(`Impact · ${tags.join(' · ')}`); + return { targetId: tid, damage: dmg, statuses: applied }; + } + + /** + * @param {string} targetId + * @param {StatusId} id + */ + hasStatus(targetId, id) { + const elapsed = this.getElapsed(); + const arr = this._byTarget.get(targetId) || []; + return arr.some((s) => s.id === id && s.until > elapsed); + } + + /** + * Movement multiplier for a target (1 = free, 0 = locked). + * @param {string} targetId + */ + moveMul(targetId) { + const elapsed = this.getElapsed(); + const arr = this._byTarget.get(targetId) || []; + let mul = 1; + for (const s of arr) { + if (s.until <= elapsed) continue; + if (s.id === 'freeze' || s.id === 'stun' || s.id === 'root') return 0; + if (s.id === 'slow') mul = Math.min(mul, 1 - Math.min(0.85, s.magnitude || 0.35)); + } + return mul; + } + + /** + * @param {number} elapsed + */ + update(elapsed) { + for (const [tid, arr] of this._byTarget) { + const live = arr.filter((s) => s.until > elapsed); + if (live.length !== arr.length) { + this._byTarget.set(tid, live); + const mesh = this._targets.get(tid)?.mesh; + if (mesh?.userData) { + if (!live.find((s) => s.id === 'freeze')) { + mesh.userData.frozen = false; + mesh.userData.frozenUntil = 0; + } + if (!live.find((s) => s.id === 'stun')) mesh.userData.stunnedUntil = 0; + if (!live.find((s) => s.id === 'root')) mesh.userData.rootedUntil = 0; + if (!live.find((s) => ['freeze', 'stun', 'root'].includes(s.id))) { + mesh.userData.statusLocked = false; + } + } + } + if (!live.length) this._byTarget.delete(tid); + } + } + + /** + * @param {string} tid + * @param {StatusInstance} inst + */ + _applyVisual(tid, inst) { + const ref = this._targets.get(tid); + const mesh = ref?.mesh; + if (!mesh?.traverse) return; + const colors = { + freeze: 0x88ddff, + stun: 0xffe066, + slow: 0xa0c4ff, + burn: 0xff6622, + root: 0x66aa44, + silence: 0xcc88ff + }; + const hex = colors[inst.id]; + if (!hex) return; + mesh.traverse((o) => { + if (!o.isMesh || !o.material?.emissive) return; + try { + if (!o.userData._statusEm) o.userData._statusEm = o.material.emissive.getHex?.(); + o.material.emissive.setHex(hex); + o.material.emissiveIntensity = Math.max(o.material.emissiveIntensity || 0, 0.55); + } catch { + /* */ + } + }); + } +} + +export { mmToM }; diff --git a/src/combat/staffSignatureSkills.js b/src/combat/staffSignatureSkills.js new file mode 100644 index 0000000..03b331a --- /dev/null +++ b/src/combat/staffSignatureSkills.js @@ -0,0 +1,203 @@ +/** + * Staff signature elemental skills — Inferno, Blizzard, Warp, Quake, Tempest. + * + * One signature per Warlords staff brand (ELEMENT_META staffWeaponId). + * Bound into castingSpellKit page 2 / ultimate slots and learnable via AI loop. + * + * Anim: magic cast · VFX: vfxCatalog · Ability: Fire/Water/Earth/Wind path. + * @see castingSpellKit.js · docs/PRODUCTION_CONTROLLER_SSOT.md + */ + +import { CASTING_ELEMENT_PHASE_VFX } from './elementWeaponSkills.js'; + +const F = CASTING_ELEMENT_PHASE_VFX.fire; +const W = CASTING_ELEMENT_PHASE_VFX.ice; +const E = CASTING_ELEMENT_PHASE_VFX.nature; +const A = CASTING_ELEMENT_PHASE_VFX.storm; +const R = CASTING_ELEMENT_PHASE_VFX.arcane; +const CAST_CLIP = 'magic/standing 1h cast spell 01'; + +/** + * @typedef {import('./castingSpellKit.js').CastingSpell} CastingSpell + */ + +/** + * Signature skills — production names agents should learn first. + * @type {readonly import('./castingSpellKit.js').CastingSpell[]} + */ +export const STAFF_SIGNATURE_SKILLS = Object.freeze([ + { + id: 'casting_inferno', + catalogSkillId: 'staff_inferno', + label: 'Inferno', + description: 'Fire Staff signature — dense volumetric fire stream + white-core impact.', + element: 'fire', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'FireAbility', + castEffectId: F.cast, + travelEffectId: F.travel, + impactEffectId: 'inferno', + staffWeaponId: F.staffWeaponId, + rangeM: 18, + cooldown: 6.5, + castDuration: 1.15, + staminaCost: 18, + manaCost: 22, + slot: 10, + slotType: 'ultimate', + learnFrom: 'FireAbility volume · VolumetricFireMaterial · settings.fire.volumeSteps', + assets: [ + 'src/abilities/FireAbility.js', + 'src/materials/VolumetricFireMaterial.js', + 'vfx:inferno|fireball|fire_hand' + ] + }, + { + id: 'casting_blizzard', + catalogSkillId: 'staff_blizzard', + label: 'Blizzard', + description: 'Ice Staff signature — wide frost AOE place + frost_wave crown.', + element: 'ice', + pathMode: 'aoe', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WaterAbility', + castEffectId: W.cast, + travelEffectId: W.travel, + impactEffectId: 'frost_wave', + staffWeaponId: W.staffWeaponId, + rangeM: 12, + cooldown: 7.0, + castDuration: 1.1, + staminaCost: 16, + manaCost: 24, + slot: 11, + slotType: 'ultimate', + learnFrom: 'WaterAbility surface march · frost_wave · settings.water', + assets: [ + 'src/abilities/WaterAbility.js', + 'src/materials/OceanWaterMaterial.js', + 'vfx:frost_wave|moon_beam' + ] + }, + { + id: 'casting_warp', + catalogSkillId: 'staff_warp', + label: 'Warp', + description: 'Arcane signature — short blink stream + arcane impact (void displacement).', + element: 'arcane', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'arcane', + castEffectId: R.cast, + travelEffectId: 'chain_lightning', + impactEffectId: 'arcane_swirl', + staffWeaponId: R.staffWeaponId, + rangeM: 10, + cooldown: 5.5, + castDuration: 0.7, + staminaCost: 14, + manaCost: 20, + slot: 12, + slotType: 'ultimate', + learnFrom: 'arcane phase VFX · short path blink feel · distortion optional', + assets: [ + 'src/combat/elementWeaponSkills.js#arcane', + 'vfx:arcane_swirl|chain_lightning', + 'src/postprocessing/DistortionShader.js' + ] + }, + { + id: 'casting_quake', + catalogSkillId: 'staff_quake', + label: 'Quake', + description: 'Nature Staff signature — earth wall/spikes along path + tower impact.', + element: 'nature', + pathMode: 'wall', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'EarthAbility', + castEffectId: E.cast, + travelEffectId: E.travel, + impactEffectId: 'earth_surge', + staffWeaponId: E.staffWeaponId, + rangeM: 14, + cooldown: 7.5, + castDuration: 1.2, + staminaCost: 20, + manaCost: 22, + slot: 13, + slotType: 'ultimate', + learnFrom: 'EarthAbility pave + fracture · settings.earth crust/tower', + assets: [ + 'src/abilities/EarthAbility.js', + 'src/materials/RockMaterial.js', + 'vfx:earth_surge' + ] + }, + { + id: 'casting_tempest', + catalogSkillId: 'staff_tempest', + label: 'Tempest', + description: 'Storm Staff signature — long wind stream + lightning burst impact.', + element: 'storm', + pathMode: 'stream', + style: 'spell', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityClass: 'WindAbility', + castEffectId: A.cast, + travelEffectId: A.travel, + impactEffectId: 'ice_lightning_burst', + staffWeaponId: A.staffWeaponId, + rangeM: 16, + cooldown: 6.0, + castDuration: 1.0, + staminaCost: 16, + manaCost: 20, + slot: 14, + slotType: 'ultimate', + learnFrom: 'WindAbility silk sheets · residual after cast · ice_lightning_burst', + assets: [ + 'src/abilities/WindAbility.js', + 'src/materials/WindMaterial.js', + 'vfx:ice_lightning_burst|chain_lightning' + ] + } +]); + +/** staffWeaponId → signature skill */ +export const SIGNATURE_BY_STAFF = Object.freeze( + Object.fromEntries(STAFF_SIGNATURE_SKILLS.map((s) => [s.staffWeaponId, s])) +); + +/** element → signature */ +export const SIGNATURE_BY_ELEMENT = Object.freeze( + Object.fromEntries(STAFF_SIGNATURE_SKILLS.map((s) => [s.element, s])) +); + +/** + * @param {string} element fire|water|earth|wind|arcane + */ +export function signatureForElement(element) { + return SIGNATURE_BY_ELEMENT[element] || null; +} + +/** + * @param {string} staffWeaponId staffFire|staffIce|… + */ +export function signatureForStaff(staffWeaponId) { + return SIGNATURE_BY_STAFF[staffWeaponId] || null; +} diff --git a/src/combat/staffWeaponSkillsBind.js b/src/combat/staffWeaponSkillsBind.js new file mode 100644 index 0000000..38221fe --- /dev/null +++ b/src/combat/staffWeaponSkillsBind.js @@ -0,0 +1,403 @@ +/** + * STAFF skills = master-weaponSkills / WEAPON_SKILLS.html only. + * + * Does **not** invent skills, traps, or parallel systems. + * Only fills empty prefab/anim/VFX so existing catalog ids play with + * lab transit (Fire|Water|Earth|Wind Ability + staff-type textures). + * + * Source: https://info.grudge-studio.com/api/v1/master-weaponSkills.json + * Browse: https://info.grudge-studio.com/WEAPON_SKILLS.html + */ + +import { CASTING_ELEMENT_PHASE_VFX, normalizeElement } from './elementWeaponSkills.js'; +import { abilityKeyForElement } from '../config/settings.js'; +import { + staffProjectileMeshUrl, + STAFF_CHARGE, + STAFF_NORMAL_ATTACK +} from '../vfx/staffOrbVfx.js'; + +const CAST_CLIP = 'magic/standing 1h cast spell 01'; + +const ABILITY_CLASS = { + fire: 'FireAbility', + water: 'WaterAbility', + earth: 'EarthAbility', + wind: 'WindAbility' +}; + +/** + * Staff school → product element + existing Ability texture/style. + * Schools come from catalog skill id/name/damageType — not new weapons. + */ +const SCHOOL = { + fire: { + element: 'fire', + cast: 'fire_hand', + travel: 'fireball', + impact: 'inferno', + style: 'volley' + }, + frost: { + element: 'ice', + cast: 'arcane_swirl', + travel: 'moon_beam', + impact: 'frost_wave', + style: 'groundFlood' + }, + holy: { + element: 'holy', + cast: 'moon_beam', + travel: 'moon_beam', + impact: 'moon_beam', + style: 'radiance' + }, + lightning: { + element: 'storm', + cast: 'arcane_swirl', + travel: 'chain_lightning', + impact: 'ice_lightning_burst', + style: 'lightning' + }, + nature: { + element: 'nature', + cast: 'earth_surge', + travel: 'earth_surge', + impact: 'earth_surge', + style: 'vineLash' + }, + arcane: { + element: 'arcane', + cast: 'arcane_swirl', + travel: 'chain_lightning', + impact: 'inferno', + style: 'voidBolt' + } +}; + +/** + * Catalog id → school (only ids that exist on WEAPON_SKILLS STAFF). + * Effects follow catalog text (burn, chill, heal, shield, AoE, signature). + */ +const CATALOG_SCHOOL = Object.freeze({ + // Primaries / secondaries / abilities (catalog written) + staff_fire_bolt: 'fire', + staff_frost_bolt: 'frost', + staff_holy_light: 'holy', + staff_flame_wave: 'fire', + staff_ice_nova: 'frost', + staff_divine_wave: 'holy', + staff_inferno_shield: 'fire', + staff_glacial_shield: 'frost', + staff_meteor_strike: 'fire', + staff_blizzard: 'frost', + staff_radiant_heal: 'holy', + staff_hellstorm: 'fire', + staff_absolute_zero: 'frost', + // Signatures on STAFF type (catalog) + staff_flame_nova: 'fire', + staff_iceblood_eruption: 'frost', + staff_holy_beacon: 'holy', + staff_sacred_apocalypse: 'holy', + staff_storm_call: 'lightning', + staff_thunder_cataclysm: 'lightning', + staff_wrath_of_the_tempest: 'lightning', + staff_inferno_incarnate: 'fire', + staff_generals_decree: 'arcane', + staff_wrath_of_the_wilds: 'nature', + staff_rune_apocalypse: 'arcane', + staff_demon_lords_wrath: 'fire', + staff_ghost_executioner: 'arcane', + staff_thundergods_judgment: 'lightning', + staff_earthquake: 'nature', + staff_crusaders_light: 'holy', + staff_manifest_destiny: 'holy', + staff_royal_decree: 'arcane', + staff_supernova: 'fire', + staff_arcane_apotheosis: 'arcane', + staff_army_of_the_dead: 'arcane', + staff_retribution: 'holy', + staff_omniscience: 'arcane', + staff_storm_of_arrows: 'lightning', + staff_warchiefs_fury: 'nature', + staff_natures_fury: 'nature', + staff_verdant_cataclysm: 'nature', + staff_wild_apocalypse: 'nature', + // T0 STAFF starters + t0_staff_practice_root: 'nature', + t0_staff_nature_ward: 'nature', + t0_staff_vine_lash: 'nature', + t0_staff_healing_sprout: 'nature' +}); + +/** + * Path mode from catalog role/text only (stream bolt · aoe · spikes · wall/shield). + * @param {object} skill + * @param {string} school + */ +function pathModeFromCatalog(skill, school) { + const blob = `${skill?.id || ''} ${skill?.name || ''} ${skill?.description || ''} ${(skill?.effects || []).join(' ')}`.toLowerCase(); + const slot = String(skill?.slotType || skill?.slotLabel || '').toLowerCase(); + + if (/shield|ward|absorb|reflect/.test(blob)) return 'wall'; + if (/meteor|delay/.test(blob)) return 'stream'; + if (/earthquake|quake/.test(blob)) return 'wall'; + if (/vine.?lash|spike|eruption/.test(blob)) return 'spikes'; + if ( + /aoe|nova|wave|blizzard|hellstorm|cataclysm|apocalypse|zone|field|channel|purge|massive|large/.test( + blob + ) || + slot === 'secondary' || + slot === 'ultimate' + ) { + // Single-target primaries stay stream even if ultimate is AoE + if (slot === 'primary' && !/aoe|nova|wave/.test(blob)) return 'stream'; + return 'aoe'; + } + if (school === 'lightning' && /storm.?call|thunder|judgment|arrow/.test(blob)) return 'stream'; + return 'stream'; +} + +/** + * Presentation flag for existing VfxDirector recipes only (not new skill systems). + * @param {object} skill + * @param {string} school + * @param {string} pathMode + */ +function presentationFromCatalog(skill, school, pathMode) { + const blob = `${skill?.id || ''} ${skill?.name || ''} ${skill?.description || ''}`.toLowerCase(); + if (/meteor|hellstorm|supernova|incarnate|demon/.test(blob)) return 'meteor'; + if (pathMode === 'wall' || /shield|ward/.test(blob)) return 'shield'; + if (school === 'lightning') return 'lightning'; + if (school === 'holy' || /heal|radiant|divine|beacon|crusader|retribution|salvation/.test(blob)) + return 'radiance'; + if (school === 'nature') return 'vineLash'; + if (school === 'frost') return pathMode === 'aoe' ? 'groundFlood' : 'groundFlood'; + if (school === 'fire') return pathMode === 'stream' && !/meteor/.test(blob) ? 'volley' : 'volley'; + if (school === 'arcane') return 'voidBolt'; + return SCHOOL[school]?.style || 'volley'; +} + +/** + * Infer school when id not in CATALOG_SCHOOL (unknown future catalog row). + * @param {object} skill + */ +function schoolFromSkill(skill) { + const id = skill?.id || ''; + if (CATALOG_SCHOOL[id]) return CATALOG_SCHOOL[id]; + + const dmg = String(skill?.damageType || '').toLowerCase(); + if (dmg === 'fire') return 'fire'; + if (dmg === 'frost' || dmg === 'ice') return 'frost'; + if (dmg === 'holy' || dmg === 'light') return 'holy'; + if (dmg === 'lightning' || dmg === 'storm') return 'lightning'; + if (dmg === 'nature' || dmg === 'earth') return 'nature'; + if (dmg === 'arcane') return 'arcane'; + + const blob = `${id} ${skill?.name || ''} ${skill?.description || ''}`.toLowerCase(); + if (/fire|flame|inferno|hell|meteor|ember|demon|nova|supernova/.test(blob)) return 'fire'; + if (/frost|ice|blizzard|glacial|chill|absolute.?zero|iceblood/.test(blob)) return 'frost'; + if (/holy|divine|sacred|radiant|crusader|beacon|retribution|light|salvation/.test(blob)) + return 'holy'; + if (/storm|thunder|lightning|tempest|arrow/.test(blob)) return 'lightning'; + if (/nature|vine|earth|quake|wild|verdant|root|sprout|ward|warchief/.test(blob)) return 'nature'; + return 'arcane'; +} + +/** + * Build runtime bind from a **catalog** skill row only. + * @param {object} skill flattened master-weaponSkills skill + */ +export function bindFromCatalogSkill(skill) { + if (!skill?.id) return null; + const school = schoolFromSkill(skill); + const s = SCHOOL[school] || SCHOOL.arcane; + const pathMode = pathModeFromCatalog(skill, school); + const presentation = presentationFromCatalog(skill, school, pathMode); + const el = normalizeElement(s.element); + const key = abilityKeyForElement(el); + const phase = CASTING_ELEMENT_PHASE_VFX[el] || CASTING_ELEMENT_PHASE_VFX.arcane; + + // Shields: aura-style impacts from existing catalog effects + let cast = s.cast; + let travel = s.travel; + let impact = s.impact; + if (pathMode === 'wall' && school === 'fire') { + cast = travel = impact = 'fire_aura'; + } else if (pathMode === 'wall' && school === 'frost') { + cast = 'frost_wave'; + travel = 'moon_beam'; + impact = 'frost_wave'; + } else if (pathMode === 'wall' && school === 'nature') { + cast = travel = impact = 'earth_surge'; + } + // Ice nova / freeze blast → around_caster freeze presentation + if ( + school === 'frost' && + (pathMode === 'aoe' || /ice.?nova|absolute.?zero|freeze|blizzard/i.test(`${skill?.id || ''} ${skill?.name || ''}`)) + ) { + cast = 'frost_wave'; + travel = 'moon_beam'; + impact = 'frost_wave'; + } + + const castDuration = + skill.castTime != null && skill.castTime > 0 ? Number(skill.castTime) : 0.85; + const rangeM = skill.range != null && skill.range > 0 ? Number(skill.range) : 14; + + // Slot-1 / primary stream bolts share staff normal attack contract (orb + charge) + const isPrimary = + String(skill?.slotType || skill?.slotLabel || '').toLowerCase() === 'primary' || + /practice|bolt|spark|ping|root/.test(`${skill?.id || ''} ${skill?.name || ''}`.toLowerCase()); + const useOrb = pathMode === 'stream' || isPrimary; + + return { + catalogSkillId: skill.id, + name: skill.name || skill.id, + school, + element: el, + pathMode, + presentation, + castEffectId: cast, + travelEffectId: travel, + impactEffectId: impact, + abilityClass: ABILITY_CLASS[key] || 'WindAbility', + rangeM: isPrimary ? STAFF_NORMAL_ATTACK.rangeM : rangeM, + castDuration: isPrimary + ? Math.min(castDuration, STAFF_NORMAL_ATTACK.castDuration + 0.15) + : castDuration, + cooldown: + skill.cooldown != null + ? Number(skill.cooldown) + : isPrimary + ? STAFF_NORMAL_ATTACK.cooldown + : 1, + damage: skill.damage, + manaCost: skill.resourceCost?.mana ?? skill.manaCost ?? 5, + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + staffWeaponId: phase.staffWeaponId, + // Per-element orb (gd_orbs) + charge shell — individually managed by staff attack + useOrbProjectile: useOrb, + projectileMeshUrl: useOrb ? staffProjectileMeshUrl(el) : null, + chargeMeshUrl: STAFF_CHARGE.path, + // Catalog text for UI/toasts — never invent effects + description: skill.description || '', + effects: skill.effects || [], + damageType: skill.damageType || school + }; +} + +/** @param {string} skillId */ +export function staffBindFor(skillId) { + if (!skillId || !CATALOG_SCHOOL[skillId]) return null; + return bindFromCatalogSkill({ id: skillId, name: skillId }); +} + +/** + * Prefer full catalog row when available. + * @param {object} skill + */ +export function inferStaffBind(skill) { + return bindFromCatalogSkill(skill); +} + +/** + * Merge lab transit onto catalog skill in place (empty prefabs only filled). + * Does not create new skill ids. + * @param {object} skill + */ +export function enrichStaffSkill(skill) { + if (!skill?.id) return skill; + const isStaff = + /staff/i.test(skill.weaponTypeId || '') || + /^staff_|^t0_staff_/.test(skill.id) || + skill.labSlot === 'staff'; + if (!isStaff) return skill; + + const b = bindFromCatalogSkill(skill); + if (!b) return skill; + + skill.labElement = b.element; + skill.labStyle = 'spell'; + skill.labPack = b.animPack; + skill.pathMode = b.pathMode; + skill.presentation = b.presentation; + skill.castEffectId = b.castEffectId; + skill.travelEffectId = b.travelEffectId; + skill.impactEffectId = b.impactEffectId; + skill.abilityClass = b.abilityClass; + skill.animation = skill.animation || b.castClip; + skill.animRole = 'cast'; + skill.useOrbProjectile = b.useOrbProjectile; + skill.projectileMeshUrl = b.projectileMeshUrl; + skill.chargeMeshUrl = b.chargeMeshUrl; + if (skill.range == null) skill.range = b.rangeM; + if (skill.castTime == null) skill.castTime = b.castDuration; + + skill.prefab = { + ...(skill.prefab || {}), + // Preserve any non-null catalog refs; fill nulls only + castingLab: true, + castingSpellId: skill.id, + abilityClass: b.abilityClass, + pathMode: b.pathMode, + element: b.element, + school: b.school, + animPack: b.animPack, + animationClip: skill.prefab?.animationClip || b.castClip, + animRole: 'cast', + castEffectId: skill.prefab?.castEffectId || b.castEffectId, + travelEffectId: skill.prefab?.travelEffectId || b.travelEffectId, + impactEffectId: skill.prefab?.impactEffectId || b.impactEffectId, + presentation: b.presentation, + vfxRef: skill.prefab?.vfxRef || b.impactEffectId, + source: 'WEAPON_SKILLS STAFF · staffWeaponSkillsBind' + }; + return skill; +} + +/** Only real catalog STAFF ids we know. */ +export function allStaffSkillIds() { + return Object.keys(CATALOG_SCHOOL); +} + +/** + * DRC skill from catalog row (no invented fields beyond lab transit). + * @param {object} catalogSkill + */ +export function catalogSkillToDrc(catalogSkill) { + const s = enrichStaffSkill({ ...catalogSkill }); + const b = bindFromCatalogSkill(s); + if (!b) return null; + return { + id: s.id, + label: s.name || s.id, + slot: 0, + style: 'spell', + element: b.element, + abilityElement: b.element, + pathMode: b.pathMode, + presentation: b.presentation, + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + rangeM: b.rangeM, + cooldown: b.cooldown, + castDuration: b.castDuration, + staminaCost: 8, + manaCost: b.manaCost, + castEffectId: b.castEffectId, + travelEffectId: b.travelEffectId, + impactEffectId: b.impactEffectId, + attachToHand: true, + weaponId: b.staffWeaponId || 'staff', + catalogSkillId: s.id, + abilityClass: b.abilityClass, + damage: s.damage, + hint: `${s.name} · ${b.school} · ${b.pathMode} · catalog ${s.id}` + }; +} + +export { CATALOG_SCHOOL, SCHOOL }; diff --git a/src/combat/t0ApprenticeWand.js b/src/combat/t0ApprenticeWand.js new file mode 100644 index 0000000..83ef8eb --- /dev/null +++ b/src/combat/t0ApprenticeWand.js @@ -0,0 +1,318 @@ +/** + * T0 Apprentice Wand — three-slot starter (WEAPON_SKILLS / t0-weapons SSOT). + * + * Slot 1 · Starter Attack (fixed): Practice Bolt + * Slot 2 · Starter Style (fixed): Focus (+spell dmg 3s) + * Slot 3 · Choose One: Frost Spark | Arcane Ping (default Frost Spark) + * + * Catalog: master-weaponSkills WAND.starterSlots · t0-weapons id t0-wand + * Lab: magic anim pack · arcane/frost VFX · wind Ability path for bolts + */ + +const CAST_CLIP = 'magic/standing 1h cast spell 01'; +const ICON = '/icons/pack/weapons/staff_34.png'; +const WEAPON_ID = 't0-wand'; +const WEAPON_NAME = 'Apprentice Wand'; + +/** + * @typedef {object} T0WandSkill + * @property {string} id + * @property {string} catalogSkillId + * @property {string} label + * @property {string} description + * @property {number} slot 0|1|2 + * @property {'primary'|'secondary'|'ability'} slotType + * @property {boolean} [fixed] + * @property {boolean} [choice] + * @property {'spell'|'buff'} style + * @property {'arcane'|'frost'} element + * @property {'cast'} animRole + * @property {string} animPack + * @property {string} castClip + * @property {string} [abilityElement] AbilityManager element + * @property {string} [pathMode] + * @property {number} rangeM + * @property {number} cooldown + * @property {number} castDuration + * @property {number} staminaCost + * @property {number} manaCost + * @property {number} damage + * @property {string} [castEffectId] + * @property {string} [travelEffectId] + * @property {string} [impactEffectId] + * @property {string[]} effects + * @property {boolean} [isFocus] + * @property {number} [focusDurationSec] + * @property {number} [focusDamageMul] + */ + +/** @type {T0WandSkill} */ +export const T0_WAND_PRACTICE_BOLT = Object.freeze({ + id: 't0_wand_practice_bolt', + catalogSkillId: 't0_wand_practice_bolt', + label: 'Practice Bolt', + description: 'Flickering arcane bolt', + slot: 0, + slotType: 'primary', + fixed: true, + style: 'spell', + element: 'arcane', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityElement: 'wind', + pathMode: 'stream', + rangeM: 12, + cooldown: 0.45, + castDuration: 0.5, + staminaCost: 0, + manaCost: 4, + damage: 14, + castEffectId: 'arcane_swirl', + travelEffectId: 'chain_lightning', + impactEffectId: 'arcane_swirl', + effects: ['Starter'], + icon: ICON, + weaponId: WEAPON_ID, + hint: '1 — Auto · Practice Bolt (T0)' +}); + +/** @type {T0WandSkill} */ +export const T0_WAND_FOCUS = Object.freeze({ + id: 't0_wand_focus', + catalogSkillId: 't0_wand_focus', + label: 'Focus', + description: 'Channel focus — next spell bonus', + slot: 1, + slotType: 'secondary', + fixed: true, + style: 'buff', + element: 'arcane', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityElement: null, + pathMode: null, + rangeM: 0, + cooldown: 5, + castDuration: 0.3, + staminaCost: 0, + manaCost: 4, + damage: 0, + castEffectId: 'arcane_swirl', + travelEffectId: null, + impactEffectId: null, + effects: ['+spell dmg 3s'], + isFocus: true, + focusDurationSec: 3, + focusDamageMul: 1.35, + icon: ICON, + weaponId: WEAPON_ID, + hint: '2 — Auto · Focus (next spell +dmg 3s)' +}); + +/** @type {T0WandSkill} */ +export const T0_WAND_FROST_SPARK = Object.freeze({ + id: 't0_wand_frost_spark', + catalogSkillId: 't0_wand_frost_spark', + label: 'Frost Spark', + description: 'Tiny frost projectile', + slot: 2, + slotType: 'ability', + choice: true, + style: 'spell', + element: 'frost', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityElement: 'water', + pathMode: 'stream', + rangeM: 11, + cooldown: 4, + castDuration: 0.6, + staminaCost: 0, + manaCost: 4, + damage: 12, + castEffectId: 'arcane_swirl', + travelEffectId: 'moon_beam', + impactEffectId: 'frost_wave', + effects: ['Slow 1s'], + icon: ICON, + weaponId: WEAPON_ID, + hint: '3 — Choose · Frost Spark' +}); + +/** @type {T0WandSkill} */ +export const T0_WAND_ARCANE_PING = Object.freeze({ + id: 't0_wand_arcane_ping', + catalogSkillId: 't0_wand_arcane_ping', + label: 'Arcane Ping', + description: 'Quick arcane pulse', + slot: 2, + slotType: 'ability', + choice: true, + style: 'spell', + element: 'arcane', + animRole: 'cast', + animPack: 'magic', + castClip: CAST_CLIP, + abilityElement: 'wind', + pathMode: 'aoe', + rangeM: 8, + cooldown: 3, + castDuration: 0.4, + staminaCost: 0, + manaCost: 2, + damage: 10, + castEffectId: 'arcane_swirl', + travelEffectId: 'arcane_swirl', + impactEffectId: 'arcane_swirl', + effects: ['Low mana'], + icon: ICON, + weaponId: WEAPON_ID, + hint: '3 — Choose · Arcane Ping' +}); + +/** Slot 3 options (choose one) */ +export const T0_WAND_SLOT3_OPTIONS = Object.freeze([ + T0_WAND_FROST_SPARK, + T0_WAND_ARCANE_PING +]); + +export const T0_WAND_DEFAULT_SLOT3 = 't0_wand_frost_spark'; + +/** + * Active slot-3 choice (session). + * @type {string} + */ +let _slot3Id = T0_WAND_DEFAULT_SLOT3; + +export function setT0WandSlot3(skillId) { + const ok = T0_WAND_SLOT3_OPTIONS.some((s) => s.id === skillId); + if (ok) _slot3Id = skillId; + return _slot3Id; +} + +export function getT0WandSlot3() { + return _slot3Id; +} + +/** + * Hotbar for Apprentice Wand: [Practice Bolt, Focus, Slot3 choice]. + * Digit 4 empty (T0 is three-slot pattern). + * @returns {object[]} + */ +export function t0ApprenticeWandHotbar() { + const slot3 = + T0_WAND_SLOT3_OPTIONS.find((s) => s.id === _slot3Id) || T0_WAND_FROST_SPARK; + return [ + toDrcT0(T0_WAND_PRACTICE_BOLT, 0), + toDrcT0(T0_WAND_FOCUS, 1), + toDrcT0(slot3, 2) + ]; +} + +/** + * @param {T0WandSkill} sk + * @param {number} barSlot + */ +export function toDrcT0(sk, barSlot = sk.slot) { + return { + id: sk.id, + label: sk.label, + slot: barSlot, + style: sk.style === 'buff' ? 'spell' : sk.style, + skillKind: sk.style, + element: sk.element === 'frost' ? 'water' : sk.element === 'arcane' ? 'arcane' : sk.element, + abilityElement: sk.abilityElement, + pathMode: sk.pathMode || 'stream', + animRole: sk.animRole, + animPack: sk.animPack, + castClip: sk.castClip, + rangeM: sk.rangeM, + cooldown: sk.cooldown, + castDuration: sk.castDuration, + staminaCost: sk.staminaCost, + manaCost: sk.manaCost, + damage: sk.damage, + castEffectId: sk.castEffectId, + travelEffectId: sk.travelEffectId, + impactEffectId: sk.impactEffectId, + attachToHand: true, + weaponId: sk.weaponId || WEAPON_ID, + catalogSkillId: sk.catalogSkillId, + isFocus: !!sk.isFocus, + focusDurationSec: sk.focusDurationSec || 0, + focusDamageMul: sk.focusDamageMul || 1, + effects: sk.effects || [], + fixed: !!sk.fixed, + choice: !!sk.choice, + tier: 0, + hint: sk.hint || sk.label + }; +} + +export function allT0WandSkills() { + return [T0_WAND_PRACTICE_BOLT, T0_WAND_FOCUS, ...T0_WAND_SLOT3_OPTIONS]; +} + +/** Prefab bind for master-weaponSkills enrichment */ +export function t0WandPrefabBind(sk) { + return { + castingLab: true, + t0: true, + weaponId: WEAPON_ID, + weaponName: WEAPON_NAME, + skillId: sk.id, + animPack: sk.animPack, + animationClip: sk.castClip, + animRole: sk.animRole, + pathMode: sk.pathMode, + element: sk.element, + castEffectId: sk.castEffectId, + travelEffectId: sk.travelEffectId, + impactEffectId: sk.impactEffectId, + isFocus: !!sk.isFocus, + focusDurationSec: sk.focusDurationSec || null, + focusDamageMul: sk.focusDamageMul || null, + liveLab: 'https://casting-abilities-threejs.vercel.app/', + source: 'CastingAbilitiesThreeJS/t0ApprenticeWand' + }; +} + +export function exportT0ApprenticeWandJson() { + return { + version: '1.0.0', + generated: new Date().toISOString(), + weaponId: WEAPON_ID, + weaponName: WEAPON_NAME, + slotPattern: 'three-slot-starter', + weaponSkillsHtml: 'https://info.grudge-studio.com/WEAPON_SKILLS.html', + slots: { + slot1: { + label: 'Slot 1 · Starter Attack', + fixed: true, + auto: true, + skill: { ...T0_WAND_PRACTICE_BOLT, prefab: t0WandPrefabBind(T0_WAND_PRACTICE_BOLT) } + }, + slot2: { + label: 'Slot 2 · Starter Style', + fixed: true, + auto: true, + skill: { ...T0_WAND_FOCUS, prefab: t0WandPrefabBind(T0_WAND_FOCUS) } + }, + slot3: { + label: 'Slot 3 · Choose One', + fixed: false, + choice: true, + defaultSkillId: T0_WAND_DEFAULT_SLOT3, + options: T0_WAND_SLOT3_OPTIONS.map((s) => ({ + ...s, + prefab: t0WandPrefabBind(s) + })) + } + } + }; +} + +export { WEAPON_ID as T0_WAND_WEAPON_ID, WEAPON_NAME as T0_WAND_WEAPON_NAME }; diff --git a/src/combat/weaponChargeSystem.js b/src/combat/weaponChargeSystem.js new file mode 100644 index 0000000..9448dc3 --- /dev/null +++ b/src/combat/weaponChargeSystem.js @@ -0,0 +1,275 @@ +/** + * Weapon charge UX — hold → ticks → release fire · rest recovery · anim blend hooks. + * + * Used by flintlock **Charged Shot** (and any chargeable skill): + * - Tap: quick fire (no charge) + * - Hold: wind-up (charged-pistol blend) · cast bar ticks · intensity 1..3 + * - Release: fire with damage/cost mul · weapon rest (best rest) + * - Cancel (move / re-tap empty): short cancel rest · restore gait blend + * + * Builds on castResources.castIntensity + DRC cast bar — not a second combat engine. + * + * @see docs/PISTOL_FLINTLOCK_SSOT.md · docs/PRODUCTION_CONTROLLER_SSOT.md + */ + +import { MathUtils } from 'three'; +import { castIntensity, skillCastCosts } from './castResources.js'; +import { settings } from '../config/settings.js'; + +/** + * Defaults — overridable via settings.drc.weaponCharge + */ +export const WEAPON_CHARGE_DEFAULTS = Object.freeze({ + /** Seconds before hold counts as charge (else tap fire) */ + tapMaxSec: 0.18, + /** Full charge hold */ + maxHoldSec: 1.35, + /** Min hold to fire as charged (between tap and full) */ + minChargeSec: 0.22, + /** UI / combat tick rate for charge bar */ + tickHz: 20, + /** Weapon rest after release fire (best rest — blocks next attack) */ + restSec: 0.42, + /** Extra rest per intensity above 1 */ + restPerIntensity: 0.12, + /** Cancel / interrupt rest */ + cancelRestSec: 0.18, + /** Global weapon GCD between any weapon skills (s) */ + gcdSec: 0.12, + /** Damage / force mul at full charge */ + maxDamageMul: 2.05, + /** Hit-frame delay scale when charged (longer windup) */ + hitFrameMul: 1.15, + /** Cast bar label base */ + label: 'Charged Shot' +}); + +/** Named charge levels for toast / bar */ +export const CHARGE_LEVELS = Object.freeze([ + { min: 0, id: 'tap', label: 'Tap', mul: 1 }, + { min: 0.22, id: 'wind', label: 'Wind', mul: 1.25 }, + { min: 0.55, id: 'charged', label: 'Charged', mul: 1.55 }, + { min: 0.95, id: 'power', label: 'Power', mul: 1.85 }, + { min: 1.25, id: 'full', label: 'Full', mul: 2.05 } +]); + +/** + * @returns {typeof WEAPON_CHARGE_DEFAULTS} + */ +export function weaponChargeConfig() { + const ov = settings.drc?.weaponCharge || {}; + return { ...WEAPON_CHARGE_DEFAULTS, ...ov }; +} + +/** + * Skill should use hold-to-charge UX (firearm power / charged-pistol). + * @param {object} skill + * @param {{ animPack?: string, weaponId?: string }} [ctx] + */ +export function isChargeableWeaponSkill(skill, ctx = {}) { + if (!skill) return false; + if (skill.isReload || skill.skillKind === 'reload' || skill.isWard || skill.isFocus) { + return false; + } + if (skill.chargeable === true || skill.useWeaponCharge === true) return true; + if (skill.chargeable === false) return false; + + const blob = `${skill.id || ''} ${skill.label || ''} ${skill.animRole || ''} ${skill.catalogSkillId || ''}`.toLowerCase(); + // Explicit charged / power / fan / suppress / charged-pistol + if (/charg|power|fan|suppress|overcharg|heavy.?shot/i.test(blob)) return true; + if (skill.animRole === 'skill2' || skill.animRole === 'charged') return true; + + // Flintlock primary Practice Shot — hold for charged shot, tap for quick + if ( + (ctx.animPack === 'pistol' || /t0_gun|flint|pistol/i.test(blob + (ctx.weaponId || ''))) && + (skill.slot === 0 || skill.slotType === 'primary' || /practice.?shot|pistol_shot/i.test(blob)) + ) { + return true; + } + return false; +} + +/** + * Level from hold seconds. + * @param {number} holdSec + */ +export function chargeLevelFromHold(holdSec) { + const h = Math.max(0, holdSec); + let level = CHARGE_LEVELS[0]; + for (const L of CHARGE_LEVELS) { + if (h >= L.min) level = L; + } + return level; +} + +/** + * Progress 0..1 toward full charge. + * @param {number} holdSec + * @param {object} [cfg] + */ +export function chargeProgress01(holdSec, cfg = weaponChargeConfig()) { + return MathUtils.clamp(holdSec / Math.max(0.05, cfg.maxHoldSec), 0, 1); +} + +/** + * Damage / force multiplier from hold. + * @param {number} holdSec + * @param {object} [cfg] + */ +export function chargeDamageMul(holdSec, cfg = weaponChargeConfig()) { + const p = chargeProgress01(holdSec, cfg); + const level = chargeLevelFromHold(holdSec); + // Smooth between level muls + cap + const base = level.mul; + const top = cfg.maxDamageMul; + return MathUtils.clamp(MathUtils.lerp(base, top, p * p), 1, top); +} + +/** + * Rest duration after a successful charged (or tap) weapon fire. + * @param {number} holdSec + * @param {number} intensity + * @param {object} [cfg] + */ +export function weaponRestAfterFire(holdSec, intensity = 1, cfg = weaponChargeConfig()) { + const p = chargeProgress01(holdSec, cfg); + return cfg.restSec + Math.max(0, intensity - 1) * cfg.restPerIntensity + p * 0.08; +} + +/** + * Build cast-bar state for charge channel. + * @param {{ + * holdSec: number, + * skill?: object, + * active?: boolean + * }} opts + */ +export function chargeBarState(opts) { + const cfg = weaponChargeConfig(); + const hold = Math.max(0, opts.holdSec || 0); + const p = chargeProgress01(hold, cfg); + const level = chargeLevelFromHold(hold); + const mul = chargeDamageMul(hold, cfg); + const label = opts.skill?.label + ? `${opts.skill.label} · ${level.label}` + : `${cfg.label} · ${level.label}`; + return { + active: opts.active !== false, + label, + progress01: p, + duration: cfg.maxHoldSec, + remaining: Math.max(0, cfg.maxHoldSec - hold), + element: opts.skill?.element || 'physical', + chargeLevel: level.id, + damageMul: mul, + kind: 'weapon_charge' + }; +} + +/** + * Resource cost for charged weapon release (uses hold intensity). + * @param {object} skill + * @param {number} holdSec + */ +export function chargedSkillCosts(skill, holdSec) { + return skillCastCosts(skill, holdSec, 0); +} + +/** + * Mutable charge session (owned by DrcCombatController). + */ +export class WeaponChargeSession { + constructor() { + this.active = false; + this.slot = 0; + /** @type {object|null} */ + this.skill = null; + this.startedAt = 0; + this.holdSec = 0; + this._tickAccum = 0; + this._lastLevelId = 'tap'; + this.animRole = 'skill2'; + } + + /** + * @param {number} slot + * @param {object} skill + * @param {number} elapsed + * @param {string} [animRole] + */ + begin(slot, skill, elapsed, animRole = 'skill2') { + this.active = true; + this.slot = slot; + this.skill = skill; + this.startedAt = elapsed; + this.holdSec = 0; + this._tickAccum = 0; + this._lastLevelId = 'tap'; + this.animRole = animRole || skill.animRole || 'skill2'; + } + + /** + * @param {number} dt + * @param {number} elapsed + * @returns {{ tick: boolean, levelChanged: boolean, level: object, progress01: number, holdSec: number }} + */ + update(dt, elapsed) { + if (!this.active) { + return { tick: false, levelChanged: false, level: CHARGE_LEVELS[0], progress01: 0, holdSec: 0 }; + } + this.holdSec = Math.max(0, elapsed - this.startedAt); + const cfg = weaponChargeConfig(); + this._tickAccum += dt; + const tickPeriod = 1 / Math.max(1, cfg.tickHz); + let tick = false; + if (this._tickAccum >= tickPeriod) { + this._tickAccum = 0; + tick = true; + } + const level = chargeLevelFromHold(this.holdSec); + const levelChanged = level.id !== this._lastLevelId; + if (levelChanged) this._lastLevelId = level.id; + return { + tick, + levelChanged, + level, + progress01: chargeProgress01(this.holdSec, cfg), + holdSec: this.holdSec + }; + } + + /** + * End session — returns release payload for fire / cancel. + * @param {'release'|'cancel'} reason + */ + end(reason = 'release') { + const holdSec = this.holdSec; + const skill = this.skill; + const slot = this.slot; + const cfg = weaponChargeConfig(); + const isTap = holdSec < cfg.tapMaxSec; + const isCharged = holdSec >= cfg.minChargeSec; + const intensity = isCharged ? castIntensity(holdSec, 0) : 1; + const damageMul = isCharged ? chargeDamageMul(holdSec, cfg) : 1; + const level = chargeLevelFromHold(holdSec); + this.active = false; + this.skill = null; + this.holdSec = 0; + return { + reason, + slot, + skill, + holdSec, + isTap, + isCharged, + intensity, + damageMul, + level, + restSec: + reason === 'cancel' + ? cfg.cancelRestSec + : weaponRestAfterFire(holdSec, intensity, cfg), + costs: skill ? chargedSkillCosts(skill, isCharged ? holdSec : 0) : null + }; + } +} diff --git a/src/combat/weaponSkillProduction.js b/src/combat/weaponSkillProduction.js new file mode 100644 index 0000000..69de23d --- /dev/null +++ b/src/combat/weaponSkillProduction.js @@ -0,0 +1,729 @@ +/** + * Production weapon skill pattern — scriptable, repeatable, catalog-first. + * + * Compiles ObjectStore / master-weaponSkills / t0 rows into a full runtime + * package: anim · VFX · delivery · physics · statuses · damage · readiness. + * + * Aligns with fleet FleetWeaponSkill (epicfight) without forking a second DB. + * + * Author loop: + * 1. Catalog skill id exists (WEAPON_SKILLS / t0) — never invent ids + * 2. Optional override JSON under skills/production/.json + * 3. compileProductionWeaponSkill(catalogRow, weaponCtx) + * 4. assessProductionReadiness → green/yellow/red + * 5. DRC / fleet host cast via compiled package + * + * Scaffold: node scripts/scaffold-weapon-skill.mjs --id staff_fire_bolt + * + * @see docs/WEAPON_SKILL_PRODUCTION_SSOT.md + * @see gameopen/lib/epicfight/src/combat/fleet/weaponSkill.ts + */ + +import { bindFromCatalogSkill } from './staffWeaponSkillsBind.js'; +import { inferDeliveryPattern, enrichSkillDelivery } from './skillDelivery.js'; +import { animRoleForSkill, vfxIdForSkill, labMapForWeaponType } from '../api/weaponSkillsCatalog.js'; +import { parseCatalogEffects } from './skillStatusSystem.js'; +import { planElementalLinearCast } from './elementalLinearCast.js'; +import { isStaffNormalAttack, staffProjectileMeshUrl, STAFF_CHARGE } from '../vfx/staffOrbVfx.js'; +import { inferElementAttackKind } from '../vfx/elementAttackVfx.js'; +import { isPistolBulletSkill, PISTOL_BULLET } from '../vfx/pistolBulletVfx.js'; +import { pistolHitFrameSec } from '../config/pistolAnimSsot.js'; + +/** + * @typedef {object} ProductionAnim + * @property {string} pack sword_shield|magic|longbow|pistol + * @property {string} role attack|cast|block|attack1|… + * @property {string|null} clip pack/clip path when known + * @property {number} hitFrameDelay s + * @property {string[]} [comboStages] + */ + +/** + * @typedef {object} ProductionVfx + * @property {string|null} castEffectId + * @property {string|null} travelEffectId + * @property {string|null} impactEffectId + * @property {string|null} meshId + * @property {string|null} projectileMeshUrl + * @property {string|null} chargeMeshUrl + * @property {number} [trailColor] + * @property {number} intensity + */ + +/** + * @typedef {object} ProductionPhysics + * @property {number} force + * @property {number} knockbackMm + * @property {number} knockupVy + * @property {number} contactRadius + * @property {number} aoeM + * @property {number} speed + * @property {number} life + * @property {{ type: string, radius?: number, halfHeight?: number, halfExtents?: number[] }|null} collider + */ + +/** + * @typedef {object} ProductionWeaponSkill + * @property {string} id + * @property {string} label + * @property {string} [uuid] + * @property {string} weaponTypeId + * @property {number} slot + * @property {string} slotType + * @property {string} style melee|ranged|spell + * @property {string} [element] + * @property {number} damage + * @property {string} damageType + * @property {number} cooldown + * @property {number} castDuration + * @property {number} staminaCost + * @property {number} manaCost + * @property {number|null} rangeM + * @property {string[]} effects + * @property {object[]} statuses + * @property {ProductionAnim} anim + * @property {ProductionVfx} vfx + * @property {ProductionPhysics} physics + * @property {string} delivery + * @property {object|null} castPlan + * @property {object|null} meshKind + * @property {boolean} useOrbProjectile + * @property {boolean} isFocus + * @property {boolean} isWard + * @property {string} source + * @property {object} [prefab] + * @property {object} [overrides] + */ + +/** + * Default physics by style (SI). + * @param {string} style + * @param {object} skill + */ +export function defaultPhysicsForStyle(style, skill = {}) { + const dmg = Number(skill.damage) || 0; + const intensity = Math.min(2, 0.7 + dmg / 40); + if (style === 'melee') { + return { + force: 7 * intensity, + knockbackMm: 140 * intensity, + knockupVy: 1.2, + contactRadius: 0.65, + aoeM: /sweep|aoe|cleave|wide/i.test(`${skill.id} ${skill.name}`) ? 1.8 : 0.9, + speed: 12, + life: 0.45, + collider: { type: 'sphere', radius: 0.55 } + }; + } + if (style === 'ranged') { + const gun = + /gun|pistol|flint|t0-gun|handgun/i.test( + `${skill.id || ''} ${skill.name || ''} ${skill.weaponTypeId || ''}` + ); + return { + force: gun ? 6 * intensity : 6 * intensity, + knockbackMm: gun ? 80 * intensity : 100 * intensity, + knockupVy: gun ? 0.4 : 0.6, + contactRadius: gun ? 0.12 : 0.35, + aoeM: gun ? 0.35 : 0.8, + speed: gun ? 90 : 28, + life: gun ? 1.2 : 2.2, + collider: { type: 'sphere', radius: gun ? 0.12 : 0.28 } + }; + } + // spell + return { + force: 8 * intensity, + knockbackMm: 160 * intensity, + knockupVy: 2.0, + contactRadius: 0.4, + aoeM: skill.range ? Math.min(3, Number(skill.range) * 0.12) : 1.2, + speed: 16, + life: 2.5, + collider: { type: 'sphere', radius: 0.4 } + }; +} + +/** + * Compile catalog skill + weapon context → production package. + * + * @param {object} catalogSkill master-weaponSkills / t0 / WeaponSkillDef row + * @param {{ + * weaponTypeId?: string, + * animPack?: string, + * labStyle?: string, + * barSlot?: number, + * overrides?: object + * }} [ctx] + * @returns {ProductionWeaponSkill|null} + */ +export function compileProductionWeaponSkill(catalogSkill, ctx = {}) { + if (!catalogSkill?.id) return null; + const ov = ctx.overrides || catalogSkill.production || catalogSkill.prefab?.production || {}; + const weaponTypeId = String( + ctx.weaponTypeId || catalogSkill.weaponTypeId || 'SWORD' + ).toUpperCase(); + const lab = labMapForWeaponType(weaponTypeId); + const labStyle = ctx.labStyle || lab.style || 'melee'; + const animPack = ctx.animPack || lab.pack || 'sword_shield'; + + const nameId = `${catalogSkill.id} ${catalogSkill.name || catalogSkill.label || ''}`; + const dmg = Number(catalogSkill.damage) || 0; + const isHeal = dmg < 0 || /heal|sprout|radiant/i.test(nameId); + const isBuff = + !isHeal && + dmg === 0 && + /focus|stance|guard|ward|buff|shield|brace|aim|reload|cover|evade/i.test(nameId); + const isFocus = isBuff && /focus|take.?aim|wind.?up|power.?stance/i.test(nameId); + const isWard = isBuff && /ward|shield|guard|brace|cover/i.test(nameId); + + let style = labStyle === 'spell' ? 'spell' : labStyle === 'ranged' ? 'ranged' : 'melee'; + if (isBuff || isHeal) style = 'spell'; + if (labStyle === 'ranged' && !isBuff) style = 'ranged'; + + // Staff school bind fills empty VFX + const staffB = + bindFromCatalogSkill({ + id: catalogSkill.id, + name: catalogSkill.name || catalogSkill.label, + description: catalogSkill.description, + damageType: catalogSkill.damageType, + effects: catalogSkill.effects, + cooldown: catalogSkill.cooldown, + castTime: catalogSkill.castTime ?? catalogSkill.castDuration, + range: catalogSkill.range ?? catalogSkill.rangeM, + damage: catalogSkill.damage, + slotType: catalogSkill.slotType, + resourceCost: catalogSkill.resourceCost + }) || null; + + const element = + ov.element || + staffB?.element || + lab.element || + inferElementFromDamageType(catalogSkill.damageType) || + (style === 'spell' ? 'arcane' : 'physical'); + + const castRaw = catalogSkill.castTime ?? catalogSkill.castDuration; + const castDuration = + ov.castDuration ?? + (castRaw == null || castRaw === '' || castRaw === 'Instant' + ? 0 + : Number(castRaw) || 0); + + const cooldown = + ov.cooldown ?? + (catalogSkill.cooldown == null || catalogSkill.cooldown === '' + ? 0 + : Number(catalogSkill.cooldown) || 0); + + const rangeM = + ov.rangeM ?? + (catalogSkill.range != null && catalogSkill.range !== '' + ? Number(catalogSkill.range) + : staffB?.rangeM || (style === 'melee' ? 2.2 : style === 'ranged' ? 18 : 14)); + + const effects = Array.isArray(catalogSkill.effects) + ? catalogSkill.effects.slice() + : []; + + const physicsBase = defaultPhysicsForStyle(style, catalogSkill); + const physics = { + ...physicsBase, + ...(ov.physics || {}), + force: ov.force ?? physicsBase.force, + knockbackMm: ov.knockbackMm ?? physicsBase.knockbackMm, + knockupVy: ov.knockupVy ?? physicsBase.knockupVy, + contactRadius: ov.contactRadius ?? physicsBase.contactRadius, + aoeM: ov.aoeM ?? ov.aoeRadius ?? physicsBase.aoeM, + speed: ov.projectileSpeed ?? ov.speed ?? physicsBase.speed, + life: ov.life ?? physicsBase.life, + collider: ov.collider || physicsBase.collider + }; + + const statuses = + ov.statuses || + parseCatalogEffects(effects, { + damage: dmg, + force: physics.force, + knockbackMm: physics.knockbackMm, + knockupVy: physics.knockupVy, + element + }); + + const animRole = + ov.animRole || + animRoleForSkill({ + ...catalogSkill, + labStyle: style, + labPack: animPack, + slotType: catalogSkill.slotType + }); + + const castEffectId = + ov.castEffectId || + catalogSkill.castEffectId || + catalogSkill.prefab?.castEffectId || + staffB?.castEffectId || + null; + const travelEffectId = + ov.travelEffectId || + catalogSkill.travelEffectId || + catalogSkill.prefab?.travelEffectId || + staffB?.travelEffectId || + null; + const impactEffectId = + ov.impactEffectId || + catalogSkill.impactEffectId || + catalogSkill.prefab?.impactEffectId || + staffB?.impactEffectId || + vfxIdForSkill({ + ...catalogSkill, + labStyle: style, + castEffectId, + travelEffectId, + impactEffectId: catalogSkill.impactEffectId + }); + + const delivery = + ov.delivery || + staffB?.pathMode || + inferDeliveryPattern({ + ...catalogSkill, + style, + pathMode: staffB?.pathMode, + isFocus, + isWard, + effects + }); + + const gunBlob = `${catalogSkill.id} ${catalogSkill.name || ''} ${weaponTypeId} ${animPack}`; + const catalogProjectile = String( + catalogSkill.projectile || catalogSkill.projectileKind || '' + ).toLowerCase(); + const useBullet = + !isBuff && + !isHeal && + (ov.useBulletProjectile === true || + catalogProjectile === 'bullet' || + isPistolBulletSkill({ + ...catalogSkill, + style, + animPack, + weaponTypeId, + slot: ctx.barSlot ?? 0, + slotType: catalogSkill.slotType, + projectile: catalogSkill.projectile, + projectileKind: catalogProjectile || catalogSkill.projectileKind + }) || + // Any non-buff t0-gun / flintlock skill (Practice · Burst · Suppress) + (/t0-gun|t0_gun|flint|pistol|handgun/i.test(gunBlob) && + (style === 'ranged' || labStyle === 'ranged' || animPack === 'pistol'))); + + // Multi-hit from catalog effects/description (Burst Fire three-round = 3) + const multiBlob = `${catalogSkill.id} ${catalogSkill.name || ''} ${(effects || []).join(' ')} ${catalogSkill.description || ''}`.toLowerCase(); + let multiHit = Number(ov.multiHit || catalogSkill.multiHit) || 0; + if (!multiHit && /double|rapid|burst|multi|three.?round|3.?round|triple/i.test(multiBlob)) { + if (/three.?round|3.?round|triple/i.test(multiBlob)) multiHit = 3; + else if (/double|twin|2.?hit/i.test(multiBlob)) multiHit = 2; + else if (/burst|multi/i.test(multiBlob)) multiHit = 3; + } + + // Ward / cover duration from effects (Take Cover: "-dmg taken 2s") + const effectBlob = effects.join(' ').toLowerCase(); + const durMatch = /(\d+(?:\.\d+)?)\s*s(?:ec)?/.exec(effectBlob); + const wardDurationSec = isWard + ? Number(ov.focusDurationSec || ov.wardDurationSec || (durMatch ? durMatch[1] : 2)) || 2 + : undefined; + const focusDurationSec = isFocus + ? Number(ov.focusDurationSec || (durMatch ? durMatch[1] : 3)) || 3 + : wardDurationSec; + + const useOrb = + !useBullet && + (ov.useOrbProjectile ?? + staffB?.useOrbProjectile ?? + (style === 'spell' && + (isStaffNormalAttack({ + ...catalogSkill, + style, + slot: ctx.barSlot ?? 0, + pathMode: staffB?.pathMode + }) || + staffB?.pathMode === 'stream'))); + + const meshKind = inferElementAttackKind({ + ...catalogSkill, + element, + pathMode: staffB?.pathMode, + effects, + style, + slotType: catalogSkill.slotType + }); + + const castPlan = planElementalLinearCast( + { + ...catalogSkill, + element, + pathMode: staffB?.pathMode || 'stream', + style, + slotType: catalogSkill.slotType, + isFocus, + isWard, + effects + }, + { focusCombat: true, intensity: 1 } + ); + + const manaCost = + ov.manaCost ?? + catalogSkill.resourceCost?.mana ?? + catalogSkill.manaCost ?? + (style === 'spell' ? 4 : 0); + const staminaCost = + ov.staminaCost ?? + catalogSkill.resourceCost?.stamina ?? + catalogSkill.staminaCost ?? + (style === 'melee' ? 8 : style === 'ranged' ? 6 : 4); + + /** @type {ProductionWeaponSkill} */ + const prod = { + id: catalogSkill.id, + uuid: catalogSkill.uuid || null, + label: catalogSkill.name || catalogSkill.label || catalogSkill.id, + description: catalogSkill.description || '', + weaponTypeId, + slot: ctx.barSlot ?? catalogSkill.slot ?? 0, + slotType: catalogSkill.slotType || 'ability', + style, + skillKind: isBuff ? 'buff' : isHeal ? 'heal' : style, + element, + abilityElement: staffB?.element || element, + damage: dmg, + damageType: catalogSkill.damageType || (style === 'spell' ? element : 'physical'), + cooldown, + castDuration, + staminaCost, + manaCost, + rangeM, + effects, + statuses, + anim: { + pack: animPack, + role: animRole, + clip: + ov.animClip || + catalogSkill.animation || + catalogSkill.prefab?.animationClip || + staffB?.castClip || + null, + hitFrameDelay: + ov.hitFrameDelay ?? + (useBullet + ? pistolHitFrameSec(catalogSkill) + : style === 'melee' + ? 0.18 + : castDuration * 0.85), + comboStages: ov.comboStages || null + }, + vfx: { + castEffectId, + travelEffectId, + impactEffectId, + meshId: + ov.meshId || + (useBullet ? 'bullet1' : useOrb ? `orb-${element === 'ice' ? 'ice' : element}` : null), + projectileMeshUrl: + ov.projectileMeshUrl || + (useBullet ? PISTOL_BULLET.meshUrl : null) || + staffB?.projectileMeshUrl || + (useOrb ? staffProjectileMeshUrl(element) : null), + chargeMeshUrl: useBullet + ? null + : ov.chargeMeshUrl || staffB?.chargeMeshUrl || STAFF_CHARGE.path, + trailColor: ov.trailColor || (useBullet ? PISTOL_BULLET.trailColor : undefined), + intensity: ov.intensity ?? 1 + }, + physics, + delivery, + pathMode: staffB?.pathMode || (typeof delivery === 'string' && delivery.startsWith('path_') + ? delivery.replace('path_', '') + : style === 'spell' + ? 'stream' + : null), + presentation: staffB?.presentation || null, + castPlan, + meshKind, + useOrbProjectile: !!useOrb, + useBulletProjectile: !!useBullet, + projectileKind: useBullet ? 'bullet' : useOrb ? 'orb' : null, + projectile: useBullet + ? 'bullet' + : catalogSkill.projectile || null, + multiHit: multiHit || undefined, + isFocus, + isWard, + isHeal, + focusDurationSec, + focusDamageMul: isFocus ? 1.35 : undefined, + /** Take Cover / wards: fraction of damage reduced while active */ + wardDamageReduce: isWard + ? (() => { + if (ov.wardDamageReduce != null) return Number(ov.wardDamageReduce); + const pct = /(\d+)\s*%/.exec(effectBlob); + return pct ? Math.min(0.9, Number(pct[1]) / 100) : 0.2; + })() + : undefined, + catalogSkillId: catalogSkill.id, + source: catalogSkill.source || 'catalog', + prefab: catalogSkill.prefab || null, + overrides: ov, + icon: catalogSkill.icon || null, + iconUrl: catalogSkill.iconUrl || null, + // DRC-compatible flat fields + castEffectId, + travelEffectId, + impactEffectId, + projectileMeshUrl: + ov.projectileMeshUrl || + (useBullet ? PISTOL_BULLET.meshUrl : null) || + staffB?.projectileMeshUrl || + (useOrb ? staffProjectileMeshUrl(element) : null), + chargeMeshUrl: useBullet + ? null + : ov.chargeMeshUrl || staffB?.chargeMeshUrl || STAFF_CHARGE.path, + force: physics.force, + knockbackMm: physics.knockbackMm, + knockupVy: physics.knockupVy, + aoe: physics.aoeM, + projectileSpeed: physics.speed + }; + + // Enrich delivery mesh URLs + const enriched = enrichSkillDelivery(prod); + prod.delivery = enriched.delivery || prod.delivery; + prod.summonMeshUrl = enriched.summonMeshUrl; + if (enriched.projectileMeshUrl) prod.projectileMeshUrl = enriched.projectileMeshUrl; + + return prod; +} + +/** + * @param {string} [dt] + */ +function inferElementFromDamageType(dt) { + const t = String(dt || '').toLowerCase(); + if (t === 'fire') return 'fire'; + if (t === 'frost' || t === 'ice' || t === 'water') return 'ice'; + if (t === 'nature' || t === 'earth') return 'nature'; + if (t === 'lightning' || t === 'storm' || t === 'wind') return 'storm'; + if (t === 'holy' || t === 'light') return 'holy'; + if (t === 'arcane') return 'arcane'; + if (t === 'physical') return 'physical'; + return null; +} + +/** + * Production readiness (fleet-aligned + casting layers). + * @param {ProductionWeaponSkill} skill + */ +export function assessProductionReadiness(skill) { + const missing = []; + const warnings = []; + if (!skill?.id) missing.push('id'); + if (!skill?.label) missing.push('label'); + if (skill?.cooldown == null) missing.push('cooldown'); + if (skill?.damage == null) missing.push('damage'); + if (skill?.staminaCost == null && skill?.manaCost == null) missing.push('resourceCost'); + if (!skill?.anim?.role && !skill?.anim?.clip) missing.push('anim.role|clip'); + if (!skill?.vfx?.castEffectId && !skill?.vfx?.impactEffectId && !skill?.vfx?.projectileMeshUrl) { + warnings.push('no VFX ids / projectile mesh'); + } + if (!skill?.physics?.collider && !skill?.vfx?.projectileMeshUrl && !skill?.physics?.aoeM) { + warnings.push('no collider/projectile/aoe — pure buff?'); + } + if (!skill?.statuses?.length && skill?.style !== 'spell') { + warnings.push('no parsed statuses from effects[]'); + } + if (skill?.style === 'spell' && !skill?.element) warnings.push('spell without element'); + if (!skill?.delivery) warnings.push('no delivery pattern'); + + const score = + missing.length === 0 ? (warnings.length === 0 ? 'green' : 'yellow') : 'red'; + + return { + id: skill?.id || 'unknown', + ok: missing.length === 0, + score, + missing, + warnings, + layers: { + anim: !!(skill?.anim?.role || skill?.anim?.clip), + vfx: !!(skill?.vfx?.castEffectId || skill?.vfx?.impactEffectId), + physics: !!skill?.physics, + statuses: (skill?.statuses || []).map((s) => s.id), + delivery: skill?.delivery || null, + linear: skill?.castPlan?.linearId || null, + meshKind: skill?.meshKind?.kind || null + } + }; +} + +/** + * Convert production package → DRC hotbar skill shape (drop-in for useSkill). + * @param {ProductionWeaponSkill} prod + */ +export function productionToDrcSkill(prod) { + if (!prod) return null; + return { + id: prod.id, + label: prod.label, + slot: prod.slot, + style: prod.style, + skillKind: prod.skillKind, + element: prod.element, + abilityElement: prod.abilityElement, + damage: prod.damage, + damageType: prod.damageType, + cooldown: prod.cooldown, + castDuration: prod.castDuration, + staminaCost: prod.staminaCost, + manaCost: prod.manaCost, + rangeM: prod.rangeM, + effects: prod.effects, + statuses: prod.statuses, + animRole: prod.anim.role, + animPack: prod.anim.pack, + castClip: prod.anim.clip, + castEffectId: prod.vfx.castEffectId, + travelEffectId: prod.vfx.travelEffectId, + impactEffectId: prod.vfx.impactEffectId, + projectileMeshUrl: prod.projectileMeshUrl || prod.vfx.projectileMeshUrl, + chargeMeshUrl: prod.chargeMeshUrl || prod.vfx.chargeMeshUrl, + useOrbProjectile: prod.useOrbProjectile, + useBulletProjectile: prod.useBulletProjectile, + projectileKind: prod.projectileKind, + projectile: prod.projectile, + multiHit: prod.multiHit, + pathMode: prod.pathMode, + presentation: prod.presentation, + delivery: prod.delivery, + force: prod.physics.force, + knockbackMm: prod.physics.knockbackMm, + knockupVy: prod.physics.knockupVy, + aoe: prod.physics.aoeM, + projectileSpeed: prod.physics.speed, + isFocus: prod.isFocus, + isWard: prod.isWard, + focusDurationSec: prod.focusDurationSec, + focusDamageMul: prod.focusDamageMul, + wardDamageReduce: prod.wardDamageReduce, + catalogSkillId: prod.catalogSkillId, + description: prod.description, + icon: prod.icon, + iconUrl: prod.iconUrl, + production: prod, + hitFrameDelay: prod.anim.hitFrameDelay + }; +} + +/** + * Batch compile + readiness report for a weapon hotbar. + * @param {object[]} skills + * @param {object} weaponCtx + */ +export function compileWeaponSkillBar(skills, weaponCtx = {}) { + const compiled = []; + const report = []; + (skills || []).forEach((sk, i) => { + const prod = compileProductionWeaponSkill(sk, { ...weaponCtx, barSlot: i }); + if (!prod) return; + compiled.push(productionToDrcSkill(prod)); + report.push(assessProductionReadiness(prod)); + }); + return { skills: compiled, report }; +} + +/** Template for scaffold script / overrides JSON */ +export const PRODUCTION_SKILL_OVERRIDE_TEMPLATE = Object.freeze({ + $schema: 'weapon-skill-production-override', + id: 'catalog_skill_id', + note: 'Optional overrides only — catalog remains SSOT for id/dmg/cd', + animRole: null, + animClip: null, + hitFrameDelay: null, + castEffectId: null, + travelEffectId: null, + impactEffectId: null, + meshId: null, + projectileMeshUrl: null, + delivery: null, + element: null, + force: null, + knockbackMm: null, + knockupVy: null, + aoeM: null, + projectileSpeed: null, + statuses: null, + comboStages: null, + intensity: 1 +}); + +/** @type {Map} */ +const _overrideCache = new Map(); + +/** + * Sync peek — only after warmProductionOverrides / loadProductionOverride. + * @param {string} skillId + */ +export function getCachedProductionOverride(skillId) { + if (!skillId || !_overrideCache.has(skillId)) return null; + return _overrideCache.get(skillId); +} + +/** + * Load optional production override from `public/skills/production/.json`. + * @param {string} skillId + * @returns {Promise} + */ +export async function loadProductionOverride(skillId) { + if (!skillId) return null; + if (_overrideCache.has(skillId)) return _overrideCache.get(skillId); + try { + const url = `./skills/production/${skillId}.json`; + const res = await fetch(url); + if (!res.ok) { + _overrideCache.set(skillId, null); + return null; + } + const json = await res.json(); + _overrideCache.set(skillId, json); + return json; + } catch { + _overrideCache.set(skillId, null); + return null; + } +} + +/** + * Prefetch overrides for a hotbar (call on equip). Then skillDefToDrc can read cache. + * @param {string[]} skillIds + */ +export async function warmProductionOverrides(skillIds) { + const ids = [...new Set((skillIds || []).filter(Boolean))]; + await Promise.all(ids.map((id) => loadProductionOverride(id))); + return ids.map((id) => ({ id, has: !!getCachedProductionOverride(id) })); +} + +/** + * Compile with optional public override JSON auto-load. + * @param {object} catalogSkill + * @param {object} [ctx] + */ +export async function compileProductionWeaponSkillAsync(catalogSkill, ctx = {}) { + const ov = + ctx.overrides || + (await loadProductionOverride(catalogSkill?.id)) || + {}; + return compileProductionWeaponSkill(catalogSkill, { ...ctx, overrides: ov }); +} diff --git a/src/config/animLibrary.js b/src/config/animLibrary.js new file mode 100644 index 0000000..ddc63eb --- /dev/null +++ b/src/config/animLibrary.js @@ -0,0 +1,353 @@ +/** + * Animation Library SSOT — language, families, and play channels for Casting lab. + * + * **Do not invent parallel role names.** Extend ANIM_PACKS (assets.js) + this + * vocabulary, then wire play* on CharacterController / DrcCombatController. + * + * Docs: docs/ANIM_LIBRARY_SSOT.md · CASTING_LAB_SSOT.md + * + * Vocabulary (use these words in UI, toasts, agent notes): + * | Term | Meaning | + * |-------------|---------| + * | pack | Named clip set: magic · sword_shield · longbow · pistol · combat_mobility | + * | role | Logical clip key: idle · walk · run · dodgeL · rollR · slide … | + * | family | gait | combat | mobility | utility | + * | channel | How it is driven: gait (setGait) · oneShot · mobility impulse | + * | MM | Motion-math displacement (100 MM = 1 m) — combat/motionMath.js | + * | afterimage | Trailing model ghosts during MM dodge — vfx/DodgeAfterimage.js | + * + * Bind order on hero load: + * 1. weapon pack (magic | sword_shield | longbow | pistol) + * 2. magic fallback if needed (idle/hands) + * 3. combat_mobility (always — rolls, dodges, slide, parry) + */ + +import { ANIM_PACKS, ANIM_PACK_META, DODGE_ROLE, ROLL_ROLE } from './assets.js'; +import { DODGE_MM, mmToM } from '../combat/motionMath.js'; + +/** @typedef {'gait'|'combat'|'mobility'|'utility'} AnimFamily */ +/** @typedef {'gait'|'oneShot'|'mobility'} AnimChannel */ + +/** + * Canonical role → family + channel + human label. + * Keys are primary action names (not pack-prefixed combat_mobility:rollL). + * @type {Record} + */ +export const ANIM_ROLE_META = Object.freeze({ + /** sword_shield idle prefers 2H FBX (meleeAnimSsot · 2hand Idle.fbx) */ + idle: { family: 'gait', channel: 'gait', label: 'Idle' }, + walk: { family: 'gait', channel: 'gait', label: 'Walk' }, + run: { family: 'gait', channel: 'gait', label: 'Run / sprint' }, + walkL: { + family: 'gait', + channel: 'gait', + label: 'Strafe walk left', + input: 'Focus · A' + }, + walkR: { + family: 'gait', + channel: 'gait', + label: 'Strafe walk right', + input: 'Focus · D' + }, + runL: { + family: 'gait', + channel: 'gait', + label: 'Strafe run left', + input: 'Focus · A + Shift' + }, + runR: { + family: 'gait', + channel: 'gait', + label: 'Strafe run right', + input: 'Focus · D + Shift' + }, + jump: { family: 'gait', channel: 'oneShot', label: 'Jump', input: 'Space' }, + fallLoop: { + family: 'gait', + channel: 'gait', + label: 'Fall loop (air descend)', + input: 'Air · vy↓' + }, + fall: { + family: 'gait', + channel: 'gait', + label: 'Falling body', + input: 'Air deep fall' + }, + fallLand: { + family: 'mobility', + channel: 'oneShot', + label: 'Fall → landing', + input: 'Land soft/hard' + }, + fallRoll: { + family: 'mobility', + channel: 'oneShot', + label: 'Fall → roll out', + input: 'Land hard + forward' + }, + fallIdle: { + family: 'gait', + channel: 'gait', + label: 'Fall idle (air hang)', + input: 'Air' + }, + frontflip: { + family: 'mobility', + channel: 'oneShot', + label: 'Frontflip (real clip / double-jump)', + input: 'Air Space' + }, + backflip: { + family: 'mobility', + channel: 'oneShot', + label: 'Backflip (real clip · camera hold)', + input: 'Air S+Space' + }, + hitReact: { + family: 'combat', + channel: 'oneShot', + label: 'Hit reaction (knocked up)', + input: 'on hit' + }, + knockedUp: { + family: 'combat', + channel: 'oneShot', + label: 'Knocked up', + input: 'on heavy hit' + }, + + cast: { family: 'combat', channel: 'oneShot', label: 'Cast', input: '1–4 / staff' }, + attack: { + family: 'combat', + channel: 'oneShot', + label: 'Melee finisher (jump-dash)', + input: 'LMB air / large MM toward target' + }, + attack1: { + family: 'combat', + channel: 'oneShot', + label: 'Melee combo 1 (into-out)', + input: 'LMB click 1' + }, + attack2: { + family: 'combat', + channel: 'oneShot', + label: 'Melee combo 2 (inward slash)', + input: 'LMB click 2' + }, + attack3: { + family: 'combat', + channel: 'oneShot', + label: 'Melee combo 3 (upward thrust)', + input: 'LMB click 3' + }, + finisher: { + family: 'combat', + channel: 'oneShot', + label: 'Finisher (ground lunge)', + input: 'LMB + large MM toward focus' + }, + finisherAir: { + family: 'combat', + channel: 'oneShot', + label: 'Air finisher (drop to target)', + input: 'LMB while airborne' + }, + block: { family: 'combat', channel: 'oneShot', label: 'Block', input: 'E' }, + parry: { family: 'combat', channel: 'oneShot', label: 'Parry', input: 'C' }, + + dodgeL: { + family: 'mobility', + channel: 'mobility', + label: 'Dodge left (MM escape)', + input: 'AA double-tap' + }, + dodgeR: { + family: 'mobility', + channel: 'mobility', + label: 'Dodge right (MM escape)', + input: 'DD double-tap' + }, + dodgeF: { + family: 'mobility', + channel: 'mobility', + label: 'Dodge forward', + input: 'WW double-tap' + }, + dodgeB: { + family: 'mobility', + channel: 'mobility', + label: 'Dodge back', + input: 'X' + }, + rollL: { + family: 'mobility', + channel: 'mobility', + label: 'Roll left (Ghost Rider)', + input: 'Ctrl+A' + }, + rollR: { + family: 'mobility', + channel: 'mobility', + label: 'Roll right (Ghost Rider)', + input: 'Ctrl+D' + }, + rollF: { + family: 'mobility', + channel: 'mobility', + label: 'Roll forward', + input: 'Ctrl+W' + }, + rollB: { + family: 'mobility', + channel: 'mobility', + label: 'Roll back', + input: 'Ctrl+S' + }, + slide: { + family: 'mobility', + channel: 'mobility', + label: 'Sprint slide', + input: 'Shift+Ctrl' + } +}); + +/** Shared mobility pack id — always bound after weapon pack. */ +export const COMBAT_MOBILITY_PACK = 'combat_mobility'; + +/** Weapon packs that drive idle/walk/run/attack/cast. */ +export const WEAPON_PACK_IDS = Object.freeze(['magic', 'sword_shield', 'longbow']); + +/** + * Mobility input map (agent / HUD copy). Roles resolve via DODGE_ROLE / ROLL_ROLE. + */ +export const MOBILITY_BINDINGS = Object.freeze({ + dodge: { + id: 'dodge', + label: 'MM dodge', + input: 'AA / DD / WW double-tap · X back', + roleMap: DODGE_ROLE, + mm: true, + afterimage: true, + invuln: true, + play: 'playDodge', + notes: 'Lateral AA/DD use DODGE_MM.lateral (3× baseline). Wind mesh afterimages.' + }, + roll: { + id: 'roll', + label: 'Ghost Rider roll', + input: 'Ctrl+A left · Ctrl+D right · Ctrl+W/S', + roleMap: ROLL_ROLE, + mm: false, + afterimage: false, + invuln: false, + play: 'playRoll', + notes: 'Primary clips ghost_rider/roll_* · locomotion fallback · longbow dodge last.' + }, + slide: { + id: 'slide', + label: 'Sprint slide', + input: 'Shift+Ctrl while sprint', + roleMap: { default: 'slide' }, + mm: false, + afterimage: false, + invuln: false, + play: 'playSlide', + notes: 'prod:extra/running-slide' + } +}); + +/** + * Strip pack prefix: `combat_mobility:rollL` → `rollL` + * @param {string} roleName + * @returns {string} + */ +export function baseRoleName(roleName) { + const s = String(roleName || ''); + const i = s.lastIndexOf(':'); + return i >= 0 ? s.slice(i + 1) : s; +} + +/** + * @param {string} roleName action key from mixer map + * @returns {{ family: AnimFamily, channel: AnimChannel, label: string, input?: string, base: string }} + */ +export function classifyRole(roleName) { + const base = baseRoleName(roleName); + const meta = ANIM_ROLE_META[base] || { + family: /** @type {AnimFamily} */ ('utility'), + channel: /** @type {AnimChannel} */ ('oneShot'), + label: base + }; + return { ...meta, base }; +} + +/** + * Group bound clip names for library UI. + * @param {string[]} roleNames + * @returns {Record} + */ +export function groupRolesByFamily(roleNames) { + /** @type {Record} */ + const out = { gait: [], combat: [], mobility: [], utility: [] }; + for (const name of roleNames || []) { + const { family } = classifyRole(name); + out[family].push(name); + } + for (const k of Object.keys(out)) out[k].sort(); + return out; +} + +/** + * Snapshot for Lab / Showcase / agent diagnostics. + * @param {{ animPackId?: string, listAnimRoles?: () => string[], actions?: Map }} character + * @returns {object} + */ +export function describeAnimLibrary(character) { + const roles = character?.listAnimRoles?.() || [...(character?.actions?.keys?.() || [])]; + const byFamily = groupRolesByFamily(roles); + const packs = Object.keys(ANIM_PACKS).map((id) => ({ + id, + label: ANIM_PACK_META[id]?.label || id, + skills: ANIM_PACK_META[id]?.skills || '', + locomotion: ANIM_PACK_META[id]?.locomotion || '', + active: id === character?.animPackId + })); + return { + version: '2026-08-08.anim-library.1', + activePack: character?.animPackId || null, + packs, + roles, + byFamily, + mobility: MOBILITY_BINDINGS, + dodgeMm: { + lateralM: mmToM(DODGE_MM.lateral), + forwardM: mmToM(DODGE_MM.forward), + backM: mmToM(DODGE_MM.back), + units: '100 MM = 1 m' + }, + playApi: { + gait: 'setGait(level, sprinting)', + combat: 'playWeaponCombat(intent) | requestOneShot(role)', + dodge: 'playDodge(dir) · DRC double-tap', + roll: 'playRoll(dir) · Ctrl+A/D', + slide: 'playSlide() · Shift+Ctrl', + library: 'playLibraryClip(role) · Showcase Anims tab' + }, + extendPattern: + '1) Add role URLs to ANIM_PACKS in assets.js · 2) ANIM_ROLE_META here · ' + + '3) roleMap LoopOnce in CharacterController._bindPack · 4) play* or DRC poll · 5) docs' + }; +} + +/** + * Short toast / HUD line for a role. + * @param {string} roleName + */ +export function roleBlurb(roleName) { + const c = classifyRole(roleName); + const input = c.input ? ` · ${c.input}` : ''; + return `${c.label}${input} [${c.family}/${c.channel}]`; +} diff --git a/src/config/assets.js b/src/config/assets.js new file mode 100644 index 0000000..c2f3e4f --- /dev/null +++ b/src/config/assets.js @@ -0,0 +1,518 @@ +/** + * Grudge / Toon RTS asset config for this sandbox. + * + * Race kits / atlases: `grudge6SSOT.js` (Multiverse stone SSOT). + * Mesh load: GLTFLoader + DRACO + Meshopt (AssetLoader). + * Baked Bip001 clips: assets.grudge-studio.com/prod/anims/… (same-origin /api/assets) + */ + +import { + CDN, + DEFAULT_RACE, + GEAR_PRESETS_URL as SSOT_GEAR_PRESETS, + HUMAN_HEIGHT_M, + RACES as SSOT_RACES, + atlasUrlForRace, + kitUrlCandidates, + kitUrlForRace +} from './grudge6SSOT.js'; +import { sameOriginFleetUrl } from './fleetEnv.js'; + +export const ASSETS_CDN = CDN; +export const OPEN_HOST = 'https://open.grudge-studio.com'; + +// Decoder pins live in gltfPipeline only — re-export so importers stay stable +export { + DRACO_DECODER_PATH, + KTX2_TRANSCODER_PATH +} from '../loaders/gltfPipeline.js'; + +export const GEAR_PRESETS_URL = SSOT_GEAR_PRESETS; +export { DEFAULT_RACE, atlasUrlForRace, kitUrlCandidates, kitUrlForRace, HUMAN_HEIGHT_M }; + +/** Race kits + atlases — Toon RTS ★ play URLs. */ +export const RACES = Object.fromEntries( + Object.entries(SSOT_RACES).map(([id, r]) => [ + id, + { + id: r.id, + label: r.label, + prefix: r.prefix, + kitUrl: r.kitGlb, + atlasUrl: r.atlasUrl, + }, + ]), +); + +/** + * Baked pack clips — each role is a candidate list (first URL that loads wins). + * + * HARD idle lesson (2026-08): open `magic/standing idle` is rotation-only but + * **has no Hand tracks** and drives Spine1/Spine2. Toon bind leaves hands in a + * broken rest while cast (`…1h cast spell…`) animates hands → cast looks formed, + * idle does not. Prefer **prod** magic idle (hands + same bone set as cast). + */ +/** + * Weapon locomotion + skill packs (Bip001 baked JSON). + * Lab binds one active pack; skills fire roles (cast/attack/block). + * Expand roles only when CDN clips exist — do not invent paths. + */ +export const ANIM_PACKS = { + magic: { + // Prefer prod (has L/R Hand). Open standing idle is hand-less — avoid as primary. + idle: [ + 'prod:magic/standing-idle', + 'magic/standing idle' // last resort only + ], + cast: [ + 'prod:magic/standing-1h-cast-spell-01', + 'magic/standing 1h cast spell 01', + 'magic/standing 2h cast spell 01' + ], + walk: ['prod:magic/standing-walk-forward', 'magic/Standing Walk Forward'], + run: ['prod:magic/standing-run-forward', 'magic/Standing Run Forward'], + /** Focus-mode lower body strafe (A/D while looking with mouse) */ + walkL: ['prod:magic/standing-walk-left', 'magic/standing-walk-left'], + walkR: ['prod:magic/standing-walk-right', 'magic/standing-walk-right'], + runL: ['prod:magic/standing-run-left', 'magic/standing-run-left'], + runR: ['prod:magic/standing-run-right', 'magic/standing-run-right'], + /** Jump one-shot (blend with locomotion) */ + jump: ['prod:magic/standing-jump', 'locomotion/jump'] + }, + sword_shield: { + // Idle: 2H melee FBX (public/anim/melee/2hand-idle.fbx) is bound first in CharacterController. + // Baked S&S idle remains fallback if FBX missing. + idle: ['sword_shield/sword and shield idle'], + /** + * Melee roles (see docs/MELEE_COMBO_SSOT.md): + * - idle = 2H idle (author: D:\Games\Models\2hand Idle.fbx) + * - attack1–3 = grounded light combo (3 LMB clicks) + * - attack / finisher = jump-dash finisher (CDN Bip001 “sword and shield attack”) + * - finisherAir = drop-to-target when airborne + * Mixamo sources baked same-origin; rematch → Bip001 at bind. + */ + attack1: [ + 'sword_shield/intoout', + 'sword_shield/one-hand-combo-hit1', + 'sword_shield/sword and shield slash' + ], + attack2: [ + 'sword_shield/st1able-sword-inward-slash', + 'sword_shield/one-hand-combo-hit2', + 'sword_shield/sword and shield slash' + ], + attack3: [ + 'sword_shield/11upward-thrust', + 'sword_shield/one-hand-combo-hit3', + 'sword_shield/sword and shield slash' + ], + /** Finisher (was misused as light attack): lunge / jump-dash */ + attack: ['sword_shield/sword and shield attack'], + finisher: ['sword_shield/sword and shield attack'], + finisherAir: ['sword_shield/dropto-target', 'sword_shield/sword and shield attack'], + block: ['sword_shield/sword and shield block'], + // Walk: open S&S walk 404s — prod magic walk is live SSOT fallback + walk: [ + 'prod:magic/standing-walk-forward', + 'sword_shield/sword and shield walk', + 'magic/standing walk forward' + ], + run: [ + 'sword_shield/sword and shield run', + 'prod:magic/standing-run-forward', + 'magic/standing run forward' + ], + /** Melee focus strafe — no native S&S side walks; magic/longbow CDN */ + walkL: [ + 'prod:magic/standing-walk-left', + 'magic/standing-walk-left', + 'longbow/standing-walk-left' + ], + walkR: [ + 'prod:magic/standing-walk-right', + 'magic/standing-walk-right', + 'longbow/standing-walk-right' + ], + runL: [ + 'prod:magic/standing-run-left', + 'magic/standing-run-left', + 'longbow/standing-run-left' + ], + runR: [ + 'prod:magic/standing-run-right', + 'magic/standing-run-right', + 'longbow/standing-run-right' + ], + jump: ['prod:magic/standing-jump', 'locomotion/jump'] + }, + longbow: { + idle: ['longbow/standing idle', 'longbow/standing idle 01', 'bow/standing idle', 'prod:magic/standing-idle'], + attack: ['longbow/standing draw arrow', 'longbow/standing aim recoil', 'bow/draw arrow', 'sword_shield/sword and shield attack'], + walk: ['longbow/standing walk forward', 'prod:magic/standing-walk-forward'], + run: ['longbow/standing run forward', 'prod:magic/standing-run-forward'], + walkL: [ + 'prod:longbow/standing-walk-left', + 'longbow/standing-walk-left', + 'longbow/walk-left', + 'prod:magic/standing-walk-left' + ], + walkR: [ + 'prod:longbow/standing-walk-right', + 'longbow/standing-walk-right', + 'longbow/walk-right', + 'prod:magic/standing-walk-right' + ], + runL: [ + 'prod:longbow/standing-run-left', + 'longbow/standing-run-left', + 'prod:magic/standing-run-left' + ], + runR: [ + 'prod:longbow/standing-run-right', + 'longbow/standing-run-right', + 'prod:magic/standing-run-right' + ], + jump: ['prod:magic/standing-jump', 'locomotion/jump'], + // Directional dodges (Danger Room AA/DD/WW/X) — longbow pack primary + dodgeL: ['longbow/standing dodge left', 'locomotion/dodge_l'], + dodgeR: ['longbow/standing dodge right', 'locomotion/dodge_r'], + dodgeF: ['longbow/standing dodge forward', 'locomotion/dodge_fwd'], + dodgeB: ['longbow/standing dodge backward', 'locomotion/dodge_back'], + block: ['sword_shield/sword and shield block'], + parry: ['sword_shield/sword and shield block'] + }, + /** + * T0 pistol / handgun pack (Bip001 baked on Open). + * + * Combat roles map Mixamo pistol gunplay + TPS Minecraft timing reference: + * minecraft_tps_model_*.glb — draw · fire · drawnidle · drawaim · fireaim + * gunplay.json ≈ spin / flourish fire (primary attack) + * drawing-gun.json ≈ holster → ready (draw) + * charged-pistol / pistol-whip ≈ power / melee skill + * + * Reference mesh (lab): public/models/reference/minecraft_tps_pistol.glb + * Timing SSOT: config/pistolAnimSsot.js + */ + pistol: { + idle: ['pistol/idle', 'pistol/pistol idle', 'longbow/standing idle', 'prod:magic/standing-idle'], + /** Primary fire — gunplay spin flourish; TPS fire is ~0.21s snap (timeScale in combat) */ + attack: [ + 'pistol/gunplay', + 'pistol/charged-pistol', + 'pistol/pistol-whip', + 'longbow/standing aim recoil' + ], + /** Aim-ready / drawn idle feel */ + cast: ['pistol/drawing-gun', 'pistol/charged-pistol', 'pistol/gunplay'], + /** Spin flourish (explicit role for skills / lab library) */ + gunplay: ['pistol/gunplay'], + spin: ['pistol/gunplay'], + draw: ['pistol/drawing-gun', 'pistol/gunplay'], + /** + * Powder reload — prefer local/CDN baked pistol/reload (drawing-gun stamp), + * then drawing-gun, then rifle reloading for review parity with Open skillSlots. + * Procedural PistolReloadPose layers on top after mixer. + */ + reload: [ + 'pistol/reload', + 'pistol/drawing-gun', + 'rifle/reloading', + 'pistol/charged-pistol' + ], + /** Open Danger: pistol_shot */ + skill1: ['pistol/gunplay', 'pistol/drawing-gun'], + /** Open Danger: pistol_fan / charged */ + skill2: ['pistol/charged-pistol', 'pistol/gunplay'], + /** Open Danger: whip review */ + skill3: ['pistol/pistol-whip', 'pistol/gunplay'], + skill4: ['pistol/charged-pistol', 'pistol/pistol-whip'], + /** Open Danger: pistol_reload */ + skill5: ['pistol/reload', 'pistol/drawing-gun', 'rifle/reloading'], + walk: ['pistol/walk-forward', 'pistol/pistol walk', 'prod:magic/standing-walk-forward'], + run: ['pistol/run-forward', 'pistol/pistol run', 'prod:magic/standing-run-forward'], + walkL: ['pistol/strafe-left', 'prod:magic/standing-walk-left', 'longbow/standing-walk-left'], + walkR: ['pistol/strafe-right', 'prod:magic/standing-walk-right', 'longbow/standing-walk-right'], + runL: ['pistol/strafe-left', 'prod:magic/standing-run-left'], + runR: ['pistol/strafe-right', 'prod:magic/standing-run-right'], + jump: ['pistol/pistol-jump', 'pistol/jump', 'prod:magic/standing-jump', 'locomotion/jump'], + dodgeL: ['longbow/standing dodge left', 'locomotion/dodge_l'], + dodgeR: ['longbow/standing dodge right', 'locomotion/dodge_r'], + dodgeF: ['longbow/standing dodge forward', 'locomotion/dodge_fwd'], + dodgeB: ['longbow/standing dodge backward', 'locomotion/dodge_back'], + block: ['pistol/drawing-gun', 'sword_shield/sword and shield block'], + parry: ['pistol/pistol-whip', 'sword_shield/sword and shield block'] + }, + /** Optional 8-way locomotion overlay (binds extra roles if CDN has clips) */ + locomotion_8way: { + idle: ['prod:magic/standing-idle'], + walk: ['locomotion/walk-forward', 'prod:magic/standing-walk-forward'], + run: ['locomotion/run-forward', 'prod:magic/standing-run-forward'], + walkL: ['prod:magic/standing-walk-left', 'longbow/standing-walk-left'], + walkR: ['prod:magic/standing-walk-right', 'longbow/standing-walk-right'], + runL: ['prod:magic/standing-run-left', 'longbow/standing-run-left'], + runR: ['prod:magic/standing-run-right', 'longbow/standing-run-right'], + jump: ['locomotion/jump', 'prod:magic/standing-jump'] + }, + /** + * Shared combat mobility — always bound on hero load. + * Rolls: Ghost Rider preferred (open …/ghost_rider/roll_*), locomotion fallbacks. + * Dodges: longbow standing L/R/F/B (AA/DD/WW/X). Slide: prod running-slide. + */ + /** + * Hit reactions (Mixamo rematch) — knockback / launched. + * Bound on hero load with combat_mobility. + */ + reactions: { + hitReact: ['reactions/knocked-up'], + knockedUp: ['reactions/knocked-up'] + }, + combat_mobility: { + // Ghost Rider rolls (user-preferred) → locomotion pack → longbow dodge as last resort + rollL: [ + 'ghost_rider/roll_left', + 'locomotion/roll_left', + 'longbow/standing dodge left', + 'locomotion/dodge_l' + ], + rollR: [ + 'ghost_rider/roll_right', + 'locomotion/roll_right', + 'longbow/standing dodge right', + 'locomotion/dodge_r' + ], + rollF: [ + 'ghost_rider/roll_forward', + 'locomotion/roll_forward', + 'longbow/standing dodge forward', + 'locomotion/dodge_fwd' + ], + rollB: [ + 'ghost_rider/roll_back', + 'locomotion/roll_back', + 'longbow/standing dodge backward', + 'locomotion/dodge_back' + ], + // Sprint+Ctrl slide (prod bake on assets CDN) + slide: ['prod:extra/running-slide', 'prod:extra/quick-roll-to-run'], + dodgeL: ['longbow/standing dodge left', 'locomotion/dodge_l', 'ghost_rider/dodgeL'], + dodgeR: ['longbow/standing dodge right', 'locomotion/dodge_r', 'ghost_rider/dodgeR'], + dodgeF: ['longbow/standing dodge forward', 'locomotion/dodge_fwd', 'ghost_rider/dodgeF'], + dodgeB: ['longbow/standing dodge backward', 'locomotion/dodge_back', 'ghost_rider/dodgeB'], + parry: ['sword_shield/sword and shield block'], + block: ['sword_shield/sword and shield block'], + /** + * Real flip clips (Open FBX rematch — see fbxClip.js). + * Baked JSON preferred if present; FBX is author SSOT on open.grudge-studio.com. + */ + frontflip: ['extra/front-flip', 'extra/running-forward-flip', 'extra/front-twist-flip'], + backflip: ['striker/backflip', 'striker/back_flip_to_uppercut'], + /** + * Fall locomotion — author FBX rematch (fallAnimSsot / public/anim/locomotion/fall). + * Baked JSON candidates first when promoted to Open CDN. + */ + fallLoop: ['locomotion/fall-loop', 'locomotion/fall_a_loop', 'extra/fall-loop'], + fall: ['locomotion/falling', 'locomotion/fall', 'extra/falling'], + fallLand: ['locomotion/fall-to-landing', 'locomotion/falling-to-landing'], + fallRoll: ['locomotion/fall-to-roll', 'locomotion/falling-to-roll'], + fallIdle: ['locomotion/fall-idle', 'locomotion/falling-idle'] + } +}; + +/** UI labels for pack picker (weapon locomotion + skills). */ +export const ANIM_PACK_META = { + magic: { label: 'Magic / staff', skills: 'cast', locomotion: 'walk·run·jump' }, + sword_shield: { + label: 'Sword & shield', + skills: 'combo×3 · finisher · block', + locomotion: 'walk·run·jump' + }, + longbow: { label: 'Longbow', skills: 'attack · dodge L/R/F/B', locomotion: 'walk·run·jump' }, + pistol: { + label: 'Pistol / handgun', + skills: 'gunplay(spin) · draw · charged · whip', + locomotion: 'walk·run·strafe·jump' + }, + locomotion_8way: { label: 'Locomotion 8-way', skills: '—', locomotion: 'walk·run·jump' }, + combat_mobility: { + label: 'Shared rolls / dodges / slide / parry / fall', + skills: 'roll·dodge·slide·parry·fall', + locomotion: 'fallLoop·fallLand·fallRoll' + } +}; + +/** Dodge role by direction for AA/DD/WW/X. */ +export const DODGE_ROLE = Object.freeze({ + left: 'dodgeL', + right: 'dodgeR', + forward: 'dodgeF', + back: 'dodgeB' +}); + +/** Roll role by direction for Ctrl+A/D (and optional F/B). Ghost Rider pack primary. */ +export const ROLL_ROLE = Object.freeze({ + left: 'rollL', + right: 'rollR', + forward: 'rollF', + back: 'rollB' +}); + +/** Fallback loadouts if CDN presets fail (mage-first for casting). + * Letters match Toon RTS human kit: Body A–E, Arms A–D, Legs A–C, head A–I, staff A–C. + */ +export const FALLBACK_PRESETS = [ + { + id: 'mage', + label: 'Mage', + description: 'Robes and a staff.', + pack: 'magic', + // Conservative A/B set so every race kit has matching mesh_ids + loadout: { body: 'A', arms: 'A', legs: 'A', head: 'A', staff: 'A' } + }, + { + id: 'knight', + label: 'Knight', + description: 'Heavy plate, sword & shield.', + pack: 'sword_shield', + loadout: { + body: 'C', + arms: 'C', + legs: 'C', + head: 'D', + shoulders: 'B', + sword: 'A', + shield: 'A' + } + }, + { + id: 'berserker', + label: 'Berserker', + description: 'Light armor, axe.', + pack: 'sword_shield', + loadout: { body: 'B', arms: 'B', legs: 'B', head: 'B', axe: 'A' } + }, + { + id: 'archer', + label: 'Archer', + description: 'Cloth + leather, bow (quiver only via carry).', + pack: 'longbow', + loadout: { + body: 'A', + arms: 'A', + legs: 'A', + head: 'A', + bow: '_default' + } + }, + { + id: 'tank', + label: 'Tank', + description: 'Maximum plate, hammer & tower shield.', + pack: 'sword_shield', + loadout: { + body: 'E', + arms: 'D', + legs: 'C', + head: 'F', + shoulders: 'B', + hammer: 'A', + shield: 'B' + } + } +]; + +/** + * Resolve one relative path (or `prod:pack/file`) to absolute URL candidates. + * @param {string} rel + * @returns {string[]} + */ +export function bakedClipUrl(rel) { + return bakedClipUrls(rel)[0]; +} + +/** + * Absolute URL candidates for a baked Bip001 clip. + * Browser never hits assets.* / open.* directly (no CORS → fetch crash). + * Live game clips: /prod/anims/{pack}/{hyphen}.json (idle + cast + loco). + * /anims/baked is a partial mirror; Open /anims/baked is 404. + * @param {string} rel + * @returns {string[]} + */ +export function bakedClipUrls(rel) { + const raw = String(rel || '').replace(/^\/+/, '').replace(/\.json$/i, ''); + if (!raw) return []; + + const encPath = (p) => + p + .replace(/^\/+/, '') + .split('/') + .map((s) => encodeURIComponent(s)) + .join('/'); + + const wrap = (url) => sameOriginFleetUrl(url); + const strip = (prefix) => raw.slice(prefix.length).replace(/^\/+/, ''); + + if (raw.startsWith('local:')) { + return [`./anims/baked/${encPath(strip('local:'))}.json`]; + } + + const bare = raw.startsWith('prod:') + ? strip('prod:') + : raw.startsWith('open:') + ? strip('open:') + : raw; + const hyphen = encPath(bare.replace(/ /g, '-')); + const enc = encPath(bare); + + const urls = [ + wrap(`${CDN}/prod/anims/${hyphen}.json`), + wrap(`${CDN}/anims/baked/${hyphen}.json`), + wrap(`${CDN}/anims/baked/${enc}.json`), + `./anims/baked/${hyphen}.json`, + `./anims/baked/${enc}.json`, + wrap(`${OPEN_HOST}/anims/baked/${enc}.json`) + ]; + const seen = new Set(); + return urls.filter((u) => u && !seen.has(u) && seen.add(u)); +} + +/** + * Flatten pack role value (string | string[]) into ordered absolute URLs. + * @param {string|string[]} roleEntry + * @returns {string[]} + */ +export function bakedClipUrlsForRole(roleEntry) { + const list = Array.isArray(roleEntry) ? roleEntry : [roleEntry]; + const out = []; + const seen = new Set(); + for (const rel of list) { + for (const url of bakedClipUrls(rel)) { + if (seen.has(url)) continue; + seen.add(url); + out.push(url); + } + } + return out; +} + +/** SI human target height (metres). */ +export const TARGET_HEIGHT_M = HUMAN_HEIGHT_M; + +/** Armor / weapon slots used by EquipmentManager + inventory panel. */ +/** Inventory UI slots — utility listed last (carry-only, hidden by default). */ +export const EQUIP_SLOTS = [ + 'body', + 'arms', + 'legs', + 'head', + 'shoulders', + 'sword', + 'axe', + 'hammer', + 'spear', + 'staff', + 'bow', + 'shield' + // bag / wood / quiver intentionally omitted from default equip UI +]; + +export const WEAPON_SLOTS = ['sword', 'axe', 'hammer', 'spear', 'staff', 'bow']; diff --git a/src/config/backSlotMobilitySsot.js b/src/config/backSlotMobilitySsot.js new file mode 100644 index 0000000..11eb7bd --- /dev/null +++ b/src/config/backSlotMobilitySsot.js @@ -0,0 +1,352 @@ +/** + * Back-slot mobility SSOT — windsurf (water only), capes/shells (land), wings (air). + * + * Windsurf is **not** a land deployable. It is a **water mobility vehicle**. + * Land back items (capes, shells, packs) and air (wings, gliders) use different + * deploy contracts on the same BackSlotEquip spine attach. + * + * Wings (next system): + * - holy_wings — jump up → glide down (angel / holy) + * - traveler_wings — double jump → fly pose · **2 flaps** · then glide (WoW DF-like) + * + * @see character/BackSlotEquip.js + * @see docs/WINDSURF_RIDE_SSOT.md + * @see docs/BACK_SLOT_MOBILITY_SSOT.md + */ + +/** Domain where deploy is legal */ +/** @typedef {'water'|'land'|'air'|'cosmetic'} MobilityDomain */ + +/** + * Deploy contract kinds (runtime host). + * @typedef {'vehicle_water'|'glide_air'|'cosmetic_land'|'shell_land'} DeployKind + */ + +/** + * @typedef {object} BackMobilityDef + * @property {string} id + * @property {string} label + * @property {MobilityDomain} domain + * @property {DeployKind} deployKind + * @property {boolean} deployable + * @property {string|null} modelUrl stow / back mesh + * @property {string|null} [deployModelUrl] full vehicle or open wings mesh + * @property {string|null} [iconUrl] + * @property {number} [stowLengthM] + * @property {number[]} [stowOffset] + * @property {number[]} [stowEulerDeg] + * @property {boolean} [cloth] + * @property {object} [flight] air mobility knobs + * @property {object} [waterBuffs] passive water buffs (shark fin etc.) + * @property {string} [notes] + */ + +/** Flight knobs shared by wing prefabs */ +export const FLIGHT_DEFAULTS = Object.freeze({ + /** Max flaps before forced glide (Traveler) */ + maxFlaps: 2, + /** Upward boost per flap (m/s) */ + flapVy: 6.2, + /** Glide sink rate (m/s, positive down) */ + glideSink: 1.4, + /** Glide forward speed (m/s) */ + glideSpeed: 9.5, + /** Traveler boosted glide speed with flaps remaining feel */ + travelerGlideSpeed: 14, + /** Holy: single jump height boost into glide */ + holyJumpVy: 8.5, + /** Min airtime before glide engages after jump (s) */ + glideEngageSec: 0.18 +}); + +/** + * Canonical back-slot catalog (equip ids). + * @type {Record} + */ +export const BACK_MOBILITY_CATALOG = Object.freeze({ + none: { + id: 'none', + label: 'None', + domain: 'cosmetic', + deployKind: 'cosmetic_land', + deployable: false, + modelUrl: null + }, + + /** Water vehicle only — never deploy on dry land */ + windsurf: { + id: 'windsurf', + label: 'Windsurf', + domain: 'water', + deployKind: 'vehicle_water', + deployable: true, + modelUrl: './models/ride/back_fly_windsurf.glb', + deployModelUrl: './models/ride/windsurf_package.glb', + stowLengthM: 0.58, + stowOffset: [0.02, 0.06, -0.14], + stowEulerDeg: [8, 180, 0], + cloth: true, + notes: + 'Water mobility only. Stow = para/sail back pack; deploy vehicle = windsurf_package + manifest sockets.' + }, + + /** Land cosmetic / passive mobility shell */ + cape: { + id: 'cape', + label: 'Cape', + domain: 'land', + deployKind: 'cosmetic_land', + deployable: false, + modelUrl: null, + cloth: true, + notes: 'Land back cosmetic — cloth follow; no vehicle. Mesh TBD from armor back pack.' + }, + + shell: { + id: 'shell', + label: 'Back Shell', + domain: 'land', + deployKind: 'shell_land', + deployable: false, + modelUrl: null, + notes: 'Land shell / pack — armor silhouette; no air deploy.' + }, + + /** + * Shark Fin — passive water utility (Poly by Google). + * Stow on spine; always-on buffs while equipped (no vehicle deploy). + * - Swim on / under water **100% faster** (×2) + * - **No aggro** from sharks + * - **Breathe** underwater (no drown) + */ + shark_fin: { + id: 'shark_fin', + label: 'Shark Fin', + domain: 'water', + deployKind: 'cosmetic_land', + deployable: false, + modelUrl: './models/ride/shark_fin.glb', + stowLengthM: 0.55, + stowOffset: [0.0, 0.12, -0.18], + stowEulerDeg: [15, 180, 0], + cloth: false, + waterBuffs: { + /** Surface + submerged loco speed multiplier (1 = base, 2 = 100% faster) */ + swimSpeedMul: 2.0, + /** Combat / fauna: sharks do not aggro the wearer */ + sharkAggroImmune: true, + /** No oxygen drain / drown while submerged */ + breatheUnderwater: true + }, + notes: + 'Back slot · Poly by Google shark fin. Passive: 2× swim (on & under water), shark aggro immune, underwater breath.' + }, + + /** + * Holy Wings — jump up, then glide down. + * Prefab ready; mesh path when animated wings asset lands in public/models/ride/wings/. + */ + holy_wings: { + id: 'holy_wings', + label: 'Holy Wings', + domain: 'air', + deployKind: 'glide_air', + deployable: true, + modelUrl: './models/ride/wings/holy_wings_stow.glb', + deployModelUrl: './models/ride/wings/holy_wings_open.glb', + stowLengthM: 0.72, + stowOffset: [0, 0.1, -0.16], + stowEulerDeg: [0, 180, 0], + flight: { + mode: 'holy', + /** Jump once → auto enter glide at apex */ + jumpToGlide: true, + maxFlaps: 0, + jumpVy: FLIGHT_DEFAULTS.holyJumpVy, + glideSink: FLIGHT_DEFAULTS.glideSink, + glideSpeed: FLIGHT_DEFAULTS.glideSpeed, + anims: { + open: 'wings/holy-open', + glide: 'wings/holy-glide', + flap: null, + close: 'wings/holy-close' + } + }, + notes: + 'Jump (Space) from ground or air → rise · at apex open wings · glide down. No multi-flap.' + }, + + /** + * Traveler Wings — double jump → fly pose · two flaps · then glide (DF-like). + */ + traveler_wings: { + id: 'traveler_wings', + label: "Traveler's Wings", + domain: 'air', + deployKind: 'glide_air', + deployable: true, + modelUrl: './models/ride/wings/traveler_wings_stow.glb', + deployModelUrl: './models/ride/wings/traveler_wings_open.glb', + stowLengthM: 0.78, + stowOffset: [0, 0.1, -0.18], + stowEulerDeg: [0, 180, 0], + flight: { + mode: 'traveler', + /** Second air jump enters fly pose */ + doubleJumpToFly: true, + maxFlaps: FLIGHT_DEFAULTS.maxFlaps, + flapVy: FLIGHT_DEFAULTS.flapVy, + glideSink: FLIGHT_DEFAULTS.glideSink * 0.85, + glideSpeed: FLIGHT_DEFAULTS.travelerGlideSpeed, + anims: { + open: 'wings/traveler-open', + flyPose: 'wings/traveler-fly', + flap: 'wings/traveler-flap', + glide: 'wings/traveler-glide', + close: 'wings/traveler-close' + } + }, + notes: + 'Air Space (double jump) → fly pose. Space again = flap (max 2). After flaps = glide down. Faster than Holy.' + } +}); + +/** + * Windsurf vehicle part sockets (deployed package) — SI travel frame +Z nose. + * Mesh nodes are often unnamed Sketchfab Tube; parts are **socket + bbox heuristics**. + */ +export const WINDSURF_PARTS = Object.freeze({ + board: { + id: 'board', + label: 'Board / deck', + sockets: ['deckCenter', 'footL', 'footR', 'mastBase'], + /** Stern←→bow along +Z */ + color: 0x4a90d9, + notes: 'Rigid body under feet; IK feet plant here' + }, + front: { + id: 'front', + label: 'Front / bow', + sockets: ['bowTip'], + color: 0x3dff9a, + notes: 'Nose into travel +Z (artYaw 0)' + }, + sail: { + id: 'sail', + label: 'Sail / boom', + sockets: ['sailPeak', 'sailRail', 'sailBoomL', 'sailBoomR', 'mastBase'], + color: 0xffc14a, + notes: 'Cloth sail + hand boom grips; SailCloth vertex wind' + }, + engine: { + id: 'engine', + label: 'Engine / stern mount', + sockets: ['engineMount'], + color: 0xff5a5a, + notes: 'Aft mount (−Z); thrust visual / future prop' + } +}); + +/** + * @param {string} id + * @returns {BackMobilityDef|null} + */ +export function getBackMobility(id) { + return BACK_MOBILITY_CATALOG[id] || null; +} + +/** + * Passive water buffs from equipped back-slot item (shark fin, etc.). + * @param {string|null|undefined} itemId + * @returns {{ + * swimSpeedMul: number, + * sharkAggroImmune: boolean, + * breatheUnderwater: boolean, + * id: string|null + * }} + */ +export function getBackWaterBuffs(itemId) { + const def = getBackMobility(itemId); + const b = def?.waterBuffs || null; + return { + id: def?.id || null, + swimSpeedMul: Number(b?.swimSpeedMul) > 0 ? Number(b.swimSpeedMul) : 1, + sharkAggroImmune: !!b?.sharkAggroImmune, + breatheUnderwater: !!b?.breatheUnderwater + }; +} + +/** + * Can this item deploy at current surface? + * @param {BackMobilityDef|null} def + * @param {{ onWater?: boolean, onLand?: boolean, airborne?: boolean }} surface + */ +export function canDeployBackItem(def, surface = {}) { + if (!def?.deployable) return { ok: false, reason: 'not deployable' }; + if (def.domain === 'water') { + // Passive water items (shark_fin) are equip-only, not deploy vehicles + if (def.id === 'shark_fin') return { ok: false, reason: 'passive water buff — always on while equipped' }; + if (surface.onWater) return { ok: true }; + return { ok: false, reason: 'Windsurf is water-only — move to ocean / wet shore' }; + } + if (def.domain === 'air') { + // Wings equip on land; deploy/flight is air-state + return { ok: true }; + } + if (def.domain === 'land') { + if (surface.onLand !== false) return { ok: true }; + return { ok: false, reason: 'Land back item' }; + } + return { ok: false, reason: 'unknown domain' }; +} + +/** + * Traveler flap budget state machine helpers. + * @param {{ flapsLeft: number, mode: string }} st + * @param {'doubleJump'|'flap'|'glide'|'land'} event + */ +export function reduceTravelerFlight(st, event) { + const next = { ...st }; + if (event === 'doubleJump') { + next.mode = 'fly'; + next.flapsLeft = FLIGHT_DEFAULTS.maxFlaps; + return next; + } + if (event === 'flap' && next.mode === 'fly' && next.flapsLeft > 0) { + next.flapsLeft -= 1; + if (next.flapsLeft <= 0) next.mode = 'glide'; + return next; + } + if (event === 'glide') { + next.mode = 'glide'; + next.flapsLeft = 0; + return next; + } + if (event === 'land') { + next.mode = 'idle'; + next.flapsLeft = 0; + return next; + } + return next; +} + +/** Expected asset drop paths (when you locate animated wing packs) */ +export const WING_ASSET_DROP = Object.freeze({ + holy: { + stow: 'public/models/ride/wings/holy_wings_stow.glb', + open: 'public/models/ride/wings/holy_wings_open.glb', + anims: 'public/anims/baked/wings/holy-*.json' + }, + traveler: { + stow: 'public/models/ride/wings/traveler_wings_stow.glb', + open: 'public/models/ride/wings/traveler_wings_open.glb', + anims: 'public/anims/baked/wings/traveler-*.json' + }, + searchHints: [ + 'D:/Games/Models/**/*wing*', + 'D:/Games/Models/**/*angel*', + 'D:/Games/Models/**/*glider*', + 'Desktop grudgeproduction / ObjectStore armor back', + 'GrudgeBuilder icons/armor/wings/wing_*.png (icons only until mesh found)' + ] +}); diff --git a/src/config/fallAnimSsot.js b/src/config/fallAnimSsot.js new file mode 100644 index 0000000..1966cf0 --- /dev/null +++ b/src/config/fallAnimSsot.js @@ -0,0 +1,150 @@ +/** + * Jump / fall locomotion SSOT — deterministic air states + blend roles. + * + * Author FBX (disk → public same-origin): + * D:\Games\Models\Falling.fbx → fall + * D:\Games\Models\Fall A Loop.fbx → fallLoop + * D:\Games\Models\Falling To Landing.fbx → fallLand + * D:\Games\Models\Falling To Roll.fbx → fallRoll + * (optional) Falling Idle.fbx → fallIdle + * + * State machine (deterministic, velocity-driven — no RNG): + * + * ground ──Space──► jump (hold apex) + * jump / air rise ──vy < fallStartVy──► fallLoop (loop) + * fallLoop ──optional──► fall (long one-shot if loaded) + * air + land impact < rollVy ──► fallLand → idle/walk + * air + land impact ≥ rollVy + forward ──► fallRoll → idle/walk + * + * Blends: gaitBlend into fall · combatBlend out of land/roll · exclusive land/roll + * + * @see animation/fbxClip.js FALL_FBX_URLS + * @see docs/ANIM_LIBRARY_SSOT.md + */ + +/** Logical roles (ANIM_PACKS.combat_mobility + weapon packs inherit) */ +export const FALL_ROLES = Object.freeze({ + fallLoop: 'fallLoop', + fall: 'fall', + fallLand: 'fallLand', + fallRoll: 'fallRoll', + fallIdle: 'fallIdle' +}); + +/** + * Same-origin FBX (lab) then Open CDN when promoted. + * Prefer local public first for deterministic author review. + */ +export const FALL_FBX_URLS = Object.freeze({ + fallLoop: [ + './anim/locomotion/fall/fall-loop.fbx', + 'https://open.grudge-studio.com/anim/locomotion/fall/fall-loop.fbx' + ], + fall: [ + './anim/locomotion/fall/falling.fbx', + 'https://open.grudge-studio.com/anim/locomotion/fall/falling.fbx' + ], + fallLand: [ + './anim/locomotion/fall/fall-to-landing.fbx', + 'https://open.grudge-studio.com/anim/locomotion/fall/fall-to-landing.fbx' + ], + fallRoll: [ + './anim/locomotion/fall/fall-to-roll.fbx', + 'https://open.grudge-studio.com/anim/locomotion/fall/fall-to-roll.fbx' + ], + fallIdle: [ + './anim/locomotion/fall/fall-idle.fbx', + 'https://open.grudge-studio.com/anim/locomotion/fall/fall-idle.fbx' + ] +}); + +/** Baked JSON candidates when promoted to Open CDN */ +export const FALL_BAKED_CANDIDATES = Object.freeze({ + fallLoop: ['locomotion/fall-loop', 'locomotion/fall_a_loop', 'extra/fall-loop'], + fall: ['locomotion/falling', 'locomotion/fall', 'extra/falling'], + fallLand: ['locomotion/fall-to-landing', 'locomotion/falling-to-landing'], + fallRoll: ['locomotion/fall-to-roll', 'locomotion/falling-to-roll'], + fallIdle: ['locomotion/fall-idle', 'locomotion/falling-idle'] +}); + +/** + * Deterministic thresholds (SI m/s). Tune in settings.drc.fall later if needed. + */ +export const FALL_THRESHOLDS = Object.freeze({ + /** Enter fallLoop when vertical velocity below this (descending) */ + fallStartVy: -0.55, + /** Soft land if |impactVy| below this */ + softLandVy: 4.2, + /** Hard land / roll if |impactVy| at or above this and moving forward */ + rollImpactVy: 6.5, + /** Min horizontal speed (m/s) to prefer fallRoll over fallLand on hard impact */ + rollMinHoriz: 1.8, + /** Blend into fallLoop from jump (s) */ + fallInBlend: 0.16, + /** Blend out land/roll to gait (s) */ + landOutBlend: 0.14, + /** Min airborne time before fallLoop (avoid flicker on small hops) */ + minAirBeforeFall: 0.12, + /** fallLand / fallRoll lock duration floor (s) if clip missing */ + landLockFloor: 0.35 +}); + +/** + * Pick land recovery role from impact (deterministic). + * @param {{ impactVy: number, horizSpeed: number, wantRoll?: boolean }} opts + * @returns {'fallRoll'|'fallLand'} + */ +export function pickLandRole(opts) { + const t = FALL_THRESHOLDS; + const iv = Math.abs(Number(opts.impactVy) || 0); + const h = Math.abs(Number(opts.horizSpeed) || 0); + const hard = iv >= t.rollImpactVy; + const rolling = + opts.wantRoll === true || (hard && h >= t.rollMinHoriz); + if (rolling && hard) return FALL_ROLES.fallRoll; + return FALL_ROLES.fallLand; +} + +/** + * Should switch from jump hold → fall loop? + * @param {{ airborne: boolean, vy: number, airTime: number, flipping?: boolean }} s + */ +export function shouldEnterFall(s) { + if (!s.airborne || s.flipping) return false; + if ((s.airTime || 0) < FALL_THRESHOLDS.minAirBeforeFall) return false; + return (s.vy || 0) <= FALL_THRESHOLDS.fallStartVy; +} + +/** + * Author map for docs / bake scripts. + */ +export const FALL_AUTHOR_MAP = Object.freeze([ + { + role: 'fallLoop', + disk: 'D:/Games/Models/Fall A Loop.fbx', + public: 'public/anim/locomotion/fall/fall-loop.fbx', + loop: true, + use: 'Descending air cycle' + }, + { + role: 'fall', + disk: 'D:/Games/Models/Falling.fbx', + public: 'public/anim/locomotion/fall/falling.fbx', + loop: true, + use: 'Long falling body (alt / deep fall)' + }, + { + role: 'fallLand', + disk: 'D:/Games/Models/Falling To Landing.fbx', + public: 'public/anim/locomotion/fall/fall-to-landing.fbx', + loop: false, + use: 'Soft / default landing recovery' + }, + { + role: 'fallRoll', + disk: 'D:/Games/Models/Falling To Roll.fbx', + public: 'public/anim/locomotion/fall/fall-to-roll.fbx', + loop: false, + use: 'Hard impact + forward momentum → roll out' + } +]); diff --git a/src/config/fleetEnv.js b/src/config/fleetEnv.js new file mode 100644 index 0000000..9326bf8 --- /dev/null +++ b/src/config/fleetEnv.js @@ -0,0 +1,236 @@ +/** + * Fleet env SSOT for Casting lab (browser). + * + * Data plane (do not invent forks): + * Player / bag / wallet → Railway Postgres via grudge-api (/api/* rewrite) + * Auth → id.grudge-studio.com + * Binary meshes/icons → assets.grudge-studio.com (R2) + * JSON catalogs → info.grudge-studio.com (+ Pages.dev mirror; not objectstore /api/v1) + * Asset INDEX only → D1 (never player SSOT) + * Skill drafts → weapon-skills DO + * + * SPA never holds DATABASE_URL or CF tokens. + * @see docs/CASTING_DEPLOY_ENV_SSOT.md · docs/TRAINING_ROOM_SSOT.md + */ + +function env(key, fallback = '') { + try { + const v = typeof import.meta !== 'undefined' ? import.meta.env?.[key] : undefined; + if (v === undefined || v === null || v === '') return fallback; + return String(v).replace(/\/+$/, ''); + } catch { + return fallback; + } +} + +/** R2 CDN binaries */ +export const ASSETS_URL = env('VITE_ASSETS_URL', 'https://assets.grudge-studio.com'); + +/** ObjectStore JSON API (custom domain — /api/v1 catalogs 404 as of 2026-08-18) */ +export const OBJECTSTORE_URL = env( + 'VITE_OBJECTSTORE_URL', + 'https://objectstore.grudge-studio.com' +); + +/** + * Live ObjectStore Pages (catalog JSON actually 200). + * Do not treat objectstore.grudge-studio.com/api/v1 as a second catalog DB. + */ +export const OBJECTSTORE_PAGES_URL = env( + 'VITE_OBJECTSTORE_PAGES_URL', + 'https://grudge-objectstore.pages.dev' +); + +/** Info gamedata API (catalogs — live SSOT for JSON) */ +export const INFO_API = env('VITE_INFO_API', 'https://info.grudge-studio.com/api/v1'); + +/** + * Catalog JSON fetch order. Same Railway player DB is never involved. + * 1. same-origin /api/info (casting rewrites) + * 2. info.grudge-studio.com + * 3. ObjectStore Pages (working git host) + * Never objectstore.grudge-studio.com/api/v1 — those keys 404. + * @param {string} file e.g. t0-weapons.json + * @returns {string[]} + */ +export function catalogJsonUrls(file) { + const name = String(file || '').replace(/^\/+/, ''); + const info = String(INFO_API || '').replace(/\/+$/, ''); + return [ + `/api/info/v1/${name}`, + `${info}/${name}`, + `${OBJECTSTORE_PAGES_URL}/api/v1/${name}` + ]; +} + +/** Grudge ID SSO */ +export const AUTH_URL = env('VITE_AUTH_URL', 'https://id.grudge-studio.com'); + +/** Weapon skill drafts Durable Object */ +export const WEAPON_SKILL_DO_URL = env( + 'VITE_WEAPON_SKILL_DO_URL', + 'https://weapon-skills.grudge-studio.com' +); + +/** Railway absolute (player API fallback) */ +export const RAILWAY_API = env( + 'VITE_RAILWAY_API', + 'https://grudge-api-production-0d46.up.railway.app' +); + +/** ThreeFlow embed — prefab / asset editor (not a second Admin Hub). */ +export const THREEFLOW_URL = env( + 'VITE_THREEFLOW_URL', + 'https://threeflow-grudgenexus.vercel.app' +); + +/** + * Open ThreeFlow with an asset for prefab authoring. + * @param {{ assetUrl?: string, name?: string, intent?: string }} [opts] + */ +export function threeflowPrefabUrl(opts = {}) { + const u = new URL(THREEFLOW_URL.replace(/\/+$/, '') + '/'); + u.searchParams.set('embed', '1'); + u.searchParams.set('intent', opts.intent || 'prefab'); + u.searchParams.set('returnTo', CASTING_CONTROL_PLANE); + if (opts.assetUrl) u.searchParams.set('asset', opts.assetUrl); + if (opts.name) u.searchParams.set('name', opts.name); + return u.toString(); +} + +/** Control plane hosts */ +export const CASTING_CONTROL_PLANE = 'https://casting.grudge.studio'; +export const CASTING_LEGACY_HOST = 'https://casting.grudge-studio.com'; +export const CASTING_VERCEL = + 'https://casting-abilities-threejs.vercel.app'; + +/** + * R2 / CDN key prefix for Training Room · DevIsland when promoted off Vercel static. + * Lab still ships meshes under /models/dev-island on the SPA for fast deploys; + * production promote copies same keys to R2 under this prefix + D1 index rows. + */ +export const TRAINING_ROOM_R2_PREFIX = 'lab/casting/training-room'; + +/** Large lab meshes (rare fish / ride / summons) when promoted off Vercel */ +export const CASTING_LAB_R2_PREFIX = 'lab/casting'; + +/** Published layout JSON keys (info/objectstore + local public mirror) */ +export const TRAINING_ROOM_LAYOUT_KEYS = Object.freeze({ + /** Same-origin (Vercel dist / public) — always works on cast deploy */ + local: './maps/training_room/layout.default.json', + /** ObjectStore mirror path (when uploaded) */ + objectstore: `/api/objectstore/v1/maps/training_room/layout.json`, + /** Info API path (when uploaded) */ + info: `/api/info/v1/maps/training_room/layout.json`, + /** R2 via same-origin proxy */ + cdn: `/api/assets/${TRAINING_ROOM_R2_PREFIX}/layout.default.json` +}); + +/** + * Absolute CDN URL from path or full URL. + * @param {string|null|undefined} path + */ +export function cdnUrl(path) { + if (!path) return null; + if (/^https?:\/\//i.test(path)) return sameOriginFleetUrl(path); + const p = String(path).replace(/^\/+/, ''); + return sameOriginFleetUrl(`${ASSETS_URL}/${p}`); +} + +/** + * Browser on casting.* cannot CORS-read assets.grudge-studio.com / open.* + * (no Access-Control-Allow-Origin). Rewrite to same-origin Vercel proxies. + * @param {string} url + * @returns {string} + */ +export function sameOriginFleetUrl(url) { + if (!url || typeof url !== 'string') return url; + if (url.startsWith('./') || url.startsWith('/') || url.startsWith('data:')) return url; + try { + const base = + typeof location !== 'undefined' && location.origin + ? location.origin + : CASTING_CONTROL_PLANE; + const u = new URL(url, base); + if (u.hostname === 'assets.grudge-studio.com') { + return `/api/assets${u.pathname}${u.search}`; + } + if (u.hostname === 'open.grudge-studio.com') { + return `/api/open${u.pathname}${u.search}`; + } + if (u.hostname === 'objectstore.grudge-studio.com' || u.hostname === 'grudge-objectstore.pages.dev') { + const path = u.pathname.replace(/^\/api(?=\/)/, ''); + return `/api/objectstore${path}${u.search}`; + } + if (u.hostname === 'info.grudge-studio.com') { + const path = u.pathname.replace(/^\/api(?=\/)/, ''); + return `/api/info${path}${u.search}`; + } + } catch { + /* keep */ + } + return url; +} + +/** + * Resolve a lab mesh/icon for best deploy behavior: + * 1. Already absolute (https) → keep + * 2. Same-origin relative (./models/… or /models/…) → keep (Vercel ships public/) + * 3. Bare R2 key → CDN + * + * @param {string|null|undefined} path + * @param {{ preferCdn?: boolean }} [opts] + */ +export function resolveLabAssetUrl(path, opts = {}) { + if (!path) return null; + const s = String(path); + if (/^https?:\/\//i.test(s)) return sameOriginFleetUrl(s); + if (opts.preferCdn) { + // Map local lab paths → R2 promote keys + const bare = s.replace(/^\.\//, '').replace(/^\//, ''); + if (bare.startsWith('models/dev-island/')) { + return sameOriginFleetUrl( + `${ASSETS_URL}/${TRAINING_ROOM_R2_PREFIX}/${bare.slice('models/'.length)}` + ); + } + if (bare.startsWith('icons/dev-island/')) { + return sameOriginFleetUrl(`${ASSETS_URL}/${TRAINING_ROOM_R2_PREFIX}/${bare}`); + } + if ( + bare.startsWith('models/fish/species/rare/') || + bare.startsWith('models/ride/') || + bare.startsWith('models/vfx/summons/') + ) { + return sameOriginFleetUrl(`${ASSETS_URL}/${CASTING_LAB_R2_PREFIX}/${bare}`); + } + return cdnUrl(bare); + } + // Default: same-origin for casting Vercel (public/ in dist) + if (s.startsWith('./') || s.startsWith('/')) return s; + return `./${s}`; +} + +/** + * Production readiness snapshot for HUD / Admin. + */ +export function fleetDeploySnapshot() { + return { + assetsUrl: ASSETS_URL, + objectstoreUrl: OBJECTSTORE_URL, + infoApi: INFO_API, + authUrl: AUTH_URL, + weaponSkillDo: WEAPON_SKILL_DO_URL, + railwayApi: RAILWAY_API, + controlPlane: CASTING_CONTROL_PLANE, + trainingRoomR2Prefix: TRAINING_ROOM_R2_PREFIX, + castingLabR2Prefix: CASTING_LAB_R2_PREFIX, + layoutKeys: { ...TRAINING_ROOM_LAYOUT_KEYS }, + authority: { + player: 'Railway Postgres (grudge-api)', + binaries: 'R2 assets.grudge-studio.com', + catalogs: 'info.grudge-studio.com (+ Pages mirror)', + index: 'D1 (search only — not player)', + drafts: 'WeaponSkillDrafts DO' + } + }; +} diff --git a/src/config/grudge6SSOT.js b/src/config/grudge6SSOT.js new file mode 100644 index 0000000..b86b69f --- /dev/null +++ b/src/config/grudge6SSOT.js @@ -0,0 +1,290 @@ +/** + * Warlords character SSOT for this lab — matches live deploy: + * https://casting-abilities-threejs.vercel.app/ + * + * Code: toonKitPlay.deployToonPlayKit · ObjectStore loadRaceKit parity + * Mesh: assets…/toon-rts-characters/glb/characters/{race}.glb ONLY + * Contract: WARLORDS_PLAY_CONTRACT_VERSION (same as ObjectStore) + * + * Do not add races bake / metaverse / FBX play loaders here. + */ + +import { sameOriginFleetUrl } from './fleetEnv.js'; + +export const GRUDGE6_SSOT_VERSION = '2026-08-13.thirty-original'; +/** Must match ObjectStore WARLORDS_PLAY_CONTRACT_VERSION */ +export const WARLORDS_PLAY_CONTRACT_VERSION = '2026-08-07.harden.1'; + +/** Live lab that owns Warlords character + ability UX proof */ +export const CASTING_LAB_LIVE = 'https://casting.grudge.studio/'; +/** Always-on Vercel project URL (alias of same deploy) */ +export const CASTING_LAB_VERCEL = 'https://casting-abilities-threejs.vercel.app/'; + +export const CDN = 'https://assets.grudge-studio.com'; +export const CDN_MIRROR_OPEN = 'https://open.grudge-studio.com'; +export const HUMAN_HEIGHT_M = 1.8; +export const ANIMS_BAKED = sameOriginFleetUrl(`${CDN}/prod/anims`); +export const GEAR_PRESETS_URL = sameOriginFleetUrl(`${CDN}/api/v1/grudge6-gear-presets.json`); +export const RACE_MODELS_URL = sameOriginFleetUrl( + `${CDN}/asset-packs/toon-rts-characters/race-models.json` +); +/** Canonical Toon RTS kit directory (race-models.json) */ +export const TOON_RTS_GLB_DIR = `${CDN}/asset-packs/toon-rts-characters/glb/characters`; + +export const BIP001_CORE_BONES = Object.freeze([ + 'Bip001 Pelvis', + 'Bip001 Spine', + 'Bip001 Neck', + 'Bip001 Head', + 'Bip001 L Clavicle', + 'Bip001 L UpperArm', + 'Bip001 L Forearm', + 'Bip001 L Hand', + 'Bip001 R Clavicle', + 'Bip001 R UpperArm', + 'Bip001 R Forearm', + 'Bip001 R Hand', + 'Bip001 L Thigh', + 'Bip001 L Calf', + 'Bip001 L Foot', + 'Bip001 R Thigh', + 'Bip001 R Calf', + 'Bip001 R Foot', +]); + +export const BIP001_BONE_COUNT = BIP001_CORE_BONES.length; + +/** Kit child mesh utility + inventory back family (windsurf is external attach) */ +export const UTILITY_SLOTS = Object.freeze(['bag', 'wood', 'quiver', 'back']); + +/** + * Equipment slot SSOT (matches info canonical-equipment-pattern + UMMORPG names). + * Hands use WeaponMeshAttach; back uses BackSlotEquip (deployable vehicle). + */ +export const EQUIPMENT_SLOTS = Object.freeze({ + mainHand: { bone: 'R_hand_container', attach: 'weapon' }, + offHand: { bone: 'L_hand_container', attach: 'weapon' }, + head: { meshIds: true }, + body: { meshIds: true }, + arms: { meshIds: true }, + legs: { meshIds: true }, + back: { bone: 'Bip001 Spine1', attach: 'backSlot', deployable: true }, + relic: { hudOnly: true }, + mount: { controller: 'mount' } +}); + +export function normalizeBoneKey(name) { + return String(name || '') + .trim() + .toLowerCase() + .replace(/[^a-z0-9]/g, ''); +} + +export function validateBip001Bones(root) { + const byKey = new Map(); + const actualNames = []; + root.traverse((n) => { + const name = n.name || ''; + if (!name) return; + const isBone = n.isBone === true || n.type === 'Bone'; + if (!isBone && !/^bip001/i.test(name)) return; + actualNames.push(name); + const k = normalizeBoneKey(name); + if (!byKey.has(k)) byKey.set(k, name); + }); + + const found = []; + const missing = []; + for (const want of BIP001_CORE_BONES) { + const hit = byKey.get(normalizeBoneKey(want)); + if (hit) found.push(hit); + else missing.push(want); + } + + return { + ok: missing.length === 0 && found.length >= BIP001_BONE_COUNT, + found, + missing, + count: found.length, + expected: BIP001_BONE_COUNT, + boneNodes: actualNames.length, + sampleNames: actualNames.slice(0, 8), + }; +} + +/** ★ Toon RTS play URL — only play mesh family (human.glb not WK.glb) */ +export function toonRtsKitUrl(libraryId) { + const id = String(libraryId || 'human').toLowerCase(); + return sameOriginFleetUrl(`${TOON_RTS_GLB_DIR}/${id}.glb`); +} + +/** + * @typedef {{ + * id: string, + * short: string, + * prefix: string, + * label: string, + * kitGlb: string, + * atlasUrl: string, + * }} RaceDef + */ + +/** PLAY races — Toon RTS kitGlb only (no kitFallback / FBX fields for play). */ +/** @type {Record} */ +export const RACES = { + WK: { + id: 'WK', + short: 'human', + prefix: 'WK_', + label: 'Western Kingdoms', + kitGlb: toonRtsKitUrl('human'), + atlasUrl: sameOriginFleetUrl(`${CDN}/textures/grudge6/western-kingdoms/WK_Standard_Units.webp`), + }, + ELF: { + id: 'ELF', + short: 'elf', + prefix: 'ELF_', + label: 'High Elves', + kitGlb: toonRtsKitUrl('elf'), + atlasUrl: sameOriginFleetUrl(`${CDN}/textures/grudge6/elves/ELF_HighElves_Texture.webp`), + }, + BRB: { + id: 'BRB', + short: 'barbarian', + prefix: 'BRB_', + label: 'Barbarians', + kitGlb: toonRtsKitUrl('barbarian'), + atlasUrl: sameOriginFleetUrl(`${CDN}/textures/grudge6/barbarians/BRB_StandardUnits_texture.webp`), + }, + ORC: { + id: 'ORC', + short: 'orc', + prefix: 'ORC_', + label: 'Orcs', + kitGlb: toonRtsKitUrl('orc'), + atlasUrl: sameOriginFleetUrl(`${CDN}/textures/grudge6/orcs/ORC_StandardUnits.webp`), + }, + UD: { + id: 'UD', + short: 'undead', + prefix: 'UD_', + label: 'Undead', + kitGlb: toonRtsKitUrl('undead'), + atlasUrl: sameOriginFleetUrl(`${CDN}/textures/grudge6/undead/UD_Standard_Units.webp`), + }, + DWF: { + id: 'DWF', + short: 'dwarf', + prefix: 'DWF_', + label: 'Dwarves', + kitGlb: toonRtsKitUrl('dwarf'), + atlasUrl: sameOriginFleetUrl(`${CDN}/textures/grudge6/dwarves/DWF_Standard_Units.webp`), + }, +}; + +export const DEFAULT_RACE = 'WK'; + +export function raceDef(raceId) { + return RACES[raceId] || RACES[DEFAULT_RACE]; +} + +export function kitUrlForRace(raceId) { + return raceDef(raceId).kitGlb; +} + +/** + * Play candidates = Toon RTS only (short name from raceDef). + * Do not chain races bake / metaverse — those hide broken Toon loads behind wrong models. + */ +export function kitUrlCandidates(raceId) { + const def = raceDef(raceId); + const short = def.short || 'human'; + const primary = def.kitGlb || toonRtsKitUrl(short); + // Same file under rare alternate keys (legacy docs used race id) + const alt = toonRtsKitUrl(short); + return [...new Set([primary, alt].filter(Boolean))]; +} + +export function atlasUrlForRace(raceId) { + return raceDef(raceId).atlasUrl; +} + +export function isToonRtsKitUrl(url) { + return /asset-packs\/toon-rts-characters\/glb\/characters\/[a-z]+\.glb/i.test(String(url || '')); +} + +/** + * Slot/variant loadout → mesh names on Toon RTS kits. + * Author: WK_Units_Body_A · WK_Units_sword_A · WK_Units_Staff_A · WK_Shield_A + * Also accept weapon_ prefix from older bakes. + */ +export function loadoutToMeshIds(prefix, loadout = {}) { + const p = prefix.endsWith('_') ? prefix : `${prefix}_`; + const ids = []; + const letter = (v) => String(v || 'A').toUpperCase(); + + // Armor + const armor = { + body: 'Units_Body', + arms: 'Units_Arms', + legs: 'Units_Legs', + head: 'Units_head', + shoulders: 'Units_shoulderpads', + }; + for (const [slot, stem] of Object.entries(armor)) { + const v = loadout[slot]; + if (!v || v === 'none') continue; + ids.push(`${p}${stem}_${letter(v)}`); + } + + // Weapons — push both Toon author and weapon_ forms; exclusive equip picks best + const addWeapon = (stemToon, stemLegacy, v) => { + if (!v || v === 'none') return; + if (v === '_default') { + ids.push(`${p}${stemToon}`); + ids.push(`${p}${stemLegacy}`); + return; + } + const L = letter(v); + ids.push(`${p}${stemToon}_${L}`); + ids.push(`${p}${stemLegacy}_${L}`); + // BRB sometimes omits Units_ + ids.push(`${p}${stemToon.replace(/^Units_/, '')}_${L}`); + }; + + if (loadout.sword) addWeapon('Units_sword', 'weapon_sword', loadout.sword); + if (loadout.axe) addWeapon('Units_axe', 'weapon_axe', loadout.axe); + if (loadout.hammer) addWeapon('Units_hammer', 'weapon_hammer', loadout.hammer); + if (loadout.spear) addWeapon('Units_spear', 'weapon_spear', loadout.spear); + if (loadout.staff) { + addWeapon('Units_Staff', 'weapon_staff', loadout.staff); + addWeapon('Units_staff', 'weapon_staff', loadout.staff); + } + if (loadout.bow) { + ids.push(`${p}Units_Bow`); + ids.push(`${p}weapon_Bow`); + ids.push(`${p}Bow`); + } + if (loadout.shield && loadout.shield !== 'none') { + const L = loadout.shield === '_default' ? 'A' : letter(loadout.shield); + ids.push(`${p}Shield_${L}`); + ids.push(`${p}Units_Shield_${L}`); + } + + // Archer quiver is part of the original 30 look. Bag/wood stay carry-only. + if (loadout.quiver) ids.push(`${p}Xtra_quiver`); + const allowUtility = loadout.carry === true || loadout.showUtility === true; + if (allowUtility) { + if (loadout.bag) ids.push(`${p}Xtra_bag`); + if (loadout.wood) ids.push(`${p}Xtra_wood`); + } + + return [...new Set(ids)]; +} + +export function logSSOT() { + console.info( + `[grudge6SSOT ${GRUDGE6_SSOT_VERSION}] play=ToonRTS★ CDN=${CDN} ` + + `human=${HUMAN_HEIGHT_M}m anims=${ANIMS_BAKED}`, + ); +} diff --git a/src/config/labAdmin.js b/src/config/labAdmin.js new file mode 100644 index 0000000..3bd0de6 --- /dev/null +++ b/src/config/labAdmin.js @@ -0,0 +1,101 @@ +/** + * Lab admin gates for casting stage (T0 catalog tiers, default weapon). + * ?admin=1 or localStorage grudge_lab_admin=1 enables full tier smoke path. + */ + +const STORAGE_KEY = 'grudge_lab_admin'; + +function queryFlag(name) { + try { + return new URLSearchParams(location.search).get(name); + } catch { + return null; + } +} + +/** + * @returns {{ + * admin: boolean, + * defaultWeaponId: string|null, + * maxTier: number, + * playerLevel: number + * }} + */ +export function loadLabAdmin() { + let stored = false; + try { + stored = localStorage.getItem(STORAGE_KEY) === '1'; + } catch { + /* ignore */ + } + const q = queryFlag('admin'); + if (q === '1' || q === 'true') { + try { + localStorage.setItem(STORAGE_KEY, '1'); + } catch { + /* ignore */ + } + stored = true; + } else if (q === '0' || q === 'false') { + try { + localStorage.removeItem(STORAGE_KEY); + } catch { + /* ignore */ + } + stored = false; + } + + const maxTier = Number(queryFlag('maxTier') ?? queryFlag('tiers') ?? (stored ? 5 : 0)) || 0; + const playerLevel = Number(queryFlag('level') ?? queryFlag('lv') ?? (stored ? 60 : 1)) || 1; + const defaultWeaponId = + queryFlag('t0') || queryFlag('weapon') || (stored ? 't0-sword' : null); + + return { + admin: !!stored || maxTier > 0, + defaultWeaponId, + maxTier: Math.max(0, Math.min(10, maxTier)), + playerLevel: Math.max(1, Math.min(100, playerLevel)) + }; +} + +export function isLabAdmin() { + return loadLabAdmin().admin; +} + +export function labMaxTier() { + return loadLabAdmin().maxTier; +} + +export function labPlayerLevel() { + return loadLabAdmin().playerLevel; +} + +export function labAdminStatusLine() { + const a = loadLabAdmin(); + if (!a.admin) return 'lab admin: off (?admin=1)'; + return `lab admin: on · maxTier=${a.maxTier} · lv=${a.playerLevel} · default=${a.defaultWeaponId || '—'}`; +} + +/** + * Persist lab admin flags (AdminHub). Partial patch over loadLabAdmin(). + * @param {{ admin?: boolean, level?: number, maxTier?: number, classId?: string, defaultWeaponId?: string|null }} patch + */ +export function saveLabAdmin(patch = {}) { + try { + if (patch.admin === true) localStorage.setItem(STORAGE_KEY, '1'); + else if (patch.admin === false) localStorage.removeItem(STORAGE_KEY); + } catch { + /* private */ + } + // Query overrides still win on next load; optional keys stashed for status + try { + if (patch.level != null) localStorage.setItem('grudge_lab_level', String(patch.level)); + if (patch.maxTier != null) localStorage.setItem('grudge_lab_max_tiers', String(patch.maxTier)); + if (patch.defaultWeaponId != null) { + localStorage.setItem('grudge_lab_default_weapon', String(patch.defaultWeaponId)); + } + } catch { + /* */ + } + return loadLabAdmin(); +} diff --git a/src/config/meleeAnimSsot.js b/src/config/meleeAnimSsot.js new file mode 100644 index 0000000..9fd2e67 --- /dev/null +++ b/src/config/meleeAnimSsot.js @@ -0,0 +1,25 @@ +/** + * 2H melee locomotion / idle SSOT (author FBX → lab public path). + * + * Author: D:\Games\Models\2hand Idle.fbx + * Lab: public/anim/melee/2hand-idle.fbx + * + * Used as sword_shield pack **idle** (2H melee stance) before 1H S&S baked idle. + */ + +/** Absolute + same-origin candidates for 2-hand melee idle. */ +export const MELEE_2H_IDLE_FBX_URLS = Object.freeze([ + './anim/melee/2hand-idle.fbx', + '/anim/melee/2hand-idle.fbx', + // Local author path is not web-served — ship under public/ only +]); + +/** + * Role → FBX URL list (same shape as FALL_FBX_URLS). + * @type {Readonly>} + */ +export const MELEE_FBX_URLS = Object.freeze({ + /** 2H melee idle — primary idle for sword_shield pack */ + idle2h: MELEE_2H_IDLE_FBX_URLS, + idle: MELEE_2H_IDLE_FBX_URLS, +}); diff --git a/src/config/pistolAnimSsot.js b/src/config/pistolAnimSsot.js new file mode 100644 index 0000000..7e0acd8 --- /dev/null +++ b/src/config/pistolAnimSsot.js @@ -0,0 +1,259 @@ +/** + * T0 pistol animation SSOT — Mixamo Bip001 pack + Minecraft TPS timing reference. + * + * Live clips (Open): open.grudge-studio.com/anims/baked/pistol/* + * Author FBX: gameopen/artifacts/animator/public/anim/pistol/ + * gunplay.fbx · drawing-gun.fbx · charged-pistol.fbx · pistol-whip.fbx · loco* + * + * Incoming author pack (2026-08): + * D:\Games\Models\_anim_packs\grudge6_incoming_2026-08-01\grudgepistolzio\ + * → one-hand gun / crossbow loco + kneel (Mixamo Bip001). Combat fire still uses + * Open baked gunplay/draw/whip until zio combat clips are baked to Open CDN. + * + * TPS reference (voxel gunplay mesh + short clips, NOT Bip001): + * Lab copy: public/models/reference/minecraft_tps_pistol.glb + * + * Do not bind TPS clips onto grudge6 — skeleton is rigid Minecraft nodes + * (Rightarm_14, Pistol_13, Slide_4…). Use for timing + prop motion only. + * + * Weapon mesh: flintlock.glb → public/models/weapons/t0-flintlock.glb for t0-gun + * Bullet: Styloo bullet1.glb → public/models/vfx/projectiles/bullet1.glb + */ + +export const PISTOL_PACK_ID = 'pistol'; + +/** Live Bip001 baked roles (Casting ANIM_PACKS.pistol). */ +export const PISTOL_BIP001_ROLES = Object.freeze({ + idle: 'pistol/idle', + walk: 'pistol/walk-forward', + run: 'pistol/run-forward', + strafeL: 'pistol/strafe-left', + strafeR: 'pistol/strafe-right', + /** Gun spin / flourish (primary attack) */ + gunplay: 'pistol/gunplay', + spin: 'pistol/gunplay', + draw: 'pistol/drawing-gun', + /** Powder reload — baked alias of drawing-gun until dedicated FBX */ + reload: 'pistol/reload', + charged: 'pistol/charged-pistol', + whip: 'pistol/pistol-whip', + jump: 'pistol/pistol-jump' +}); + +/** + * Minecraft TPS model clip timings (seconds) — author feel for T0 fire cadence. + * Source: minecraft_tps_model_1780812780503.glb + */ +export const TPS_PISTOL_TIMING = Object.freeze({ + sourceGlb: 'minecraft_tps_model_1780812780503.glb', + clips: { + idle: 9.083, + walk: 0.875, + draw: 0.25, + drawnidle: 1.958, + fire: 0.208, + drawaim: 0.167, + drawaimidle: 1.625, + fireaim: 0.208 + }, + nodes: { + pistol: 'Pistol_13', + slide: 'Slide_4', + mag: 'Mag_6', + trigger: 'Trigger_12', + rightArm: 'Rightarm_14', + leftArm: 'Leftarm_19' + } +}); + +/** + * Map TPS clip → Casting / Multiverse role. + */ +export const TPS_TO_FLEET_ROLE = Object.freeze({ + idle: 'idle', + walk: 'walk', + draw: 'draw', + drawnidle: 'idle', + fire: 'attack', + drawaim: 'draw', + drawaimidle: 'cast', + fireaim: 'attack' +}); + +/** + * Bip001 clip durations (approx from live bake) for timeScale / CD hints. + */ +export const PISTOL_BIP001_DURATION = Object.freeze({ + idle: 1.333, + 'walk-forward': 0.8, + 'run-forward': 0.5, + gunplay: 0.567, + 'drawing-gun': 2.0, + 'charged-pistol': 2.533, + 'pistol-whip': 2.567 +}); + +/** + * Suggested timeScale so Mixamo gunplay/fire feels closer to TPS snap (~0.21s). + * gunplay ~0.57s → scale ≈ 0.57/0.21 ≈ 2.7 for hyper-snap; use milder default. + * @param {'fire'|'draw'|'spin'|'charged'|'whip'|'reload'} kind + */ +export function pistolTimeScale(kind = 'fire') { + const tps = TPS_PISTOL_TIMING.clips; + switch (kind) { + case 'fire': + case 'spin': + // gunplay 0.567 → aim ~0.38s (readable flintlock snap, not hyper) + return Math.min(1.85, PISTOL_BIP001_DURATION.gunplay / 0.38); + case 'draw': + return Math.min(1.55, PISTOL_BIP001_DURATION['drawing-gun'] / Math.max(0.45, tps.draw * 2.2)); + case 'reload': + // drawing-gun as reload base — stretch slightly for powder pour overlay + return Math.min(1.15, PISTOL_BIP001_DURATION['drawing-gun'] / FLINTLOCK_RELOAD.durationSec); + case 'charged': + return 0.95; + case 'whip': + return 1.05; + default: + return 1.0; + } +} + +/** + * Flintlock fire cadence — hit frame + burst gap + muzzle (SI). + * Aligned with TPS fire (~0.21s) on scaled gunplay (~0.38s wall). + */ +export const FLINTLOCK_FIRE = Object.freeze({ + /** Seconds into fire clip when bullet leaves barrel (after timeScale) */ + hitFrameSec: 0.14, + /** Wall-clock fire clip target (s) after timeScale */ + fireDurationSec: 0.38, + /** Burst Fire gap between rounds (s) */ + burstGapSec: 0.09, + /** Small yaw fan per burst index (rad) */ + burstSpreadRad: 0.026, + /** Hand → muzzle along barrel when marker missing (m) */ + muzzleFallbackM: 0.42, + /** Delay after last bullet before reload pose (s) */ + reloadAfterShotSec: 0.12 +}); + +/** + * Procedural powder reload (power-shot refill feel). + * Gun rotates toward body middle · off-hand to barrel · barrel tilts up. + */ +export const FLINTLOCK_RELOAD = Object.freeze({ + durationSec: 0.92, + powerDurationSec: 1.25, + /** Degrees barrel tilts up during pour phase */ + barrelTiltDeg: 22, + /** 0..1 how hard gun pulls toward chest midline */ + gunInWeight: 0.72, + /** + * Auto procedural reload after every shot — OFF for production chamber model. + * Empty → digit 1 is Reload (user must press 1). Lab can set true. + */ + afterShot: false, + /** Baked clip role (ANIM_PACKS.pistol.reload) */ + bakedRole: 'reload', + bakedClip: 'pistol/reload' +}); + +/** + * Soft-lock / crosshair assist when pistol pack is equipped. + * Stronger magnetic pull than default melee — flintlock is loud not accurate. + */ +export const PISTOL_SOFT_LOCK = Object.freeze({ + softLockBlend: 0.82, + softLockMaxAngleDeg: 34, + softLockRange: 36, + /** Crosshair spread when soft-lock snug */ + spreadSoft: 0.06, + /** Crosshair spread free aim (homemade inaccurate) */ + spreadFree: 0.22, + optimalRangeMin: 3, + optimalRangeMax: 18, + /** Auto-acquire nearest frontal target on fire if none selected */ + acquireOnFire: true +}); + +/** Lab reference GLB path (same-origin after copy). */ +export const TPS_PISTOL_REF_URL = './models/reference/minecraft_tps_pistol.glb'; + +/** + * Role to play for a weapon skill slot on pistol pack. + * @param {'primary'|'secondary'|'ability'|string} slotType + * @param {number} [abilityIndex] + */ +export function pistolRoleForSkillSlot(slotType, abilityIndex = 0) { + if (slotType === 'primary') return 'attack'; // gunplay spin + if (slotType === 'secondary') return 'draw'; + if (slotType === 'reload') return 'reload'; + const skills = ['skill1', 'skill2', 'skill3', 'skill4', 'skill5']; + return skills[abilityIndex % skills.length] || 'gunplay'; +} + +/** + * Open Danger weapon-live-packs pistol.skillSlots → casting / anim map. + * @see gameopen/content/anims/weapon-live-packs.json + */ +export const OPEN_DANGER_PISTOL_SLOTS = Object.freeze([ + { id: 'pistol_shot', label: 'Shot', anim: 'gunplay', t0: 't0_gun_practice_shot', needsLoad: true }, + { id: 'pistol_double', label: 'Double/Burst', anim: 'gunplay', t0: 't0_gun_burst_fire', needsLoad: true }, + { id: 'pistol_fan', label: 'Fan/Suppress', anim: 'charged-pistol', t0: 't0_gun_suppressing_shot', needsLoad: true }, + { id: 'pistol_reload', label: 'Reload', anim: 'reload', t0: 't0_gun_reload', needsLoad: false } +]); + +/** + * Hit-frame delay (s) for bullet spawn — catalog override wins. + * @param {object} [skill] + */ +export function pistolHitFrameSec(skill = {}) { + if (skill.hitFrameDelay != null && Number(skill.hitFrameDelay) >= 0) { + return Number(skill.hitFrameDelay); + } + return FLINTLOCK_FIRE.hitFrameSec; +} + +/** + * grudgepistolzio incoming author clips (review 2026-08). + * One-hand guns + can cover crossbow-style loco; combat still Open bake. + */ +export const GRUDGE_PISTOL_ZIO_INCOMING = Object.freeze({ + path: 'D:/Games/Models/_anim_packs/grudge6_incoming_2026-08-01/grudgepistolzio', + use: 'one_hand_gun_and_crossbow_loco', + clips: Object.freeze([ + 'pistol idle', + 'pistol walk', + 'pistol run', + 'pistol walk backward', + 'pistol run backward', + 'pistol strafe', + 'pistol strafe (2)', + 'pistol walk arc', + 'pistol walk arc (2)', + 'pistol run arc', + 'pistol run arc (2)', + 'pistol run backward arc', + 'pistol run backward arc (2)', + 'pistol walk backward arc', + 'pistol walk backward arc (2)', + 'pistol jump', + 'pistol jump (2)', + 'pistol stand to kneel', + 'pistol kneeling idle', + 'pistol kneel to stand', + 'Heavy_mixamo' + ]), + mapToFleet: Object.freeze({ + 'pistol idle': 'idle', + 'pistol walk': 'walk', + 'pistol run': 'run', + 'pistol strafe': 'walkL', + 'pistol strafe (2)': 'walkR', + 'pistol jump': 'jump', + 'pistol kneeling idle': 'cast', + Heavy_mixamo: 'attack' // review — may be heavy weapon, not flint fire + }), + note: 'Bake zio loco to open …/anims/baked/pistol/* when ready; keep combat gunplay/draw/whip from existing bake until author fire clips exist.' +}); diff --git a/src/config/settings.js b/src/config/settings.js index f7e7070..cfd0b01 100644 --- a/src/config/settings.js +++ b/src/config/settings.js @@ -18,13 +18,270 @@ export const settings = { /* Interaction mode — what a drawn path *means* */ /* ------------------------------------------------------------------ */ /** - * 'casting' — the stroke becomes an elemental ability (the original mode). - * 'walk' — the avatar leaps onto the head of the stroke, drops into the - * meditation pose on an air scooter and rides the path to its - * end. See animation/WalkController.js. + * What a finished PathDrawer stroke means: + * - 'casting' → AbilityManager (element staff path cast) + * - 'walk' → WalkController four-phase ride over the same curve: + * leap → ride (path) → freeride/dismount → idle + * See docs/WINDSURF_RIDE_SSOT.md · animation/WalkController.js */ mode: 'casting', + /** + * DRC session: equip (mesh panel / sandbox) vs combat (WASD + skills + TPS). + * Q toggles. Independent of path cast/walk mode (M). + */ + drc: { + session: 'combat', // combat-first showcase (Q → equip) + moveSpeed: 3.6, + sprintMul: 1.65, + /** Jump (Space) — SI m/s */ + jumpVelocity: 5.4, + /** Second air jump (frontflip) vertical m/s */ + doubleJumpVelocity: 5.0, + /** Max jumps before needing ground (1 = single, 2 = double) */ + maxJumps: 2, + /** + * Air mobility (2nd jump): + * - Space alone → quick **frontflip** (standard double jump) + * - S+Space → hard stop + **backflip** (more horizontal) + hang time for air attacks + */ + frontflipDuration: 0.48, + frontflipSpeed: 3.2, + /** Backflip reverse dash m/s (horizontal-heavy) */ + backflipSpeed: 6.8, + /** Backflip upward kick (keep lower than double jump so arc is flat) */ + backflipVertical: 2.4, + /** Seconds for procedural backflip spin */ + backflipDuration: 0.52, + /** Hard zero XZ velocity before reverse dash (s) */ + backflipHardStop: 0.1, + /** + * Gravity scale while hang is active (1 = normal −9.81). + * Lower = float longer so air attacks can aim. + */ + backflipHangGravity: 0.32, + /** Hang duration after backflip starts (s) */ + backflipHangDuration: 1.15, + /** Coyote / buffer feel */ + jumpBufferMs: 120, + /** Double-tap window for AA / DD / WW dodges (ms) */ + doubleTapMs: 280, + /** + * Baseline dodge distance (m) for WW / X. + * AA/DD use motion-math lateral (3×) via dodgeMm.lateral — see combat/motionMath.js + */ + dodgeDistance: 2.4, + /** Lateral AA/DD multiplier when dodgeMm not used (fallback) */ + dodgeLateralMul: 3, + /** + * Motion-math dodge peaks (100 MM = 1 m). Lateral 720 = 7.2 m escape. + * @see combat/motionMath.js DODGE_MM + */ + dodgeMm: { + lateral: 720, + forward: 240, + back: 240 + }, + /** Dodge duration (s) — also one-shot lock + invuln window */ + dodgeDuration: 0.42, + /** Stamina cost per dodge */ + dodgeStamina: 10, + /** I-frames while MM dodge + afterimage trail is live (s); 0 = match dodgeDuration */ + dodgeInvuln: 0, + /** Parry stamina */ + parryStamina: 8, + /** + * Locomotion scheme (Casting combat): + * - Walk: face aim, A/D body-strafe (corrected) + * - Shift: freelook-run — A/D rotate into run (no strafe) + * - Ctrl+A / Ctrl+D: left / right roll (Ghost Rider clips) + * - Shift+Ctrl while sprint: slide + * - AA/DD double-tap: MM escape dodge + wind afterimage + invuln + */ + rollDistance: 3.0, + rollDuration: 0.55, + rollStamina: 12, + slideDistance: 4.2, + slideDuration: 0.72, + slideStamina: 14, + /** + * Dual resources for spells (Warlords production controller). + * Hold LMB + long path → castIntensity 1..3 multiplies mana/stamina. + * @see combat/castResources.js + */ + manaMax: 100, + manaRegen: 12, // /s + staminaRegen: 18, // /s + resources: { + holdWeight: 0.42, + lengthWeight: 0.055, + intensityMin: 1, + intensityMax: 3 + }, + /** + * Weapon charge (Charged Shot) — hold digit/F → ticks → release. + * @see combat/weaponChargeSystem.js + */ + weaponCharge: { + tapMaxSec: 0.18, + maxHoldSec: 1.35, + minChargeSec: 0.22, + tickHz: 20, + restSec: 0.42, + restPerIntensity: 0.12, + cancelRestSec: 0.18, + gcdSec: 0.12, + maxDamageMul: 2.05, + hitFrameMul: 1.15, + label: 'Charged Shot' + }, + /** Move cancels charge after min charge (pistol aim usually false) */ + chargeCancelOnMove: false, + /** Base path-cast costs by placement kind (before intensity) */ + pathCastCosts: { + aoe: { mana: 8, stamina: 6 }, + spikes: { mana: 12, stamina: 10 }, + wall: { mana: 16, stamina: 12 }, + stream: { mana: 10, stamina: 8 } + }, + /** + * Dodge afterimage — blur of model own colors, vapor dissipate. + * @see vfx/DodgeAfterimage.js + */ + afterimage: { + enabled: true, + count: 6, + life: 0.55, + stampInterval: 0.048, + stampLife: 0.32, + vaporRise: 0.55, + vaporExpand: 0.32, + vaporHold: 0.1, + vaporPower: 2.5 + } + }, + + /** + * Staff / path cast from LMB draw (combat + casting modes). + * Hold-drag classifies placement: aoe · spikes · wall · stream + */ + staffCast: { + enabled: true, + /** Short path or tap-place → AOE at endpoint */ + aoeMaxLength: 3.2, + /** Medium stroke → ice spikes / earth spikes along path */ + spikesMaxLength: 9, + /** Longer stroke → wall / barrier along path */ + wallMinLength: 9, + /** Hold seconds that bias toward wall even if path medium */ + wallHoldSec: 0.85, + /** Combat allows shorter paths than sandbox free-cast */ + combatMinPathLength: 0.9, + /** Element override per placement kind (null = selected element) */ + aoeElement: null, + spikesElement: 'water', + wallElement: 'earth', + streamElement: null + }, + + /* ------------------------------------------------------------------ */ + /* Shared effect primitives — isolatable edit knobs (lab → JSON prefab) */ + /* intensity · aoe · speed · size · color · mesh — used by VfxDirector, */ + /* residual melee, and Ability path layers. Do not fork per-skill code. */ + /* ------------------------------------------------------------------ */ + effect: { + /** 0..2 multiplies glow / particle / burst strength */ + intensity: 1.0, + /** metres — impact / residual contact radius scale */ + aoe: 1.2, + /** m/s travel or residual wave speed */ + speed: 12, + /** SI mesh / burst radius scale */ + size: 1.0, + /** #rrggbb override (empty = catalog color) */ + color: '#7dd3fc', + /** mesh id: none | slashblue | slashred | slashpurple | slashyellow | orb-fire | orb-ember | orb-core | orb-flare */ + meshId: 'slashblue', + /** seconds for residual / impact life */ + duration: 0.45, + /** cast tell attach */ + attach: 'R_hand', + /** which primitive the editor is currently authoring */ + activeKind: 'residual' + }, + + /** + * Melee residual (Getsuga-class) — attack-frame only, from weapon tip. + * Mirrors Open meleeStrikeFx projectile + aoe knobs. Space is jump, not this. + */ + residual: { + enabled: true, + /** metres travel from tip (1 light … ~10 finisher) */ + range: 3.2, + /** m/s wave speed */ + speed: 14, + /** visual mesh scale 0.5…2 */ + meshScale: 0.9, + /** path hit radius m */ + contactRadius: 0.65, + /** ground AoE on connect m (0 = none) */ + aoeRadius: 0.8, + intensity: 1.05, + color: '#7dd3fc', + /** slashred | slashblue | slashpurple | slashyellow */ + variant: 'slashblue', + /** tip offset along grip→tip approx (m) when no blade mesh measure */ + tipOffset: 0.55, + /** hit-frame delay after attack start (s) — residual apex / combo apex */ + hitFrameDelay: 0.18, + /** + * Melee hit volume starts this far past the blade tip (m). + * Physics residual + VFX origin use tip + beyondBladeM along blade forward. + */ + beyondBladeM: 0.38, + /** Sample grip→tip ribbon during swing (weapon mesh trail) */ + trailEnabled: true, + /** Seconds of tip sampling around the swing */ + trailDuration: 0.34, + /** Ribbon half-width scale (m) */ + trailWidth: 0.14, + trailMaxPoints: 18, + /** Fire-style blur trail (uses fire Ability path + warm ribbon) */ + fireTrail: true, + trailColor: '#ff6a22', + /** Spawn SkillProjectileSystem residual along blade at apex */ + physicsProjectile: true, + /** Cast fire Ability ribbon along residual path */ + pathTrail: true + }, + + /** + * Sword & shield melee combo (3 light clicks) + finisher. + * Current CDN “sword and shield attack” is the finisher (jump/dash), not light. + * @see docs/MELEE_COMBO_SSOT.md + */ + meleeCombo: { + /** max chain length (click 1→2→3) */ + hits: 3, + /** seconds after a hit to accept next combo click */ + chainWindow: 0.85, + /** light residual range m (hit 1–3) */ + lightRange: 2.6, + /** finisher residual range m */ + finisherRange: 5.5, + /** + * Large MM toward target → force finisher (100 MM = 1 m). + * Sprint toward focus or recent forward impulse above this MM. + */ + finisherMm: 280, + /** body lunge MM during ground finisher */ + finisherLungeMm: 320, + /** air finisher drop MM toward aim (horizontal) */ + airLungeMm: 180, + /** residual range m while airborne finisher */ + airFinisherRange: 6.5 + }, + /* ------------------------------------------------------------------ */ /* Global multipliers — apply to every ability simultaneously */ /* ------------------------------------------------------------------ */ @@ -50,8 +307,73 @@ export const settings = { fresnel: 1.0, opacity: 1.0, animationSpeed: 1.0, // character animation playback rate - cameraShake: 1.0, - explosionIntensity: 1.0 + /** Master camera shake (0 = off). Lab default soft — was 1.0 full trauma. */ + cameraShake: 0.32, + /** Master explosion size / light punch (not shake). */ + explosionIntensity: 0.85 + }, + + /** + * Creative element presentation — how staffs use shared shaders/VFX + * without a second ability engine. + * @see combat/elementPresentation.js · VfxDirector.deployPresentation + */ + presentation: { + /** Multi fire micro-bolts per cast (rendering-friendly vs one fat volume) */ + fireVolleyCount: 5, + fireVolleyDelayMs: 65, + fireVolleySize: 0.32, + /** Meteor: spawn from sky, small projectiles + ground infernos */ + meteorHeight: 14, + meteorShards: 4, + meteorDelayMs: 90, + /** Ice ground-flood then erupt (earth-move timing, water shaders) */ + iceFloodRadius: 4.2, + iceEruptDelayMs: 280, + /** Nature vine lash — green earth surge + heal aura */ + natureVineCount: 3, + natureHealAura: true, + /** Storm defensive shield aura (wall / guard skills) */ + stormShieldRadius: 2.4, + stormShieldDuration: 1.8, + /** + * Lightning / chain lightning (storm offense). + * Narrow · fast · electric core + wind residual — not fat orbs. + */ + lightning: { + /** Primary bolt speed feel (m/s of particle stream) */ + boltSpeed: 42, + /** Segment life (s) — short = snappy electric, not laggy trails */ + boltLife: 0.14, + /** Core thickness (particle size) — keep thin */ + coreSize: 0.045, + /** Outer glow size */ + glowSize: 0.09, + /** Zigzag amplitude (m) along bolt */ + zigzag: 0.28, + /** Segments along primary cast→aim */ + segments: 7, + /** Chain hops after primary (virtual targets) */ + chainHops: 3, + /** Delay between chain hops (ms) — fast cascade */ + hopDelayMs: 38, + /** Lateral search radius for chain hops (m) */ + hopRadius: 3.2, + /** Max hop distance (m) */ + hopRange: 5.5, + /** Wind residual motes along bolt (silk feel) */ + windResidual: true, + /** Soft flash on strike */ + flash: 0.05, + coreColor: '#eef9ff', + glowColor: '#6ec8ff', + arcColor: '#a8e8ff' + }, + /** Arcane void — purple + near-black */ + arcaneCore: '#1a0a28', + arcaneGlow: '#b070ff', + /** Micro bullet size for first projectile in a volley */ + microBulletSize: 0.14 }, /* ------------------------------------------------------------------ */ @@ -92,6 +414,12 @@ export const settings = { character: { pose: 'idle', // 'idle' (the FBX clip) or 'sitting' (animation/SittingPose.js) blendTime: 0.9, // seconds to cross-fade between the two + /** Locomotion crossfade (idle↔walk↔run) — production controller smoothness */ + gaitBlend: 0.28, + /** Blend into charged-pistol wind-up */ + chargeBlend: 0.22, + /** Attack/cast/dodge one-shot fade */ + combatBlend: 0.12, breathing: 1.0, // breath amplitude while seated (0 = perfectly still) breathRate: 0.2, // breaths per second legSpread: 1.0, // widens or narrows the crossed legs @@ -105,30 +433,103 @@ export const settings = { /* Walk mode — the leap, the air scooter and the ride */ /* ------------------------------------------------------------------ */ walk: { - /* --- the leap onto the head of the path --- */ - jumpSpeed: 7.0, // metres/second of ground covered by the leap - jumpHeight: 1.75, // apex of the arc above the straight line - jumpMin: 0.45, // seconds — floor and ceiling on the leap duration, so a - jumpMax: 1.15, // step and a long dive both read as one jump - tuck: 0.62, // fraction of the leap at which the legs fold up - poseBlend: 0.45, // seconds to fold into / out of the seated pose - - /* --- riding the path --- */ - speed: 5.0, // metres/second along the drawn path - accel: 0.45, // seconds spent easing up to speed after landing - brake: 0.6, // seconds of gliding to a stop at the far end - dismountTime: 0.55, // seconds to step off the ball - returnHome: false, // leap back to where he started once the path is ridden - - /* --- how he rides --- */ - hover: 0.06, // gap between the ball and the floor - seatSink: 0.34, // how deep the seat sits into the ball, × radius - bob: 0.035, // vertical bounce while riding, metres - bobRate: 2.3, // bounces per second - lean: 26, // degrees of bank at a full-rate turn - leanRate: 2.0, // radians/second of yaw that counts as a full-rate turn - leanDamping: 0.004, // how quickly the bank follows the turn (damp rate) - turnDamping: 0.0001, // how quickly the body swings onto the new heading + /** + * Windsurf / freeride (tslda / Wind Waker boat feel). + * Deploy = frontflip + sail from back → land feet on deck, hands on boom. + * Back-slot utility asset: public/models/ride/windsurf_package.glb + * @see docs/WINDSURF_RIDE_SSOT.md · Robpayot/tslda boat control + */ + /* --- frontflip deploy onto board --- */ + jumpSpeed: 7.0, + jumpHeight: 2.1, // higher arc for readable frontflip + jumpMin: 0.5, + jumpMax: 1.25, + tuck: 0.55, + poseBlend: 0.35, + /** Frontflip duration during LEAP (matches tilt spin) */ + frontflipDuration: 0.72, + /** Sail/board birth delay into flip (0..1) */ + sailDeployAt: 0.28, + + /* --- path ride (drawn stroke) --- */ + speed: 5.0, + accel: 0.45, + brake: 0.6, + dismountTime: 0.55, + returnHome: false, + /** After path ends, enter freeride instead of auto-dismount */ + freerideAfterPath: true, + + /* --- freeride (WASD boat, tslda-like) --- */ + freeride: true, + freerideSpeed: 7.2, // m/s max + freerideAccel: 4.5, // m/s² + /** Water coast: low drag so releasing W does not stop immediately */ + freerideDrag: 0.55, // m/s² coast (was 1.8 — felt like brakes) + freerideBrakeDrag: 2.4, // m/s² when holding S + freerideTurnRate: 1.85, // rad/s at full A/D + freerideJumpVy: 5.8, // Space hop off waves + freerideGravity: 14, + freerideWaterY: 0.0, // stage water base; bob adds + freerideWaveFollow: 0.9, // how hard board tracks sample height + /** StageWater wave base amp (m); storm multiplies */ + oceanWaveAmp: 0.16, + /** + * Open-sea weather 0..1 (null = auto cycle). Matches StageWater storm. + * water.grudge-studio.com-style open ocean read for freeride. + */ + oceanStorm: null, + /** Soft-body / ragdoll-lite: extra hip drop sway from wave + bank */ + softBody: true, + softBodyHip: 0.06, + softBodyLean: 0.12, + /** Allow 1–4 / F skills while riding (ranged / staff) */ + skillsWhileRide: true, + /** + * Freeride + ranged/staff: LMB path cast in **non-focus** mode (unlocked cursor). + * Melee still uses combat focus/select when not casting path. + */ + freerideRangedCast: true, + /** + * Mesh art yaw (deg) relative to travel +Z (sin/cos world heading). + * 0 = package nose along travel; 180 = stern-first (felt “pointed wrong”). + */ + boardArtYawDeg: 0, + /** Debug: colored spheres on board/sail/front/engine sockets */ + debugSockets: false, + debugParts: false, + /** + * Equip contract: windsurf is a **back-slot vehicle** (same family as glider). + * Deploy → parent seat + RideIK · E get off → board removed · land loco. + * @see docs/WINDSURF_RIDE_SSOT.md + */ + backSlot: 'windsurf', + /** + * Ambient ocean wind indications — soft silk gusts + motes from wind-attack family. + * Nearly invisible (opacity ~0.07); disable for pure combat pad shots. + */ + oceanWindIndicators: true, + /** Prevailing wind heading (rad) for ocean gusts / sail cloth bias */ + oceanWindYaw: 0.75, + /** Ambient gust advection speed (m/s) — soft, not storm */ + oceanWindSpeed: 4.0, + /** Soft mote emission rate over water (particles/s) */ + oceanWindMoteRate: 8, + + /* --- deck / IK --- */ + hover: 0.06, + standOffset: 0.02, + /** Hip drop for bent knees on deck (absolute vs bind Y) */ + hipDrop: 0.14, + debugSockets: false, + seatSink: 0.0, + bob: 0.04, + bobRate: 2.1, + rideShake: 0.05, + lean: 22, + leanRate: 2.0, + leanDamping: 0.004, + turnDamping: 0.0001, /* --- the air ball itself (see materials/AirScooterMaterial.js) --- */ radius: 0.46, @@ -157,20 +558,200 @@ export const settings = { }, /* ------------------------------------------------------------------ */ - /* Camera rig */ + /* Combat aim + soft lock (MouseAim + CombatFocus) */ + /* ------------------------------------------------------------------ */ + aim: { + enabled: true, + turnSpeed: 14, + /** + * Focus mode: max body yaw rate toward camera (rad/s). + * Keep low — high values make mouse look whip the body (felt “turn too easily”). + */ + focusTurnSpeed: 6.5, + /** + * Deadzone (deg): body does not turn until camera yaw differs by this much. + * Stops micro look from spinning the character. + */ + focusTurnDeadzoneDeg: 16, + /** + * When true, body only yaws with camera while WASD moving (look freer when idle). + * Default false: always lag-follow with deadzone (predictable TPS). + */ + focusTurnOnlyWhenMoving: false, + /** Free aim: A/D tank turn rate (rad/s) */ + tankTurnSpeed: 2.6, + sprintTurnSpeed: 18, + moveRelativeToAim: true, + cameraFollowAim: true, + cameraYawLag: 0.12, + /** + * World ground ring under aim point. + * false = never. true = only when placement/AoE/path skill needs ground aim + * (not for basic focus look or pure projectile lock). + */ + groundMarker: true, + /** + * Also show ground ring while drawing an LMB path (staff place/wall/aoe). + */ + groundMarkerOnPathDraw: true, + /** Screen-center HUD crosshair (focus mode) — separate from ground ring */ + crosshair: true, + faceTravelWhenMoving: false, + /** + * Soft lock ON while focus is enabled (auto-acquire nearest if none). + * Magnetic aim blend toward selected target — not hard camera snap. + */ + softLockOnFocus: true, + /** Soft lock blend toward selected target (focus aim cone) — auto-aim help */ + softLockBlend: 0.68, + softLockRange: 32, + /** Tab cycles soft-lock targets (Shift+Tab previous) */ + tabCycleTargets: true, + /** + * Focus aim — snow-brawl style (camera ray → hit → launch dir). + * @see discourse snow-brawl · docs/COMBAT_CAMERA_FOCUS_SSOT.md + */ + /** Max soft-lock magnetic angle from crosshair (deg) — generous for action assist */ + softLockMaxAngleDeg: 26, + /** Pistol / flintlock soft-lock (stronger assist — loud not accurate) */ + pistolSoftLockBlend: 0.82, + pistolSoftLockMaxAngleDeg: 34, + pistolOptimalRangeMin: 3, + pistolOptimalRangeMax: 18, + /** Camera ray length (m) when no ground/mesh hit */ + aimRayFar: 80, + /** Projectile aim height on ground hits (m, SI chest) */ + projectileAimHeight: 1.15, + /** Spawn height above feet for launch origin */ + spawnHeight: 1.35, + /** Spawn nudge along body forward so mesh clears torso */ + spawnForwardM: 0.55, + /** Hand lateral offset (m) for L/R throw alternate */ + handOffsetM: 0.28, + /** Use full 3D launch vector for skills/projectiles in focus */ + use3dLaunch: true, + /** Crosshair range ring (m) vs soft-lock target — GRUDOX animator rangeState */ + optimalRangeMin: 2.5, + optimalRangeMax: 12 + }, + + /** + * Player control preferences (Editor → Controls). + * Default: Shift and RMB **toggle** on press (not hold). + */ + controls: { + /** + * true = press Shift toggles sprint on/off + * false = hold Shift to sprint + */ + sprintToggle: true, + /** + * true = short RMB click toggles focus (current) + * false = hold RMB to stay in focus (release = off) + */ + focusToggle: true, + /** Show on-screen hotkey chips under tight bar (off — clutter; help uses ?) */ + showHotkeyChips: false, + /** Invert look Y in focus/TPS */ + invertLookY: false, + /** Mouse look sensitivity scale (multiplies camera.orbitSensitivity) */ + lookSensitivity: 1.0 + }, + + /** + * Three-layer terrain (L0 height · L1 ground · L2 vegetation). + * Patterns: snakey-locomotion · three-stylized grounds/grass · forestoutline · Rapier heightfield. + * @see docs/THREE_LAYER_TERRAIN_SSOT.md + */ + terrain: { + enabled: true, + seed: 17, + /** Peak hill height on pad (m) — SI human yardstick */ + amp: 0.85, + segments: 96, + /** Rapier grid verts ≈ grid; cells = grid-1 */ + grid: 65, + shoreBand: null, // null → WORLD.shoreBand + flatCore: 8, + /** Island mesh vertex colors (NOT environment.floorColor void slab) */ + meadowColor: '#3f6b3a', + dirtColor: '#6f5435', + shoreColor: '#8a7355', + /** L2 harvest-node forest (Desktop forestoutline.html procedural instanced) */ + forestCount: 64, + forestEnabled: true, + forestClearRadius: 9, + /** L2 Stylized grass (three-stylized meadow on L0) */ + grassEnabled: true, + /** denser meadow for Dev Island playtest (was 28) */ + grassDensity: 52, + grassClearRadius: 5, + grassBladeMax: 0.55, + grassWind: 0.22, + grassColorBottom: '#3d6b1a', + grassColorTop: '#a8d44a' + }, + + /* ------------------------------------------------------------------ */ + /* Camera rig — combat angles from grudge-third-person-controller */ /* ------------------------------------------------------------------ */ camera: { - distance: 11.5, - minDistance: 3.5, // scroll-wheel zoom range - maxDistance: 30, - zoomSpeed: 1.0, - zoomDamping: 0.002, // fraction of the zoom gap left after 1s - minPolar: 0.35, - maxPolar: 1.32, - fov: 46, - targetHeight: 1.35, + /** + * Fortnite TPS from grudge-third-person-controller (CAMERA_MODES.md): + * distance 5.5 · height ~1.8 · shoulder 0.8 · FOV 85 combat / 70 free + * Soft-lock look: grudge-combat-targeting. Orbit sandbox unchanged. + * @see docs/COMBAT_CAMERA_FOCUS_SSOT.md + * @see MolochDaGod/grudge-third-person-controller + */ + /** + * Fortnite shoulder TPS (grudge-third-person-controller CAMERA_MODES): + * free ~6 m · focus 5.5 m · shoulder 0.72/0.8 · FOV 70/78 sprint. + * Builder uses orbit only in equip; combat always TPS. + */ + distance: 6.0, + focusDistance: 5.5, + minDistance: 2.5, + maxDistance: 12, + zoomSpeed: 0.55, + zoomDamping: 0.003, + minPolar: 0.22, + maxPolar: 1.4, + fov: 70, + sprintFov: 78, + actionFov: 70, + fovDamping: 0.14, + /** Chest look height (m) — ~human 1.8 m band */ + targetHeight: 1.55, damping: 0.06, - autoFrame: 0.35 // how strongly the rig drifts toward active abilities + autoFrame: 0.28, + tpsDamping: 0.12, + tpsDistanceScale: 1.0, + /** Free shoulder (m) */ + shoulderOffset: 0.72, + /** Focus over-the-shoulder (Fortnite) */ + focusShoulderOffset: 0.8, + tpsDefaultPitch: 0.4, + minPitch: 0.12, + maxPitch: 1.35, + /** + * Soft-lock camera look bias 0..1 when focus + target (not hard snap). + * Higher when focused — soft lock is ON in focus. + */ + /** Mild free soft-look; stronger in focus for GRUDOX framing (not hard snap) */ + /** Mild soft-look only — never auto yaw/pitch (shoulder rider owns orbit) */ + softLockLook: 0.18, + softLockLookFocus: 0.28, + /** Purged: auto camera yaw toward target (was action-angle fight) */ + softLockYawAssist: 0, + softLockYawConeDeg: 48, + /** Purged: auto pitch toward target chest */ + softLockPitchAssist: false, + softLockPitchDamp: 0.045, + orbitSensitivity: 0.0042, + /** Shoulder: -1 left · 0 center · +1 right */ + shoulderSide: 1, + /** Extra pitch bias when sprinting */ + sprintPitchBias: 0.04 }, /* ------------------------------------------------------------------ */ @@ -184,30 +765,32 @@ export const settings = { // The key rakes in from the left of the default camera; the rim sits almost // opposite it, behind the character, so it draws a cool edge against the // dark backdrop instead of lighting the floor. + // Lab-readable fill (was 0.12/0.34 — crushed meadow + hero to pure black on live) sunIntensity: 3.0, sunColor: '#fff2dd', sunAzimuth: 2.95, sunElevation: 0.6, - ambientIntensity: 0.12, - ambientColor: '#8ea8d8', - hemiIntensity: 0.34, - hemiSkyColor: '#bdd7ff', - hemiGroundColor: '#3a4552', - rimIntensity: 0.9, + ambientIntensity: 0.32, + ambientColor: '#9eb4d4', + hemiIntensity: 0.55, + hemiSkyColor: '#c5dcff', + hemiGroundColor: '#3d5240', + rimIntensity: 0.95, rimColor: '#9ec2ff', rimAzimuth: 5.45, // radians rimElevation: 0.35, - envIntensity: 0.3, - // Backdrop and fog share a colour, so the floor dissolves into the void - // instead of showing a hard horizon. - backgroundColor: '#14181d', - fogColor: '#14181d', - fogNear: 10, - fogFar: 38, + envIntensity: 0.45, + // Slightly lifted void so island green separates from sky (still cinematic) + backgroundColor: '#1a222c', + fogColor: '#1a222c', + fogNear: 18, + fogFar: 72, shadowBias: -0.0008, shadowRadius: 2.2, floorColor: '#1a1f26', floorTint: '#242a33', // second slab colour mixed into the base + /** Island shore sand/stone (Ground shore band → water) */ + shoreColor: '#3d4a3a', floorRoughness: 0.88, floorSheen: 0.3, // faint polished-stone reflectivity floorPool: 0.8, // how hard the floor darkens away from the stage centre @@ -220,21 +803,21 @@ export const settings = { /* ------------------------------------------------------------------ */ post: { enabled: true, - exposure: 1.05, + exposure: 1.15, // Against the dark stage the bloom threshold can sit much lower without the // floor blooming: only the VFX ever get near it. - bloomStrength: 0.7, - bloomRadius: 0.72, - bloomThreshold: 0.72, - vignette: 0.52, - chromaticAberration: 0.4, - contrast: 1.12, - saturation: 1.08, + bloomStrength: 0.55, + bloomRadius: 0.65, + bloomThreshold: 0.78, + vignette: 0.28, + chromaticAberration: 0.22, + contrast: 1.06, + saturation: 1.1, temperature: 0.02, // + warm / - cool - lift: -0.008, - gain: 1.0, - grain: 0.045, - flashStrength: 1.0 // multiplier for ability screen flashes + lift: 0.02, + gain: 1.05, + grain: 0.03, + flashStrength: 0.4 // softer screen flashes (was 1.0) }, /* ================================================================== */ @@ -344,8 +927,8 @@ export const settings = { // Explosion explosionSize: 3.0, explosionBrightness: 2.2, - explosionShake: 1.0, - explosionFlash: 0.55 + explosionShake: 0.28, + explosionFlash: 0.18 }, /* ================================================================== */ @@ -427,8 +1010,8 @@ export const settings = { rippleSpeed: 1.0, foamSpread: 5.0, // radius of the foam left on the ground foamLingering: 3.4, // how long that foam takes to drain away - explosionShake: 0.6, - explosionFlash: 0.12 + explosionShake: 0.18, + explosionFlash: 0.06 }, /* ================================================================== */ @@ -458,9 +1041,10 @@ export const settings = { riseSpeed: 4.95, sinkDelay: 0.48, tumble: 1.0, - colorRock: '#6b5744', - colorRockDark: '#3a2e24', - colorMoss: '#4f6b33', + // Nature-leaning palette (green moss / vine earth — product element "nature") + colorRock: '#5a6b44', + colorRockDark: '#2d3a24', + colorMoss: '#4ecf6a', // Ground damage crackWidth: 0.78, crackDepth: 0.85, @@ -486,9 +1070,9 @@ export const settings = { towerHold: 2.4, // seconds standing before it sinks back towerRocks: 16, towerRockRadius: 2.0, - shakeIntensity: 1.5, + shakeIntensity: 0.45, shakeDuration: 1.38, - explosionFlash: 0.15 + explosionFlash: 0.06 }, /* ================================================================== */ @@ -549,31 +1133,261 @@ export const settings = { tornadoRoughness: 0.17, tornadoLean: 0.55, burstIntensity: 1.5, - explosionShake: 0.42, - explosionFlash: 0.2 + explosionShake: 0.14, + explosionFlash: 0.08 + }, + + /* ================================================================== */ + /* HOLY — light staff (editor + VFX colors; path ability = wind pool) */ + /* ================================================================== */ + holy: { + speed: 13.0, + lifetime: 2.5, + ribbonCount: 3, + ribbonWidth: 2.0, + ribbonOpacity: 0.95, + ribbonLength: 22.0, + spiralRadius: 0.95, + sheetTwist: 1.2, + rotationSpeed: 4.5, + vortexStrength: 1.2, + swirlSpeed: 1.8, + filamentCount: 28, + filamentSharpness: 0.6, + turbulence: 0.55, + haze: 0.28, + noiseStrength: 0.55, + noiseFrequency: 0.85, + distortion: 0.06, + fresnel: 1.5, + opacity: 0.7, + glow: 1.35, + colorInner: '#fff8e8', + colorOuter: '#ffe08a', + leafCount: 24, + leafSize: 0.05, + leafSpin: 3.5, + leafLifetime: 2.2, + dustAmount: 1.4, + dustSize: 0.05, + dustRate: 120, + lightIntensity: 14, + lightRadius: 10, + lightColor: '#ffe9a8', + tornadoHeight: 7.5, + tornadoRadius: 1.8, + tornadoDuration: 1.4, + tornadoNeck: 0.22, + tornadoShells: 3, + tornadoRoughness: 0.12, + tornadoLean: 0.4, + burstIntensity: 1.6, + explosionShake: 0.12, + explosionFlash: 0.12 + }, + + /* ================================================================== */ + /* ARCANE — void / warp staff (editor; path ability = wind pool) */ + /* ================================================================== */ + arcane: { + speed: 14.5, + lifetime: 2.3, + ribbonCount: 4, + ribbonWidth: 1.9, + ribbonOpacity: 0.92, + ribbonLength: 23.0, + spiralRadius: 1.1, + sheetTwist: 1.7, + rotationSpeed: 6.2, + vortexStrength: 1.85, + swirlSpeed: 2.6, + filamentCount: 32, + filamentSharpness: 0.58, + turbulence: 0.95, + haze: 0.3, + noiseStrength: 0.8, + noiseFrequency: 1.05, + distortion: 0.1, + fresnel: 1.4, + opacity: 0.65, + glow: 1.2, + colorInner: '#f0e0ff', + colorOuter: '#9b5cff', + leafCount: 30, + leafSize: 0.05, + leafSpin: 5.0, + leafLifetime: 2.3, + dustAmount: 1.8, + dustSize: 0.05, + dustRate: 160, + lightIntensity: 12, + lightRadius: 9, + lightColor: '#c090ff', + tornadoHeight: 8.0, + tornadoRadius: 2.0, + tornadoDuration: 1.5, + tornadoNeck: 0.18, + tornadoShells: 3, + tornadoRoughness: 0.2, + tornadoLean: 0.5, + burstIntensity: 1.7, + explosionShake: 0.14, + explosionFlash: 0.088 } }; -/** Element ids in selection order (keys 1-4). */ -export const ELEMENTS = ['fire', 'water', 'earth', 'wind']; +/** + * Product **staff weapon** ids in hotbar order (keys 1–6). + * Product language = weapons (not free “elements”). + * Path-cast abilities still pool as fire|water|earth|wind — see ELEMENT_ABILITY. + * HUD / staff labels: ELEMENT_META ≡ WEAPON_STAFF_META. + * Full T0 catalog weapons: docs/T0_WEAPON_PLAY_STACK_SSOT.md + */ +export const ELEMENTS = ['fire', 'storm', 'ice', 'nature', 'holy', 'arcane']; + +/** Alias — prefer this name in new code (same array as ELEMENTS). */ +export const WEAPON_STAFF_IDS = ELEMENTS; + +/** + * Staff weapon id → AbilityManager path pool (Fire/Water/Earth/Wind ability classes). + * Holy + arcane reuse the wind path mesh until dedicated abilities ship. + */ +export const ELEMENT_ABILITY = Object.freeze({ + fire: 'fire', + storm: 'wind', + ice: 'water', + nature: 'earth', + holy: 'wind', + arcane: 'wind' +}); + +/** @param {string} element product staff weapon or legacy id */ +export function abilityKeyForElement(element) { + if (!element) return 'fire'; + if (ELEMENT_ABILITY[element]) return ELEMENT_ABILITY[element]; + // Legacy ids still used by older skills / catalogs + if (element === 'water' || element === 'frost') return 'water'; + if (element === 'earth') return 'earth'; + if (element === 'wind' || element === 'lightning') return 'wind'; + if (element === 'fire') return 'fire'; + return ELEMENT_ABILITY[element] || 'wind'; +} + +/** @param {string} weaponId staff weapon id (fire…arcane) or legacy element */ +export function abilityKeyForWeapon(weaponId) { + return abilityKeyForElement(weaponId); +} + +/** + * Live settings block for a staff weapon (editor + ability config). + * ice→water, storm→wind, nature→earth; holy/arcane own blocks. + */ +export function settingsForElement(element) { + if (element === 'ice' || element === 'water' || element === 'frost') return settings.water; + if (element === 'storm' || element === 'wind' || element === 'lightning') return settings.wind; + if (element === 'nature' || element === 'earth') return settings.earth; + if (element === 'holy') return settings.holy; + if (element === 'arcane') return settings.arcane; + if (element === 'fire') return settings.fire; + return settings.fire; +} + +/** @param {string} weaponId staff weapon id */ +export function settingsForWeapon(weaponId) { + return settingsForElement(weaponId); +} /** Interaction modes, in toggle order (key M). */ export const MODES = ['casting', 'walk']; /** Presentation metadata for the HUD's mode switch. */ export const MODE_META = { - casting: { label: 'Cast', glyph: '✦', hint: 'Casting mode', blurb: 'Draw a path. Release to cast.' }, - walk: { label: 'Walk', glyph: '◎', hint: 'Walk mode', blurb: 'Draw a path. Release to ride it.' } + casting: { + label: 'Cast', + glyph: 'C', + hint: 'Staff weapon path cast', + blurb: 'Draw path to staff weapon · 1-6 staffs · M walk' + }, + walk: { + label: 'Surf', + glyph: 'S', + hint: 'Windsurf freeride', + blurb: 'Space deploy · path = course · WASD boat · 1-6 weapon skills on board' + } }; -/** Presentation metadata for the HUD. */ +/** + * Staff weapon presentation (Warlords brands) — product = weapons, not free elements. + * Product ids: fire · storm · ice · nature · holy · arcane. + * Same object as WEAPON_STAFF_META. + */ export const ELEMENT_META = { - fire: { label: 'Fire', accent: '#ff6a1a', glyph: '🜂', hint: 'Firebending' }, - water: { label: 'Water', accent: '#31b6ff', glyph: '🜄', hint: 'Waterbending' }, - earth: { label: 'Earth', accent: '#b98a4d', glyph: '🜃', hint: 'Earthbending' }, - wind: { label: 'Air', accent: '#c9f0ff', glyph: '🜁', hint: 'Airbending' } + fire: { + label: 'Fire Staff', + short: 'Fire', + accent: '#ff6a1a', + /** Latin short code for text UI; SVG sigils live in glyphs.js */ + glyph: 'F', + hint: '1 - Fire staff weapon', + staffWeaponId: 'staffFire', + staffLabel: 'Fire Staff', + kind: 'weapon' + }, + storm: { + label: 'Storm Staff', + short: 'Storm', + accent: '#9fdcff', + glyph: 'S', + hint: '2 - Storm staff weapon', + staffWeaponId: 'staffStorm', + staffLabel: 'Storm Staff', + kind: 'weapon' + }, + ice: { + label: 'Ice Staff', + short: 'Ice', + accent: '#31b6ff', + glyph: 'I', + hint: '3 - Ice staff weapon', + staffWeaponId: 'staffIce', + staffLabel: 'Ice Staff', + kind: 'weapon' + }, + nature: { + label: 'Nature Staff', + short: 'Nature', + accent: '#6bbf4a', + glyph: 'N', + hint: '4 - Nature staff weapon', + staffWeaponId: 'staffNature', + staffLabel: 'Nature Staff', + kind: 'weapon' + }, + holy: { + label: 'Holy Staff', + short: 'Holy', + accent: '#ffe08a', + glyph: 'H', + hint: '5 - Holy staff weapon', + staffWeaponId: 'staffHoly', + staffLabel: 'Holy Staff', + kind: 'weapon' + }, + arcane: { + label: 'Arcane Staff', + short: 'Arcane', + accent: '#b070ff', + glyph: 'A', + hint: '6 - Arcane staff weapon', + staffWeaponId: 'staffArcane', + staffLabel: 'Arcane Staff', + kind: 'weapon' + } }; +/** Alias — prefer in new HUD / agent code */ +export const WEAPON_STAFF_META = ELEMENT_META; + /** Immutable snapshot used by "Reset to defaults" and the preset system. */ export const DEFAULT_SETTINGS = structuredClone(settings); diff --git a/src/config/skillAuthoringSSOT.js b/src/config/skillAuthoringSSOT.js new file mode 100644 index 0000000..499847a --- /dev/null +++ b/src/config/skillAuthoringSSOT.js @@ -0,0 +1,353 @@ +/** + * Skill authoring organization SSOT — Casting lab → client production. + * + * Inspired by **uMMORPG / Unity ScriptableObject skill packs**: + * SkillTemplate (data) → SkillEffect modules → Target / Delivery → VFX bind + * Runtime only *activates* assets; it does not invent skill rows. + * + * Mapped onto **existing** Casting systems (no second combat engine): + * + * | uMMORPG idea | Casting SSOT | + * |---------------------------|---------------------------------------------------| + * | Scriptable Skill asset | master-weaponSkills / t0 / `weaponSkillProduction` | + * | Skill.cooldown / mana | skill.cooldownSec · economy fields | + * | Target type | soft-lock · aim · ground ring · self | + * | Projectile / AOE / Buff | `SkillDeliveryPattern` in skillDelivery.js | + * | Scriptable effect modules | `EffectPrimitive[]` in effectPrefab.js | + * | Prefab visual | VfxDirector effectId + meshId + settings knobs | + * | Engine scripting | settings.js live knobs · prefab JSON export | + * + * Authoring pipeline (one direction): + * + * ``` + * Catalog skill row + * → Delivery pattern (weapon · linear · over · under · aura · path…) + * → Effect primitives (trail · travel · cast · impact · residual · decal · aura) + * → Mesh / texture samples (orbs · slash · rocks) + * → Live knobs (settings.effect / residual / element) + * → Export EffectPrefab JSON → Warlords / Open + * ``` + * + * @see docs/SKILL_AUTHORING_STUDIO_SSOT.md + * @see src/combat/skillDelivery.js + * @see src/vfx/effectPrefab.js + */ + +import { DELIVERY_META } from '../combat/skillDelivery.js'; +import { EFFECT_KINDS, EFFECT_MESH_IDS, MESH_PATH_HINT } from '../vfx/effectPrefab.js'; +import { VFX_CATALOG } from '../vfx/vfxCatalog.js'; + +/** Studio shell tabs — singular container, no parallel editors */ +export const STUDIO_TABS = Object.freeze([ + { + id: 'pipeline', + label: 'Pipeline', + icon: '⧉', + hint: 'How skill data flows from catalog → delivery → VFX → export' + }, + { + id: 'skill', + label: 'Skill', + icon: '⚔', + hint: 'Scriptable skill fields (uMMORPG SkillTemplate equivalent)' + }, + { + id: 'delivery', + label: 'Delivery', + icon: '◎', + hint: 'Weapon · linear · over · under · around · aura · path' + }, + { + id: 'vfx', + label: 'VFX', + icon: '✦', + hint: 'Primitives + catalog effectIds (cast / travel / impact / residual / aura)' + }, + { + id: 'linear', + label: 'Linear', + icon: '→', + hint: 'Linear skillshots (ice / thunder / meteor / beam / snare)' + }, + { + id: 'samples', + label: 'Samples', + icon: '▣', + hint: 'Mesh + color texture samples for authoring' + }, + { + id: 'knobs', + label: 'Knobs', + icon: '⚙', + hint: 'Live settings (lil-gui) — intensity · AOE · residual · elements · post' + }, + { + id: 'export', + label: 'Export', + icon: '↑', + hint: 'EffectPrefab JSON for client / Warlords production' + } +]); + +/** + * Delivery groups for studio UI (maps skillDelivery patterns). + * User-facing names match combat language: linear, over, under, aura… + */ +export const DELIVERY_GROUPS = Object.freeze([ + { + id: 'weapon', + label: 'Weapon', + patterns: ['weapon'], + blurb: 'Melee residual from tip (Getsuga-class). F attack frame.' + }, + { + id: 'linear', + label: 'Linear', + patterns: ['caster_to_target'], + blurb: 'Caster → target bolt / beam / skillshot (default spell).' + }, + { + id: 'over', + label: 'Over', + patterns: ['over_target'], + blurb: 'Spawn above target and drop (meteor, smite, rain).' + }, + { + id: 'under', + label: 'Under', + patterns: ['under_target'], + blurb: 'Erupt under target (spikes, roots, ice bloom).' + }, + { + id: 'around', + label: 'Around', + patterns: ['around_caster', 'around_target'], + blurb: 'Ring / nova on caster or target.' + }, + { + id: 'aura', + label: 'Aura', + patterns: ['toggle_aura'], + blurb: 'Toggle ward / shield / stance — no travel projectile.' + }, + { + id: 'place', + label: 'Place', + patterns: ['at_location'], + blurb: 'Ground aim placement (zone, beacon, field).' + }, + { + id: 'path', + label: 'Path', + patterns: ['path_stream', 'path_aoe', 'path_spikes', 'path_wall'], + blurb: 'Drawn staff path: stream · AoE · spikes · wall.' + } +]); + +/** Effect primitive cards for VFX tab */ +export const PRIMITIVE_META = Object.freeze({ + trail: { + label: 'Trail', + color: '#7dd3fc', + blurb: 'Ribbon / path trail · blade tip · stroke' + }, + travel: { + label: 'Travel', + color: '#ff8a4a', + blurb: 'Projectile mesh along path · orbs' + }, + cast: { + label: 'Cast', + color: '#c4a0ff', + blurb: 'Hand / channel tell before travel' + }, + impact: { + label: 'Impact', + color: '#ff6a3d', + blurb: 'Hit burst · explosion · shockwave' + }, + residual: { + label: 'Residual', + color: '#5eead4', + blurb: 'Melee wave past blade (F apex)' + }, + decal: { + label: 'Decal', + color: '#a3a3a3', + blurb: 'Ground scorch / frost mark' + }, + aura: { + label: 'Aura', + color: '#fbbf24', + blurb: 'Ring / buff around body' + } +}); + +/** + * Texture / mesh sample cards for Samples tab. + * Paths under public/ or CDN — CSS gradient fallback when no still image. + */ +export const SAMPLE_LIBRARY = Object.freeze([ + { + id: 'orb-fire', + label: 'Fire orb', + kind: 'mesh', + path: './models/vfx/orbs/orb-fire.glb', + swatch: ['#ff6a1e', '#ffb020', '#3a0a00'], + tags: ['travel', 'fire'] + }, + { + id: 'orb-ice', + label: 'Ice orb', + kind: 'mesh', + path: './models/vfx/orbs/orb-ice.glb', + swatch: ['#9fdcff', '#e8f6ff', '#1a3a50'], + tags: ['travel', 'ice'] + }, + { + id: 'orb-storm', + label: 'Storm orb', + kind: 'mesh', + path: './models/vfx/orbs/orb-storm.glb', + swatch: ['#a0d8ff', '#7aa8ff', '#102040'], + tags: ['travel', 'storm'] + }, + { + id: 'orb-holy', + label: 'Holy orb', + kind: 'mesh', + path: './models/vfx/orbs/orb-holy.glb', + swatch: ['#fff6c0', '#ffe080', '#403010'], + tags: ['travel', 'holy'] + }, + { + id: 'orb-arcane', + label: 'Arcane orb', + kind: 'mesh', + path: './models/vfx/orbs/orb-arcane.glb', + swatch: ['#b070ff', '#e0c0ff', '#201040'], + tags: ['travel', 'arcane'] + }, + { + id: 'orb-nature', + label: 'Nature orb', + kind: 'mesh', + path: './models/vfx/orbs/orb-nature.glb', + swatch: ['#80e060', '#c0f0a0', '#103010'], + tags: ['travel', 'nature'] + }, + { + id: 'staff-charge', + label: 'Staff charge', + kind: 'mesh', + path: './models/vfx/charge/staff-charge.glb', + swatch: ['#60d0ff', '#ffffff', '#082030'], + tags: ['cast', 'channel'] + }, + { + id: 'slashblue', + label: 'Slash blue', + kind: 'mesh', + path: 'slashblue', + swatch: ['#7dd3fc', '#bae6fd', '#0c4a6e'], + tags: ['residual', 'melee'] + }, + { + id: 'slashred', + label: 'Slash red', + kind: 'mesh', + path: 'slashred', + swatch: ['#ff6a3d', '#ffb4a0', '#4a1010'], + tags: ['residual', 'melee'] + }, + { + id: 'rock-0', + label: 'Rock 0', + kind: 'mesh', + path: './models/vfx/rocks/rock-0.glb', + swatch: ['#8a7355', '#c4a574', '#2a2010'], + tags: ['travel', 'earth'] + }, + { + id: 'arrow-path', + label: 'Arrow path', + kind: 'mesh', + path: './models/vfx/arrows/arrow-path.glb', + swatch: ['#d4c4a0', '#8a7050', '#201808'], + tags: ['travel', 'ranged'] + }, + { + id: 'summon-fire-fist', + label: 'Fire fist', + kind: 'mesh', + path: './models/vfx/summons/summon-fire-fist.glb', + swatch: ['#ff5510', '#ffaa40', '#301000'], + tags: ['impact', 'summon'] + }, + { + id: 'summon-ice-shard', + label: 'Ice shard', + kind: 'mesh', + path: './models/vfx/summons/summon-ice-shard.glb', + swatch: ['#c8f0ff', '#60a0c0', '#102030'], + tags: ['impact', 'summon'] + }, + { + id: 'bullet1', + label: 'Bullet', + kind: 'mesh', + path: './models/vfx/projectiles/bullet1.glb', + swatch: ['#c0c8d0', '#606870', '#101418'], + tags: ['travel', 'gun'] + } +]); + +/** Linear skillshot families (elemental linear cast) */ +export const LINEAR_FAMILIES = Object.freeze([ + { id: 'ice', label: 'Ice bolt', element: 'ice', delivery: 'caster_to_target' }, + { id: 'thunder', label: 'Thunder', element: 'storm', delivery: 'caster_to_target' }, + { id: 'meteor', label: 'Meteor', element: 'fire', delivery: 'over_target' }, + { id: 'beam', label: 'Beam', element: 'arcane', delivery: 'caster_to_target' }, + { id: 'snare', label: 'Snare', element: 'nature', delivery: 'at_location' }, + { id: 'glacier', label: 'Glacier', element: 'ice', delivery: 'under_target' } +]); + +/** Scriptable skill field template (uMMORPG SkillTemplate-shaped) */ +export const SKILL_TEMPLATE_FIELDS = Object.freeze([ + { key: 'id', label: 'Skill id', type: 'string', required: true }, + { key: 'label', label: 'Display name', type: 'string' }, + { key: 'style', label: 'Style', type: 'enum', options: ['melee', 'spell', 'ranged'] }, + { key: 'element', label: 'Element', type: 'string' }, + { key: 'cooldownSec', label: 'Cooldown (s)', type: 'number' }, + { key: 'castTimeSec', label: 'Cast time (s)', type: 'number' }, + { key: 'manaCost', label: 'Mana', type: 'number' }, + { key: 'staminaCost', label: 'Stamina', type: 'number' }, + { key: 'rangeM', label: 'Range (m)', type: 'number' }, + { key: 'damage', label: 'Damage', type: 'number' }, + { key: 'delivery', label: 'Delivery pattern', type: 'delivery' }, + { key: 'castEffectId', label: 'Cast VFX id', type: 'vfxId' }, + { key: 'travelEffectId', label: 'Travel VFX id', type: 'vfxId' }, + { key: 'impactEffectId', label: 'Impact VFX id', type: 'vfxId' } +]); + +export function deliveryMetaList() { + return Object.entries(DELIVERY_META).map(([id, m]) => ({ id, ...m })); +} + +export function catalogByCategory() { + /** @type {Record} */ + const map = {}; + for (const e of VFX_CATALOG) { + if (!map[e.category]) map[e.category] = []; + map[e.category].push(e); + } + return map; +} + +export { + DELIVERY_META, + EFFECT_KINDS, + EFFECT_MESH_IDS, + MESH_PATH_HINT, + VFX_CATALOG +}; diff --git a/src/config/weaponAnimPack.js b/src/config/weaponAnimPack.js new file mode 100644 index 0000000..b5d1dba --- /dev/null +++ b/src/config/weaponAnimPack.js @@ -0,0 +1,133 @@ +/** + * Equipped weapon → Bip001 anim pack + combat role map. + * SSOT for lab: mesh_ids weapon slot drives locomotion + attack/cast clips. + * + * Packs live in assets.js ANIM_PACKS. + * Shared mobility (dodge/roll/slide) lives in combat_mobility — always bound. + * Role language / families: config/animLibrary.js · docs/ANIM_LIBRARY_SSOT.md + */ + +/** @typedef {'magic'|'sword_shield'|'longbow'|'pistol'} WeaponAnimPackId */ + +/** + * Weapon inventory slot → anim pack. + * @type {Record} + */ +export const WEAPON_SLOT_TO_PACK = Object.freeze({ + staff: 'magic', + tome: 'magic', + sword: 'sword_shield', + axe: 'sword_shield', + hammer: 'sword_shield', + spear: 'sword_shield', + shield: 'sword_shield', + bow: 'longbow', + longbow: 'longbow', + /** Handgun / T0 pistol — Open baked pistol/* (gunplay spin, draw, whip) */ + pistol: 'pistol', + gun: 'pistol', + handgun: 'pistol' +}); + +/** Shared mobility roles (combat_mobility pack — not weapon-specific). */ +export const MOBILITY_ROLES = Object.freeze([ + 'dodgeL', + 'dodgeR', + 'dodgeF', + 'dodgeB', + 'rollL', + 'rollR', + 'rollF', + 'rollB', + 'slide', + 'parry', + 'block' +]); + +/** + * What one-shot roles each pack owns for combat skills. + * Mobility is always layered from combat_mobility (see MOBILITY_ROLES). + * @type {Record} + */ +export const PACK_COMBAT_ROLES = Object.freeze({ + magic: { + attack: 'cast', + cast: 'cast', + block: 'block', + loco: ['idle', 'walk', 'run', 'jump'], + mobility: [...MOBILITY_ROLES] + }, + sword_shield: { + /** Light path uses combo roles; finisher uses attack / finisherAir */ + attack: 'attack1', + cast: 'attack1', + block: 'block', + combo: ['attack1', 'attack2', 'attack3'], + finisher: 'finisher', + finisherAir: 'finisherAir', + loco: ['idle', 'walk', 'run', 'jump'], + mobility: [...MOBILITY_ROLES] + }, + longbow: { + attack: 'attack', + cast: 'attack', + block: 'block', + loco: ['idle', 'walk', 'run', 'jump'], + mobility: [...MOBILITY_ROLES] + }, + pistol: { + /** gunplay = spin/flourish fire; cast = drawing-gun */ + attack: 'attack', + cast: 'draw', + block: 'block', + gunplay: 'gunplay', + spin: 'spin', + skill: ['skill1', 'skill2', 'skill3', 'skill4', 'skill5'], + loco: ['idle', 'walk', 'run', 'jump', 'walkL', 'walkR'], + mobility: [...MOBILITY_ROLES] + } +}); + +/** + * Resolve active weapon slot from loadout (exclusive). + * @param {Record} loadout + * @returns {string|null} + */ +export function activeWeaponSlot(loadout = {}) { + const order = ['staff', 'pistol', 'gun', 'bow', 'sword', 'axe', 'hammer', 'spear']; + for (const s of order) { + if (loadout[s] && loadout[s] !== 'none') return s; + } + return null; +} + +/** + * @param {Record} loadout + * @param {string} [presetPack] fallback from class preset + * @returns {WeaponAnimPackId} + */ +export function animPackForLoadout(loadout = {}, presetPack) { + const slot = activeWeaponSlot(loadout); + if (slot && WEAPON_SLOT_TO_PACK[slot]) return WEAPON_SLOT_TO_PACK[slot]; + if ( + presetPack === 'magic' || + presetPack === 'sword_shield' || + presetPack === 'longbow' || + presetPack === 'pistol' + ) { + return presetPack; + } + if (presetPack?.includes?.('pistol') || presetPack?.includes?.('gun')) return 'pistol'; + if (presetPack?.includes?.('bow')) return 'longbow'; + if (presetPack?.includes?.('sword') || presetPack?.includes?.('shield')) return 'sword_shield'; + return 'magic'; +} + +/** + * Human label for UI. + * @param {WeaponAnimPackId} packId + */ +export function packCombatBlurb(packId) { + const r = PACK_COMBAT_ROLES[packId] || PACK_COMBAT_ROLES.magic; + return `${packId}: ${r.attack}/${r.cast} · loco ${r.loco.join('·')} · mobility dodge/roll/slide`; +} diff --git a/src/config/worldScale.js b/src/config/worldScale.js new file mode 100644 index 0000000..e4b8c28 --- /dev/null +++ b/src/config/worldScale.js @@ -0,0 +1,109 @@ +/** + * SI world scale for Casting Abilities stage. + * + * Yardstick: human hero ~1.8–2.0 m (Toon fit). Play extents grow with that + * so locomotion, path cast, and ride paths read as a real yard, not a closet. + * + * MAP_SCALE 1.5 = ~50% larger than the original fog/pool stage (~38–40 m). + */ + +/** Average adult / grudge6 fit height */ +export const HUMAN_HEIGHT_M = 1.8; + +/** Target tall-hero yardstick (orc upper band) */ +export const HERO_HEIGHT_M = 2.0; + +/** + * Linear map scale vs the original sandbox (fogFar≈38, pool edge≈40). + * 1.5 → playfield and fog pushed out ~50%. + */ +export const MAP_SCALE = 1.5; + +/** Original design numbers (pre-scale) */ +const ORIG = Object.freeze({ + fogNear: 10, + fogFar: 38, + floorPoolInner: 5, + floorPoolOuter: 40, + groundSize: 400, + physicsHalf: 80, + shadowExtent: 26, + cameraDistance: 11.5, + cameraMaxDistance: 30 +}); + +export const WORLD = Object.freeze({ + mapScale: MAP_SCALE, + humanHeightM: HUMAN_HEIGHT_M, + heroHeightM: HERO_HEIGHT_M, + + /** Fog (metres) — slightly further than 1.0× scale so meadow/hero stay readable */ + fogNear: ORIG.fogNear * MAP_SCALE * 1.2, + fogFar: ORIG.fogFar * MAP_SCALE * 1.45, + + /** Floor radial pool in Ground shader (metres) */ + floorPoolInner: ORIG.floorPoolInner * MAP_SCALE, + floorPoolOuter: ORIG.floorPoolOuter * MAP_SCALE, + + /** Ground plane edge length (metres) — visible slab */ + groundSize: Math.max(ORIG.groundSize * 0.45, ORIG.floorPoolOuter * MAP_SCALE * 3), + + /** Water plane under/around stage (metres) — open-sea freeride ring */ + waterSize: ORIG.floorPoolOuter * MAP_SCALE * 7.5, + /** + * Sea surface Y (metres). SI: water line = 0. + * Land hills rise above 0; bathymetry drops below into the sea. + */ + waterY: 0, + + /** + * Island shelf / weld Y (metres) — shore bathymetry ends here; + * horizon islands plant bottoms at this shelf. Water surface stays at 0. + */ + seafloorY: -5, + + /** + * Deep ocean floor Y (metres) — open sea beyond the island shelf. + * Terrain heightfield slopes from seafloorY (−5) down to this (−50). + */ + oceanFloorY: -50, + + /** + * Stage island pad (metres): + * - Land = IslandHeightfield (mesh + Rapier + sample) — continuous into water + * - islandRadius = shelf weld ring (terrain ≈ seafloorY) + * - shoreBand = land→underwater slope (slow then sharper to −5 m) + * - oceanDepthBand = radial distance past pad to reach oceanFloorY + * - Aim/path use terrainGround.projectToTerrain (same sample) + * @see docs/TERRAIN_PHYSICS_SSOT.md · docs/ISLAND_WATER_SEAFLOOR_SSOT.md + */ + islandRadius: ORIG.floorPoolOuter * MAP_SCALE * 0.85, + /** Width of shore drop ring inside islandRadius (metres) */ + shoreBand: 6.5 * MAP_SCALE, + /** + * Distance past islandRadius (m) over which floor deepens −5 → −50. + */ + oceanDepthBand: ORIG.floorPoolOuter * MAP_SCALE * 1.35, + /** Beach tint mix strength 0..1 at shore */ + shoreTint: 0.55, + /** Sand beach color (hex string for settings / materials) */ + sandColor: '#c2a86a', + seafloorColor: '#8a7350', + + /** Rapier ground cuboid half-extents XZ */ + physicsGroundHalf: ORIG.physicsHalf * MAP_SCALE, + + /** Directional shadow ortho half-extent */ + shadowExtent: ORIG.shadowExtent * MAP_SCALE, + + /** Default camera distances (TPS/orbit) */ + cameraDistance: ORIG.cameraDistance * Math.sqrt(MAP_SCALE), + cameraMaxDistance: ORIG.cameraMaxDistance * MAP_SCALE, + cameraMinDistance: 3.5 +}); + +/** Human capsule tuned for ~1.8–2.0 m heroes */ +export const PLAYER_CAPSULE = Object.freeze({ + radius: 0.35, + halfHeight: 0.55 // total ≈ 0.35*2 + 1.1 ≈ 1.8 m +}); diff --git a/src/core/App.js b/src/core/App.js index 17809ec..e110d1a 100644 --- a/src/core/App.js +++ b/src/core/App.js @@ -7,8 +7,17 @@ import { frame } from './FrameUniforms.js'; import { Environment } from '../world/Environment.js'; import { Ground } from '../world/Ground.js'; +import { StageWater } from '../world/StageWater.js'; +import { Seafloor } from '../world/Seafloor.js'; +import { OceanWindIndicators } from '../effects/OceanWindIndicators.js'; +import { IslandHeightfield } from '../world/IslandHeightfield.js'; +import { IslandTown } from '../world/IslandTown.js'; +import { terrainHandle } from '../world/terrainGround.js'; +import { mountTerrainLayers, TERRAIN_LAYER } from '../world/terrainLayers.js'; +import { OpenSeaShells } from '../world/OpenSeaShells.js'; import { DustMotes } from '../world/DustMotes.js'; import { ContactShadows } from '../world/ContactShadows.js'; +import { WORLD } from '../config/worldScale.js'; import { AssetLoader } from '../loaders/AssetLoader.js'; import { CharacterController } from '../animation/CharacterController.js'; @@ -16,6 +25,8 @@ import { WalkController } from '../animation/WalkController.js'; import { InputManager } from '../input/InputManager.js'; import { PathDrawer } from '../input/PathDrawer.js'; +import { MouseAim } from '../input/MouseAim.js'; +import { Mesh, MeshBasicMaterial, RingGeometry, DoubleSide } from 'three'; import { ParticleEngine } from '../particles/ParticleEngine.js'; import { LightPool } from '../effects/LightPool.js'; @@ -25,21 +36,107 @@ import { CameraShake } from '../effects/CameraShake.js'; import { ScreenFlash } from '../effects/ScreenFlash.js'; import { AbilityManager } from '../abilities/AbilityManager.js'; +import { installSkillSfxGestureUnlock, SkillSfx } from '../audio/skillSfx.js'; +import { + LinearSkillBridge, + LINEAR_HOTKEYS, + PRODUCT_TO_LINEAR +} from '../skillshot/LinearSkillBridge.js'; import { PostProcessing } from '../postprocessing/PostProcessing.js'; import { HUD, LoadingScreen } from '../ui/HUD.js'; +import { OverheadNameplates } from '../ui/OverheadNameplates.js'; import { Editor } from '../ui/Editor.js'; +import { VfxStudio } from '../ui/vfxStudio/VfxStudio.js'; +import { InventoryPanel } from '../ui/InventoryPanel.js'; +import { AdminHub } from '../ui/AdminHub.js'; +import { ShowcasePanel } from '../ui/ShowcasePanel.js'; +import { DrcCombatController } from '../combat/DrcCombatController.js'; +import { loadWeaponSkillsCatalog } from '../api/weaponSkillsCatalog.js'; +import { loadSkillBindings } from '../combat/skillBindings.js'; +import { loadPrefabCatalog, pickSamplePrefab, bagItemFromPresent } from '../loot/prefabAssets.js'; +import { WorldDrops } from '../world/WorldDrops.js'; +import { DevIslandHarvest } from '../world/DevIslandHarvest.js'; +import { HARVEST_RANGE_M } from '../world/devIslandCatalog.js'; +import { + TRAINING_ROOM_BUILDERS, + TRAINING_ROOM_LABEL, + TRAINING_ROOM_MAP_ID +} from '../world/trainingRoomMap.js'; +import { loadTrainingRoomLayoutForPlay } from '../world/trainingRoomDeploy.js'; +import { fleetDeploySnapshot } from '../config/fleetEnv.js'; +import { DropBag } from '../ui/DropBag.js'; +import '../ui/dropBag.css'; +import { + applyWarlordsUiCssVars, + preloadWarlordsUi +} from '../ui/warlordsUiSkin.js'; +import { + configureWarlordsCursors, + setCursorIntent, + preloadWarlordsCursors, + intentFromInteractKind +} from '../ui/warlordsCursors.js'; +import '../ui/warlords-dev-ui.css'; +import { ModeRadial } from '../ui/ModeRadial.js'; +import { + RADIAL_HOLD_S, + HARVEST_TOOL_RADIAL, + MODE_LABEL, + DEFAULT_HARVEST_TOOL +} from '../combat/playerActivity.js'; +import { + createPlayerActivityActor, + activityFromSnap, + toolIdFromSnap, + DEFAULT_TOOL_ID +} from '../combat/playerActivityMachine.js'; +import { + getEquippedWeapon, + equipWeaponById, + unequipWeapon, + swapWeaponLoadout +} from '../combat/equippedWeaponRuntime.js'; +import { + setActiveSkillTree, + getActiveSkills +} from '../combat/drcSkills.js'; +import { T0_ALL_WEAPON_IDS } from '../api/t0WeaponCatalog.js'; +import { loadLabAdmin, labAdminStatusLine } from '../config/labAdmin.js'; +import { PhysicsWorld } from '../physics/PhysicsWorld.js'; +import { VfxDirector } from '../vfx/VfxDirector.js'; +import { loadGeneratedCatalog, spawnGeneratedProp } from '../assets/generatedCatalog.js'; +import { CASTING_LAB_CONTRACT } from '../sdk/castingLabSdk.js'; +import { fleetApi } from '../api/fleetApi.js'; import { settings, ELEMENTS, MODES, MODE_META } from '../config/settings.js'; +import { SessionState, INTERACTION_MODE, DRC_SESSION } from './SessionState.js'; +import { runGameplayGates, logGameplayGates } from './gameplayGates.js'; +import { FishingController } from '../fishing/FishingController.js'; +import { + resolvePlayerIdentity, + displayNameForKit +} from '../player/playerIdentity.js'; +import { raceDef } from '../config/grudge6SSOT.js'; +import { CombatFocus } from '../combat/CombatFocus.js'; +import { + skillNeedsGroundMarker, + inferDeliveryPattern, + deliveryNeedsGroundMarker +} from '../combat/skillDelivery.js'; +import { skillBySlot, skillForFKey } from '../combat/drcSkills.js'; const HDR_URL = './hdri/spruit_sunrise.hdr'; /** * Application root: owns every subsystem and the frame loop. * - * The wiring is deliberately one-directional — App builds the systems, hands - * each ability a context object of the shared services, and then does nothing - * but order the per-frame updates. No subsystem reaches back into App. + * Wiring is one-directional. **Session orchestration** lives in SessionState: + * mode / combat-equip / ride phase / element + derived gates. Controllers report + * phase changes; App applies camera/inventory/HUD once on session.change. + * Tweaks (numbers/colors) stay in settings.js. + * + * @see docs/SESSION_STATE_SSOT.md */ export class App { constructor(canvas) { @@ -49,6 +146,16 @@ export class App { this.paused = false; this._raf = 0; + /** Session SSOT — mode, drc, ride, element, gates */ + this.session = new SessionState(); + + // Weapon skill / ability SFX (user WAVs under public/audio/sfx) + this.skillSfx = SkillSfx; + try { + installSkillSfxGestureUnlock(); + if (typeof window !== 'undefined') window.SkillSfx = SkillSfx; + } catch (_) {} + /* ---- core ---- */ this.renderer = new Renderer(canvas); this.rig = new CameraRig(canvas); @@ -57,16 +164,73 @@ export class App { this.environment = new Environment(this.renderer, this.camera); this.scene = this.environment.scene; - /* ---- world ---- */ - this.ground = new Ground(this.environment); + /* ---- world (SI: ~2 m hero → map scale 1.5× original stage) ---- */ + // Fog follows island scale. Camera stays Fortnite shoulder TPS (settings.camera) + // — do NOT overwrite distance with WORLD.cameraDistance (~14 m) or focus dies. + settings.environment.fogNear = WORLD.fogNear; + settings.environment.fogFar = WORLD.fogFar; + // Soft max only: allow zoom out a bit on large pad, keep min tight for builder + settings.camera.maxDistance = Math.max( + settings.camera.maxDistance ?? 12, + Math.min(18, WORLD.cameraMaxDistance * 0.45) + ); + settings.camera.minDistance = Math.min(settings.camera.minDistance ?? 2.5, WORLD.cameraMinDistance); + + // Ground pad removed when heightfield is on — was double ground + z-fight. + // Seafloor (−5 m sand) + single StageWater (y=0) only. + this.ground = null; + this.seafloor = new Seafloor(); + this.water = new StageWater(); + /** Heightfield land (snakey / three-stylized / Rapier terrain patterns) */ + this.islandTerrain = + settings.terrain?.enabled !== false ? new IslandHeightfield() : null; + this.growingForest = null; + /** L2 stylized grass (three-stylized pattern on L0 height) */ + this.stylizedGrass = null; + this.islandTown = null; this.dust = new DustMotes(); - this.contactShadows = new ContactShadows(this.renderer, { size: 2.6, height: 2.4, blur: 2.0 }); + this.contactShadows = new ContactShadows(this.renderer, { + size: 2.6 * Math.sqrt(WORLD.mapScale), + height: 2.4, + blur: 2.0 + }); - this.scene.add(this.ground.mesh, this.dust.points, this.contactShadows.group); + this.scene.add( + this.seafloor.mesh, + this.water.mesh, + this.dust.points, + this.contactShadows.group + ); + /** One terrain handle for aim / path / harvest / drops (no N lambdas) */ + this.terrain = terrainHandle(this.islandTerrain); + if (this.islandTerrain?.mesh) { + this.scene.add(this.islandTerrain.mesh); + } else { + // Fallback flat pad only if heightfield disabled + this.ground = new Ground(this.environment); + this.scene.add(this.ground.mesh); + } + console.info( + `[App] world waterY=${WORLD.waterY} shelfY=${WORLD.seafloorY} oceanFloorY=${WORLD.oceanFloorY ?? -50} (shore bathymetry · single water)` + ); this.dust.setPixelRatio(this.renderer.gl.getPixelRatio()); + /** One map: Training Room · DevIsland (play + /devnode) */ + this.mapId = TRAINING_ROOM_MAP_ID; + this.mapLabel = TRAINING_ROOM_LABEL; + console.info( + `[App] ${TRAINING_ROOM_LABEL} · SI mapScale=${WORLD.mapScale} ground=${WORLD.groundSize}m fogFar=${WORLD.fogFar}m water=${WORLD.waterSize}m terrain=${!!this.islandTerrain}` + ); + console.info( + `[App] layers L0=${TRAINING_ROOM_BUILDERS.layers.L0_height.code} L2 grass+forest L3=${TRAINING_ROOM_BUILDERS.layers.L3_detail.harvest.code}` + ); /* ---- shared VFX services ---- */ this.particles = new ParticleEngine(this.scene); + /** Soft ocean wind (wind-attack silk, nearly invisible) — after particles */ + this.oceanWind = new OceanWindIndicators({ + scene: this.scene, + particles: this.particles + }); this.lights = new LightPool(this.scene); this.decals = new DecalSystem(this.scene); this.bursts = new BurstSystem(this.scene); @@ -85,44 +249,428 @@ export class App { flash: this.flash }); + /** + * Linear skillshots (LinearAbilityCastingThreeJS learnings): + * ice/thunder/meteor/beam/snare/glacier + MOBA aim indicators. + * Coexists with path-cast fire/storm/ice/nature/holy/arcane pools. + */ + this.linearSkills = new LinearSkillBridge({ + scene: this.scene, + camera: this.camera, + environment: this.environment, + particles: this.particles, + lights: this.lights, + decals: this.decals, + bursts: this.bursts, + shake: this.shake, + flash: this.flash, + character: null, // filled after CharacterController + onToast: (m) => this.hud?.showToast?.(m) + }); + /* ---- character ---- */ this.character = new CharacterController(this.environment); this.scene.add(this.character.root); + this.linearSkills.ctx.character = this.character; - // Walk mode: the same drawn path, ridden instead of cast. + // Walk mode: drawn path → windsurf ride (HoverboardRide + RideIK). this.walk = new WalkController(this.character, { scene: this.scene, particles: this.particles, lights: this.lights, decals: this.decals, bursts: this.bursts, - shake: this.shake + shake: this.shake, + water: this.water, + session: this.session, + assets: null, // filled in load() + onToast: (m) => this.hud?.showToast?.(m), + /** Fishing harvest tree + blue meals + rod sea_legs → freeride boat speed */ + getNauticalSpeedMul: () => this.fishing?.getNauticalSpeedMul?.() ?? 1 }); /* ---- input ---- */ this.input = new InputManager(canvas); this.pathDrawer = new PathDrawer(this.camera); this.scene.add(this.pathDrawer.object3D); + this.mouseAim = new MouseAim(this.camera); + // Same terrain for aim + path (after both exist) + this.mouseAim.setTerrain(this.terrain); + this.pathDrawer.setTerrain(this.terrain); + this.combatFocus = new CombatFocus(); + this.rig.setCombatFocus?.(this.combatFocus); + this.combatFocus.on('toast', (msg) => this.hud.showToast(msg)); + this.combatFocus.on('focus', (on) => { + this.hud.setCrosshairVisible?.(!!on); + this.hud.root?.classList.toggle('hud--focus', !!on); + this._applyMouseLockForFocus(!!on); + // Focus play: purge editor / admin / equip chrome so look is pure combat + if (on) { + this.editor?.close?.(); + if (this.adminHub?.open) this.adminHub.setOpen?.(false); + if (this.drc?.session === 'equip') this.drc.setSession?.('combat'); + if (this.session.mode === INTERACTION_MODE.WALK && !this.session.riding) { + // Stay walk only if freeriding; land combat wants casting mode for TPS + // Keep current mode if freeriding skills + } else if (this.session.mode !== INTERACTION_MODE.CASTING && !this.session.freeriding) { + this.session.setMode?.(INTERACTION_MODE.CASTING, { silent: true }); + } + this.rig.setViewMode?.('tps'); + this.rig.enterFocusLook?.(); + if (settings.aim?.softLockOnFocus !== false) { + const feet = this.character?.position || this.character?.root?.position; + const fwd = this.rig.getCameraForward?.(new Vector3()); + if (feet) { + this.combatFocus.acquireBest?.(feet, fwd) || + this.combatFocus.acquireNearest?.(feet, fwd); + } + } + const t = this.combatFocus.selectedTarget; + if (t) { + this.hud.setTargetFrame?.({ + name: + t.mesh?.userData?.displayName || + t.mesh?.name || + t.kind || + 'Target', + hp01: Number.isFinite(t.mesh?.userData?.hp01) + ? t.mesh.userData.hp01 + : 1, + present: true + }); + this.hud.root?.classList.add('hud--softlock'); + } + this.hud.showToast?.( + t + ? `Focus · soft-lock ${t.mesh?.userData?.displayName || t.mesh?.name || 'target'} · LMB attack` + : 'Focus · mouse look · crosshair · Tab cycle · LMB attack', + 2200 + ); + } else { + this.hud.root?.classList.remove('hud--softlock'); + } + }); + // Ground aim ring under crosshair (combat) + const ringGeo = new RingGeometry(0.18, 0.32, 32); + const ringMat = new MeshBasicMaterial({ + color: 0x7fd6ff, + transparent: true, + opacity: 0.75, + side: DoubleSide, + depthWrite: false + }); + this.aimMarker = new Mesh(ringGeo, ringMat); + this.aimMarker.rotation.x = -Math.PI / 2; + this.aimMarker.position.y = 0.04; + this.aimMarker.visible = false; + this.scene.add(this.aimMarker); /* ---- post ---- */ this.post = new PostProcessing(this.renderer, this.scene, this.camera); /* ---- UI ---- */ + // Warlords-era bag shells + pirate cursor theme (dev island gameplay chrome) + applyWarlordsUiCssVars(document.documentElement); + void preloadWarlordsUi(); + void preloadWarlordsCursors(); + configureWarlordsCursors({ + theme: 'pirate', + enabled: true, + target: canvas, + root: document.body + }); + setCursorIntent('default'); + this.loading = new LoadingScreen(); this.hud = new HUD(document.getElementById('hud')); + /** Bars pack overhead HP (enemy 003 · ally 001) */ + this.overheadBars = new OverheadNameplates({ + host: document.body, + maxDist: 48 + }); this.editor = new Editor({ onClear: () => this.clearEffects(), onToast: (message) => this.hud.showToast(message) }); + /** Singular tabbed VFX / skill authoring shell (hosts lil-gui knobs) */ + this.vfxStudio = new VfxStudio({ + editor: this.editor, + onClear: () => this.clearEffects(), + onToast: (message) => this.hud.showToast(message), + onPreviewEffect: (effectId) => { + try { + this.drc?.previewSandboxEffect?.(effectId); + } catch { + /* optional */ + } + }, + onPreviewDelivery: (pattern) => { + this.hud.showToast(`Delivery · ${pattern}`); + }, + getActiveSkill: () => { + try { + const f = typeof skillForFKey === 'function' ? skillForFKey() : null; + return f || this.drc?.skills?.[0] || null; + } catch { + return null; + } + } + }); + // Studio is the shell — start closed; V / toggleEditor opens it + this.vfxStudio.close(); + this.inventory = new InventoryPanel({ + character: this.character, + onToast: (message) => this.hud.showToast(message), + onEquip: () => { + // Weapon equip / dual-set swap refreshes hotbar from equipped catalog + try { + setActiveSkillTree('equipped'); + if (this.drc) this.drc.skills = getActiveSkills(); + } catch { + /* catalog optional during boot */ + } + this._syncPlayerFrame(); + this.hud.refreshSkillLabels?.(); + }, + onRace: async (raceId) => { + await this.character.setRace(raceId); + if (this._playerIdentity) { + this._playerIdentity.raceId = raceId; + // Keep custom name; refresh kit default when name came from kit + if ( + this._playerIdentity.source === 'kit' || + this._playerIdentity.source === 'kit-default' + ) { + this._playerIdentity.displayName = displayNameForKit( + raceId, + this._playerIdentity.roleId + ); + this._playerIdentity.raceLabel = raceDef(raceId).label; + } + } + this._syncPlayerFrame(); + this.hud.showToast(`Race · ${raceDef(raceId).label}`); + }, + onMode: (mode) => this.setMode(mode), + onMountToggle: () => {}, + getDrc: () => this.drc, + onDropWorld: (item, cx, cy) => this._throwBagItem?.(item, cx, cy), + onDepositItem: async (item) => { + // Prefer Railway deposit via fleetApi (InventoryPanel also tries first) + try { + const r = await fleetApi.depositItem(item); + if (r.ok) { + this.hud.showToast?.(r.message); + return; + } + if (r.authRequired) { + this.hud.showToast?.(r.message); + window.open('https://id.grudge-studio.com', '_blank', 'noopener'); + return; + } + } catch { + /* fall through */ + } + window.open('https://grudgewarlords.com/craft/', '_blank', 'noopener'); + this.hud.showToast?.( + `Deposit ${item?.name || item?.id || 'item'} → Craft bag SSOT` + ); + } + }); + // Admin F1 + deep links open Main Panel tabs + if (typeof window !== 'undefined') { + window.__castingInventory = this.inventory; + } + + /** F1–F5 admin tools: player · assets · creatures · prefabs · world */ + this.adminHub = new AdminHub({ + character: this.character, + getDrc: () => this.drc, + session: this.session, + onToast: (message) => this.hud.showToast(message), + onOpenInventoryPrefabs: () => { + this.showcase?.setOpen?.(false); + this.inventory.openTab?.('prefabs'); + }, + onHelp: () => this.hud.toggleHelp(), + spawnLoot: (n) => this.spawnWorldLoot?.(n), + respawnHarvest: async () => { + await this.worldHarvest?.spawnDefaultLayout?.(); + await this.worldHarvest?.spawnDecor?.(); + }, + equipHarvestTool: () => this._equipHarvestTool?.(), + respawnDummies: () => { + this.worldHarvest?.spawnTrainingDummies?.(); + this.overheadBars?.bindDummies?.(this.worldHarvest?.dummies || []); + } + }); + + this.showcase = new ShowcasePanel({ + character: this.character, + getDrc: () => this.drc, + onToast: (message) => this.hud.showToast(message), + onRace: async (raceId) => { + await this.character.setRace(raceId); + if (this._playerIdentity) { + this._playerIdentity.raceId = raceId; + if ( + this._playerIdentity.source === 'kit' || + this._playerIdentity.source === 'kit-default' + ) { + this._playerIdentity.displayName = displayNameForKit( + raceId, + this._playerIdentity.roleId + ); + } + } + this._syncPlayerFrame(); + }, + onShowcaseMode: (on) => { + // Orbit for review; combat returns to TPS when closed if still in combat + if (on) { + this.rig.setViewMode('orbit'); + this.drc.setSession?.('equip'); + this.inventory.setOpen?.(false); + } else if (this.drc.session === 'combat') { + this.rig.setViewMode('tps'); + } + this.hud.refreshSkillLabels?.(); + }, + onBindingsChanged: () => this.hud.refreshSkillLabels?.() + }); + + this.dropBag = new DropBag({ + onToast: (m) => this.hud.showToast(m), + onThrow: (item, cx, cy) => this._throwBagItem(item, cx, cy) + }); + + /** Activity: combat | harvest (Open Hold-Q parity) — XState owns pure mode/hand/tool */ + this.activityMode = 'combat'; + this.harvestToolId = DEFAULT_HARVEST_TOOL || DEFAULT_TOOL_ID || 'pick'; + this._combatWeaponId = null; + this.activityActor = createPlayerActivityActor({ + onTransition: (snap) => { + this.activityMode = activityFromSnap(snap); + this.harvestToolId = toolIdFromSnap(snap); + this.input.setActivityMode?.(this.activityMode); + } + }); + this.modeRadial = new ModeRadial(); + this._qHold = { armed: false, t: 0, open: false }; + this._rHold = { armed: false, t: 0, open: false }; + this.input.setActivityMode?.(this.activityMode); + + /** @type {WorldDrops|null} filled after assets load */ + this.worldDrops = null; + this._prefabCatalog = null; + + this.physics = new PhysicsWorld(); + this.vfxDirector = new VfxDirector({ + scene: this.scene, + particles: this.particles, + lights: this.lights, + decals: this.decals, + bursts: this.bursts, + shake: this.shake, + flash: this.flash + }); + + this.drc = new DrcCombatController({ + character: this.character, + abilities: this.abilities, + camera: this.camera, + physics: null, + vfx: this.vfxDirector, + aim: this.mouseAim, + combatFocus: this.combatFocus, + sessionState: this.session, + scene: this.scene, + linearSkills: this.linearSkills, + onToast: (message) => this.hud.showToast(message), + onCastBar: (st) => this.hud.setCastBar?.(st), + // Side effects applied once via session.change — toast only here + onSession: () => {} + }); + // Elemental skills ↔ linear skillshot bridge (learned LinearAbilityCasting) + this.drc.setLinearSkills?.(this.linearSkills); + // Warm fire/ice summon projectiles (extracted SI meshes) + this.drc.projectiles?.warm?.().catch?.(() => {}); this._bindEvents(); - this._mode = null; - this.setMode(settings.mode); + this.session.on('change', (snap, prev, reason) => this._onSessionChange(snap, prev, reason)); + // Bootstrap: apply gates without double toast + this.session.setMode(settings.mode, { silent: true }); + this.session.setDrc(settings.drc?.session || 'combat', { silent: true }); + this.session.setElement(ELEMENTS[0], { silent: true }); + this._onSessionChange(this.session.snapshot(), null, 'boot'); this.selectElement(ELEMENTS[0]); this._focusPoint = new Vector3(); } + /** + * Single place for camera / inventory / HUD / keys from SessionState. + * Controllers must not scatter these side effects. + * @param {import('./SessionState.js').SessionSnapshot} snap + * @param {import('./SessionState.js').SessionSnapshot|null} prev + * @param {string} reason + */ + _onSessionChange(snap, prev, reason) { + const g = snap.gates; + const modeChanged = !prev || prev.mode !== snap.mode; + const drcChanged = !prev || prev.drc !== snap.drc; + const rideChanged = !prev || prev.ridePhase !== snap.ridePhase; + + // Leaving walk / equip cancels ride machine + if (modeChanged && snap.mode !== INTERACTION_MODE.WALK) { + this.walk?.cancel?.(); + } + if (drcChanged && snap.drc === DRC_SESSION.EQUIP) { + this.walk?.cancel?.(); + } + + // Camera: combat/freeride = Fortnite shoulder TPS; equip/builder = orbit + if (g.tpsCamera) { + this.rig.applyGameplayMode?.('combat') || this.rig.setViewMode('tps'); + } else if (g.orbitCamera) { + this.rig.applyGameplayMode?.('builder') || this.rig.setViewMode('orbit'); + } + + this.input.setCombatKeys?.(g.combatKeys); + this.pathDrawer.setCombatMinLength?.( + snap.drc === DRC_SESSION.COMBAT && snap.mode === INTERACTION_MODE.CASTING + ? settings.staffCast?.combatMinPathLength ?? 0.9 + : null + ); + + if (g.inventoryOk) this.inventory?.setOpen?.(true); + else if (drcChanged || modeChanged) this.inventory?.setOpen?.(false); + + // HUD mirrors session (no local mode/session forks) + this.hud.setMode?.(snap.mode); + this.hud.setDrcSession?.(snap.drc); + if (this.hud.blurb) this.hud.blurb.textContent = this.session.blurb(); + + if (modeChanged && reason !== 'boot') { + if (snap.mode === INTERACTION_MODE.WALK) { + this.inventory?.setOpen?.(false); + if (this._assets && !this.walk.scooter?.ready) { + this.walk.load(this._assets).catch(() => {}); + } + this.hud.showToast('Windsurf · Space deploy vehicle · E get off · WASD steer · draw path = course'); + } else { + const meta = MODE_META[snap.mode]; + if (meta) this.hud.showToast(`${meta.hint} — ${meta.blurb}`); + } + this.editor?.refresh?.(); + } + + if (rideChanged && snap.freeriding && reason !== 'boot') { + // Freeride implies combat skills on board + if (snap.drc !== DRC_SESSION.COMBAT) this.session.setDrc(DRC_SESSION.COMBAT); + } + } + /* ------------------------------------------------------------------ */ _bindEvents() { @@ -132,27 +680,400 @@ export class App { this.dust.setPixelRatio(pixelRatio); }); - this.input.on('draw:start', (pointer) => this.pathDrawer.begin(pointer)); - this.input.on('draw:move', (pointer) => this.pathDrawer.move(pointer)); - this.input.on('draw:end', () => this.pathDrawer.end()); + // LMB: skillshot confirm · focus→attack · free→select · else path draw + this.input.getLmbMode = () => this._lmbMode(); + this.input.on('lmb:attack', () => { + if (this.linearSkills?.aim?.isArmed) { + this.linearSkills.confirm(); + return; + } + this._onLmbAttack(); + }); + this.input.on('lmb:select', (ptr) => { + if (this.linearSkills?.aim?.isArmed) { + this.linearSkills.aim.point(ptr); + this.linearSkills.confirm(); + return; + } + this._onLmbSelect(ptr); + }); + this.input.on('draw:start', (pointer) => { + if (this.linearSkills?.aim?.isArmed) { + this.linearSkills.aim.point(pointer); + this.linearSkills.confirm(); + return; + } + this.pathDrawer.begin(pointer); + }); + this.input.on('draw:move', (pointer) => { + if (this.linearSkills?.aim?.isArmed) { + this.linearSkills.aim.point(pointer); + return; + } + this.pathDrawer.move(pointer); + }); + this.input.on('draw:end', () => { + if (this.linearSkills?.aim?.isArmed) return; + this.pathDrawer.end(); + }); - this.input.on('element', (index) => this.selectElement(ELEMENTS[index])); - this.input.on('action', (action) => this._handleAction(action)); + this.input.on('element', (index) => { + // Combat: digits align element; fire is hold-to-charge (skillHold:start/end) + if (this.session.gates.combatSkills || this.drc.inCombat) { + const el = ELEMENTS[index]; + if (el) this.selectElement(el); + // Do not instant-fire here — skillHold:start begins charge / skillHold:end releases + return; + } + this.selectElement(ELEMENTS[index]); + }); + // Hold-to-charge weapon skills (Charged Shot UX · combat timer · rest) + this.input.on('skillHold:start', (slot) => { + if (!(this.session.gates.combatSkills || this.drc.inCombat)) return; + if (slot === 'f') { + // F still runs best-action first via combatAction; charge only if no pickup + return; + } + this.drc.beginWeaponCharge?.(slot); + }); + this.input.on('skillHold:end', (slot) => { + if (!(this.session.gates.combatSkills || this.drc.inCombat)) return; + // Release Charged Shot (F or digit) — tap or full charge + if (this.drc.weaponCharge?.active) { + this.drc.releaseWeaponCharge?.(); + } + }); + this.input.on('action', (action, detail) => this._handleAction(action, detail)); - // One gesture, two meanings — the mode decides what a finished stroke does. - this.pathDrawer.on('cast', (curve) => { - if (settings.mode === 'walk') { + // Fishing profession — wheel reel/slack · snag + this.canvas?.addEventListener?.( + 'wheel', + (e) => { + if (this.fishing?.phase === 'fight') { + e.preventDefault(); + this.fishing.onWheel(e.deltaY); + } + }, + { passive: false } + ); + window.addEventListener('keydown', (e) => { + if (e.code === 'KeyS' && this.fishing?.phase === 'bite') { + this.fishing.onSnagKey(); + } + // Profession start: hold Shift+F when pole equipped near water (optional) + if (e.code === 'KeyF' && e.shiftKey && this.fishing?.isPoleEquipped?.()) { + e.preventDefault(); + this.fishing.beginProfession(); + } + }); + // LMB/RMB during fishing fight / cast (capture before path when active) + this.canvas?.addEventListener?.('pointerdown', (e) => { + if (!this.fishing?.active && this.fishing?.phase === 'idle') return; + if (e.button === 0 && this.fishing?.onPrimaryDown?.()) { + e.preventDefault(); + e.stopPropagation(); + } + if (e.button === 2 && this.fishing?.onSecondaryDown?.()) { + e.preventDefault(); + } + }); + this.canvas?.addEventListener?.('pointerup', (e) => { + if (e.button === 0) this.fishing?.onPrimaryUp?.(); + if (e.button === 2) this.fishing?.onSecondaryUp?.(); + }); + this.input.on('sandboxVfx', (effectId) => { + if (this.drc.previewSandboxEffect(effectId)) { + this.hud.showToast(`VFX · ${effectId}`); + } + }); + // Linear skillshots (Alt+Shift+Q/E/R/F/V/G) + this.input.on('linearSkill', (id) => { + const feet = this.character?.position || this.character?.root?.position; + if (!feet || !this.linearSkills) return; + this.linearSkills.select(id); + this.linearSkills.arm(feet); + // Intensity follows settings.global.shaderIntensity if present + const inten = settings.global?.shaderIntensity ?? 1; + this.linearSkills.applyIntensity(inten); + }); + + // Path stroke meaning from SessionState.gates (not scattered settings.mode checks) + this.pathDrawer.on('cast', (curve, _pts, _n, length = 0, holdSec = 0) => { + const g = this.session.gates; + // Freeride: path = cast (ranged/staff), never re-deploy course + if (g.pathIsRide && !this.session.freeriding) { if (!this.walk.begin(curve)) this.hud.showToast('Path too short to ride'); + return; + } + if (this.drc.inCombat || this.session.freeriding) { + this.pathDrawer.setCombatMinLength?.(settings.staffCast?.combatMinPathLength ?? 0.9); + const ok = this.drc.castPathAbility?.(curve, length || curve?.getLength?.() || 0, holdSec); + if (!ok) { + this.abilities.cast(curve); + this.character.playCastFlourish?.(); + } + return; + } + this.abilities.cast(curve); + this.character.playCastFlourish?.(); + const end = curve?.getPoint?.(1) || curve?.points?.[curve.points.length - 1]; + if (end) { + this.character.setCasting?.(true, { aimX: end.x, aimY: end.y + 0.2, aimZ: end.z }); } else { - this.abilities.cast(curve); + this.character.setCasting?.(true); } }); this.hud.onSelect = (element) => this.selectElement(element); + this.hud.onSkillSlot = (slot) => { + // UI click = tap fire (no hold). Keyboard uses skillHold charge UX. + if (this.drc.inCombat) this.drc.useSkill(slot, { skipCharge: true }); + }; + this.hud.onMelee = () => { + // F slot = weapon skill (not residual) + if (this.drc.inCombat) this.drc.useWeaponSkillF?.(); + }; + this.hud.onQuickAction = (actionId) => { + if (actionId === 'interact' || actionId === 'fskill') { + this._tryBestAction(); + return; + } + this.drc.performQuickAction?.(actionId); + }; + this.hud.onMenu = (menuId) => this._handleHudMenu(menuId); this.hud.onMode = (mode) => this.setMode(mode); + + // Combat hotkeys: E = block · C = parry · F = best-next-action (pickup/harvest/attack) + this.input.on('combatAction', (actionId) => { + if (actionId === 'interact') { + this._tryBestAction(); + return; + } + this.drc.performQuickAction?.(actionId); + }); + + // Canvas drag-drop from bag → throw world drop + const canvas = this.canvas; + canvas.addEventListener('dragover', (e) => { + e.preventDefault(); + e.dataTransfer.dropEffect = 'copy'; + }); + canvas.addEventListener('drop', (e) => { + e.preventDefault(); + this.dropBag?.handleCanvasDrop?.(e); + }); + } + + _handleHudMenu(menuId) { + switch (menuId) { + case 'lab': + case 'inventory': + this.showcase?.setOpen?.(false); + this.inventory.toggle(); + break; + case 'showcase': + this.inventory.setOpen?.(false); + this.showcase?.toggle?.(); + break; + case 'bag': + this.dropBag?.toggle?.(); + break; + case 'loot': + this.spawnWorldLoot?.(3); + break; + case 'editor': + if (this.vfxStudio) this.vfxStudio.toggle(); + else this.editor.toggle(); + break; + case 'admin': + this.adminHub?.openTab?.('prefabs'); + break; + case 'help': + this.hud.toggleHelp(); + break; + case 'clear': + this.clearEffects(); + this.worldDrops?.clear?.(); + this.hud.showToast('Effects + world drops cleared'); + break; + case 'mainpanel': + window.open('https://ui.grudge-studio.com/main-panel.html?era=warlords', '_blank', 'noopener'); + break; + default: + break; + } + } + + /** + * F — context priority then weapon skill: + * 1. Pickup nearby world drop + * 2. Harvest nearest node within 5 m (tool in hand when required) + * 3. Equipped weapon skill (primary / Showcase F bind) — cast times + prefabs + * Class abilities deferred. Residual is not F. + * @returns {boolean} + */ + _tryBestAction() { + // 1) World loot pickup + if (this.worldDrops) { + const bag = this.worldDrops.tryPickup(this.character.position, 2.4); + if (bag) { + this.dropBag?.add(bag); + this.hud.showToast(`Picked up ${bag.name || 'item'}`); + return true; + } + } + + // 2) Harvest — always try if node in range; harvest mode prioritizes over skills + const wantHarvest = + this.activityMode === 'harvest' || + this.worldHarvest?.nearestAlive?.(this.character.position, HARVEST_RANGE_M); + if (wantHarvest) { + if (typeof this.tryHarvest === 'function') { + const harvested = this.tryHarvest(); + if (harvested) return true; + } + if (this.worldHarvest?.tryInteract) { + const ok = this.worldHarvest.tryInteract( + this.character.position, + HARVEST_RANGE_M + ); + if (ok) return true; + } + if (this.activityMode === 'harvest') { + this.hud.showToast('No harvest node ≤5 m'); + return true; + } + } + + // 3) Weapon skill F (prefab + cast bar path) — combat mode + if (this.drc?.inCombat) { + return !!( + this.drc.useWeaponSkillF?.() || + this.drc.performQuickAction?.('fskill') || + false + ); + } + if (this.character.playWeaponAttack?.()) { + this.hud.showToast('Equip combat (Q) for weapon skills'); + return true; + } + this.hud.showToast('F · nothing nearby'); + return false; + } + + /** + * Explicit harvest attempt (nearest ≤5 m). Used by F chain + Admin. + * Rocks/ore via DevIslandHarvest; mature growing trees (wood) via GrowingForest. + * @returns {boolean} + */ + tryHarvest() { + const pos = this.character?.position || this.character?.root?.position; + if (!pos) return false; + // Prefer catalog nodes first + if (this.worldHarvest?.tryHarvestNearest) { + const ok = this.worldHarvest.tryHarvestNearest(pos, HARVEST_RANGE_M); + if (ok) return true; + } + // Growing forest trees (hatchet / wood class) + if (this.growingForest) { + const tree = this.growingForest.findNearest(pos, HARVEST_RANGE_M); + if (tree) { + const tool = this.harvestToolId || 'pick'; + if (tool !== 'hatchet' && tool !== 'hand' && this.activityMode === 'harvest') { + // still allow with any tool in combat F path + } + const result = this.growingForest.damage(tree, 14); + if (result?.chopped && result.loot) { + for (const L of result.loot) { + this.dropBag?.add?.({ + id: L.id, + name: L.label, + qty: L.qty, + icon: L.icon + }); + } + this.character?.playWeaponCombat?.('attack') || + this.character?.playRoleOnce?.('attack'); + } else if (result && !result.chopped) { + this.character?.playWeaponCombat?.('attack'); + } + return !!result; + } + } + return false; + } + + /** + * Equip t0-tool for mine/chop harvest swings (sword_shield attack anim). + */ + async _equipHarvestTool() { + try { + await equipWeaponById('t0-tool', { + character: this.character, + onToast: (m) => this.hud.showToast(m) + }); + setActiveSkillTree?.('equipped'); + this.hud.refreshSkillLabels?.(); + this.hud.showToast('Tool equipped · F harvest nearest ≤5 m'); + } catch (err) { + this.hud.showToast(err?.message || 't0-tool equip failed'); + } + } + + /** + * Throw bag item to screen/world aim. + * @param {object} item + * @param {number} clientX + * @param {number} clientY + */ + async _throwBagItem(item, clientX, clientY) { + if (!this.worldDrops || !item) return; + this.mouseAim.updateFromClient(clientX, clientY, this.character.position); + const to = this.mouseAim.valid + ? this.mouseAim.point.clone() + : this.character.position.clone().add(new Vector3(0, 0, 2)); + const from = this.character.position.clone(); + from.y += 1.2; + await this.worldDrops.throwFrom(item, from, to); + this.hud.showToast(`Threw ${item.name}`); + } + + /** + * Spawn sample prefab drops near player (loot demo). + * @param {number} [count] + */ + async spawnWorldLoot(count = 3) { + if (!this.worldDrops) { + this.hud.showToast('Drops not ready'); + return; + } + try { + if (!this._prefabCatalog) { + this.hud.showToast('Loading weapon prefabs…'); + this._prefabCatalog = await loadPrefabCatalog(); + } + const cat = this._prefabCatalog; + const origin = this.character.position; + for (let i = 0; i < count; i++) { + const p = pickSamplePrefab(cat, { maxTier: 5 }); + if (!p) continue; + const ang = (i / count) * Math.PI * 2 + Math.random() * 0.4; + const r = 1.8 + Math.random() * 2.2; + const pos = new Vector3( + origin.x + Math.cos(ang) * r, + 0, + origin.z + Math.sin(ang) * r + ); + await this.worldDrops.spawn(p, pos); + } + this.hud.showToast(`Spawned ${count} world drops (icon+glow · F pickup)`); + } catch (err) { + console.warn(err); + this.hud.showToast(err?.message || 'Prefab catalog failed'); + } } - _handleAction(action) { + _handleAction(action, detail) { const index = ELEMENTS.indexOf(this.abilities.selected); switch (action) { case 'nextElement': @@ -161,18 +1082,116 @@ export class App { case 'prevElement': this.selectElement(ELEMENTS[(index - 1 + ELEMENTS.length) % ELEMENTS.length]); break; + case 'toggleDrcSession': + this.drc.toggleSession(); + break; + case 'qHoldStart': + this._beginQHold(); + break; + case 'qHoldEnd': + this._endQHold(); + break; + case 'rHoldStart': + this._beginRHold(); + break; + case 'rHoldEnd': + this._endRHold(); + break; case 'toggleHelp': this.hud.toggleHelp(); break; + case 'adminTab': { + // F1–F4 · ] World → Admin Hub + const key = typeof detail === 'string' ? detail : 'F4'; + this.adminHub?.openByKey?.(key); + break; + } + case 'toggleAutoTraverse': { + const on = this.drc?.toggleAutoTraverse?.(); + // Freeride: keep board thrust via auto KeyW inject in input.keys + if (on) this.input?.keys?.add?.('KeyW'); + else this.input?.keys?.delete?.('KeyW'); + break; + } + case 'cycleTarget': { + // Tab / Shift+Tab soft-lock (grudge-combat-targeting) + if (settings.aim?.tabCycleTargets === false) break; + const feet = this.character?.position || this.character?.root?.position; + if (!feet || !this.combatFocus) break; + const reverse = !!(detail && detail.reverse); + // Focus + soft lock engage when cycling + if (!this.combatFocus.focusEnabled) { + this.combatFocus.focusEnabled = true; + this.combatFocus.showCrosshair = true; + this.combatFocus.softLockEnabled = true; + this.combatFocus.emit('focus', true); + this._applyMouseLockForFocus?.(true); + } + this.combatFocus.softLockEnabled = true; + const fwd = this.rig.getCameraForward?.(new Vector3()); + this.combatFocus.cycleTarget(feet, reverse, fwd); + // HUD target frame + const t = this.combatFocus.selectedTarget; + if (t) { + const label = + t.mesh?.userData?.displayName || + t.mesh?.name || + t.kind || + 'Target'; + this.hud.setTargetFrame?.({ + name: label, + hp01: Number.isFinite(t.mesh?.userData?.hp01) + ? t.mesh.userData.hp01 + : 1, + present: true + }); + } else { + this.hud.setTargetFrame?.(null); + } + break; + } + case 'closeAdmin': + this.linearSkills?.cancel?.(); + if (this.adminHub?.open) { + this.adminHub.setOpen(false); + this.hud.showToast('Admin closed'); + } + break; case 'toggleEditor': - this.editor.toggle(); + if (this.vfxStudio) this.vfxStudio.toggle(); + else this.editor.toggle(); + break; + case 'toggleInventory': + if (this.drc.inCombat) this.drc.setSession('equip'); + else { + this.showcase?.setOpen?.(false); + this.inventory.toggle(); + this.hud.showToast(this.inventory.open ? 'Inventory open' : 'Inventory closed'); + } + break; + case 'toggleShowcase': + this.inventory.setOpen?.(false); + this.showcase?.toggle?.(); + this.hud.showToast(this.showcase?.open ? 'Showcase open' : 'Showcase closed'); + break; + case 'toggleDropBag': + this.dropBag?.toggle?.(); + break; + case 'spawnLoot': + this.spawnWorldLoot?.(4); + break; + case 'weaponAttack': + // Legacy path — same as F best-next-action + this._tryBestAction(); break; case 'clear': this.clearEffects(); + this.character.setCasting?.(false); this.hud.showToast('Effects cleared'); break; case 'togglePause': this.paused = !this.paused; + this.session.setPaused(this.paused); this.hud.showToast(this.paused ? 'Paused' : 'Resumed'); break; case 'togglePose': { @@ -191,32 +1210,640 @@ export class App { selectElement(element) { if (!element) return; + this.session.setElement(element); this.abilities.select(element); this.hud.setElement(element); } /** - * Switch between casting and walking. - * - * `settings.mode` is the source of truth — the editor writes it directly and - * the frame loop notices — so this is also the sync point for presets and - * "reset to defaults". + * Combat LMB routing (grudge-combat-targeting): + * focus ON → attack + * focus OFF → select (unlocked mouse) + * casting / walk path → draw + * @returns {'draw'|'attack'|'select'} */ - setMode(mode) { - const next = MODES.includes(mode) ? mode : MODES[0]; - const changed = this._mode !== next; - this._mode = next; - settings.mode = next; + _lmbMode() { + // Skillshot armed → free cursor confirm (not path draw) + if (this.linearSkills?.aim?.isArmed) return 'select'; + // Freeride + ranged/staff: non-focus path cast (unlocked cursor) + if (this.session?.freeriding && this._isRangedOrStaffEquipped()) { + return 'draw'; + } + if (!this.drc?.inCombat) return 'draw'; + // Walk land (not mounted): draw course for windsurf path + if (this.session?.mode === INTERACTION_MODE.WALK && !this.session?.riding) return 'draw'; + if (this.session?.mode === INTERACTION_MODE.WALK && this.session?.freeriding) { + return this._isRangedOrStaffEquipped() ? 'draw' : 'select'; + } + if (this.combatFocus?.focusEnabled) return 'attack'; + return 'select'; + } + + /** + * Staff / bow / wand / magic = freeride non-focus cast path. + */ + _isRangedOrStaffEquipped() { + const w = getEquippedWeapon?.() || this.character?.weapon || null; + const id = `${w?.id || ''} ${w?.weaponType || ''} ${w?.animPack || ''}`.toLowerCase(); + if (/staff|wand|bow|longbow|crossbow|gun|pistol|rifle|magic|tome|spell/.test(id)) return true; + const pack = this.character?.animPack || this.character?.activePack || ''; + if (/magic|longbow|pistol|bow/.test(String(pack))) return true; + // Default staff casting lab + if (!w) return true; + return false; + } + + /* ── Hold Q / R radials (Open parity) ───────────────────────── */ - if (next !== 'walk') this.walk.cancel(); - this.hud.setMode(next); - if (changed) this.hud.showToast(`${MODE_META[next].hint} — ${MODE_META[next].blurb}`); - this.editor.refresh(); + _beginQHold() { + this._qHold = { armed: true, t: 0, open: false }; + } + + _endQHold() { + if (!this._qHold?.armed && !this._qHold?.open) return; + if (this._qHold.open) { + const aim = this.modeRadial?.getAimId?.(); + if (aim === 'mode_harvest') this.setActivityMode('harvest'); + else if (aim === 'mode_combat') this.setActivityMode('combat'); + this.modeRadial?.hide?.(); + } else if (this._qHold.armed && this._qHold.t < RADIAL_HOLD_S) { + // Tap Q · combat → dual weapon loadout A↔B (mesh · anim pack · skills) + // Tap Q · harvest → back to combat (hold Q = mode radial) + if (this.activityMode === 'combat') { + void this._swapWeaponLoadoutTap(); + } else { + this.setActivityMode('combat'); + } + } + this._qHold = { armed: false, t: 0, open: false }; + } + + /** + * Combat Q-tap: swap Weapon 1 ↔ Weapon 2 (paperdoll mainHand / weapon2). + * Rotates mesh attach, anim pack / locomotion, and equipped skill hotbar. + */ + async _swapWeaponLoadoutTap() { + try { + const result = await swapWeaponLoadout({ + character: this.character, + onToast: (m) => this.hud.showToast(m), + onSkills: () => { + setActiveSkillTree('equipped'); + if (this.drc) this.drc.skills = getActiveSkills(); + this.hud.refreshSkillLabels?.(); + this.inventory?.refresh?.(); + } + }); + if (result?.ok) { + this._combatWeaponId = result.weapon?.id || this._combatWeaponId; + this._syncPlayerFrame?.(); + } + } catch (err) { + console.warn('[App] weapon loadout swap', err); + this.hud.showToast(err?.message || 'Weapon swap failed'); + } + } + + _beginRHold() { + if (this.activityMode !== 'harvest') return; + this._rHold = { armed: true, t: 0, open: false }; + } + + _endRHold() { + if (!this._rHold?.armed && !this._rHold?.open) return; + if (this._rHold.open) { + const aim = this.modeRadial?.getAimId?.(); + if (aim) this._selectHarvestTool(aim); + this.modeRadial?.hide?.(); + } else if (this._rHold.armed && this._rHold.t < RADIAL_HOLD_S) { + // Tap R → draw last used tool (default pick) without radial + this._drawLastHarvestTool(); + } + this._rHold = { armed: false, t: 0, open: false }; + } + + /** + * @param {'combat'|'harvest'} mode + */ + async setActivityMode(mode) { + const next = mode === 'harvest' ? 'harvest' : 'combat'; + const prev = this.activityMode; + if (next === prev) { + this.hud.showToast?.(`${MODE_LABEL[next]} mode`); + return; + } + + if (next === 'harvest') { + // Stow combat weapon memory → draw last tool (default pick) + const eq = getEquippedWeapon?.(); + const combatId = eq?.id || this._combatWeaponId || null; + this._combatWeaponId = combatId; + this.activityActor?.send?.({ + type: 'ENTER_HARVEST', + combatWeaponId: combatId, + toolId: this.harvestToolId || DEFAULT_TOOL_ID + }); + await this._applyHandForHarvest(this.harvestToolId || DEFAULT_TOOL_ID); + } else { + // Stow tool → restore combat weapon + this.activityActor?.send?.({ + type: 'ENTER_COMBAT', + combatWeaponId: this._combatWeaponId + }); + await this._applyHandForCombat(); + if (this.drc?.session === 'equip') { + this.drc.setSession?.('combat'); + } + } + + this.activityMode = next; + this.input.setActivityMode?.(next); + this.hud.showToast?.( + `${MODE_LABEL[next]} mode · Hold Q switch · ${ + next === 'harvest' + ? 'F nearest · Hold R tools · Tap R last tool' + : 'Tap Q weapon 1↔2 · F skill · 1–4' + }` + ); + this.hud.root?.classList.toggle('hud--harvest', next === 'harvest'); + } + + /** Tap R / machine DRAW_LAST_TOOL — pull last tool, default pick. */ + async _drawLastHarvestTool() { + if (this.activityMode !== 'harvest') return; + this.activityActor?.send?.({ type: 'DRAW_LAST_TOOL' }); + const toolId = + this.activityActor?.getSnapshot?.()?.context?.toolId || + this.harvestToolId || + DEFAULT_TOOL_ID; + await this._selectHarvestTool(toolId, { quiet: false, fromTapR: true }); + } + + /** + * Harvest enter: put away weapon mesh, equip tool. + * @param {string} toolId + */ + async _applyHandForHarvest(toolId) { + await this._selectHarvestTool(toolId || DEFAULT_TOOL_ID, { quiet: true }); + } + + /** Combat enter: restore stashed weapon (or default staff/sword). */ + async _applyHandForCombat() { + const id = this._combatWeaponId; + try { + if (id) { + await equipWeaponById(id, { + character: this.character, + onToast: (m) => this.hud.showToast(m) + }); + setActiveSkillTree?.('equipped'); + } else { + try { + await equipWeaponById('t0-wand', { + character: this.character, + onToast: () => {} + }); + setActiveSkillTree?.('equipped'); + } catch { + unequipWeapon?.({ character: this.character, onToast: () => {} }); + } + } + this.hud.refreshSkillLabels?.(); + } catch (e) { + this.hud.showToast?.(e?.message || 'Restore weapon failed'); + } + } + + /** + * @param {string} toolId + * @param {{ quiet?: boolean, fromTapR?: boolean }} [opts] + */ + async _selectHarvestTool(toolId, opts = {}) { + const def = HARVEST_TOOL_RADIAL.find((t) => t.id === toolId); + if (!def) return; + this.activityActor?.send?.({ type: 'SELECT_TOOL', toolId }); + this.harvestToolId = toolId; + if (toolId === 'back_slot') { + this.setMode('walk'); + if (!opts.quiet) this.hud.showToast('Back slot · Surf (M) · Space deploy windsurf'); + return; + } + if (toolId === 'hand') { + try { + unequipWeapon({ character: this.character, onToast: (m) => this.hud.showToast(m) }); + } catch { + /* ok */ + } + if (!opts.quiet) this.hud.showToast('Hands · gather herbs / pebbles'); + return; + } + if (def.weaponId) { + try { + await equipWeaponById(def.weaponId, { + character: this.character, + onToast: opts.quiet ? () => {} : (m) => this.hud.showToast(m) + }); + setActiveSkillTree?.('equipped'); + this.hud.refreshSkillLabels?.(); + if (!opts.quiet) { + this.hud.showToast( + opts.fromTapR + ? `Tool · ${def.label} (last) · F harvest ≤5 m` + : `Tool · ${def.label} · F harvest nearest ≤5 m` + ); + } + } catch (e) { + this.hud.showToast(e?.message || `Equip ${def.label} failed`); + } + } + } + + _tickRadials(dt) { + const cx = this.input?.clientX ?? window.innerWidth * 0.5; + const cy = this.input?.clientY ?? window.innerHeight * 0.5; + + if (this._qHold?.armed || this._qHold?.open) { + this._qHold.t += dt; + if (this._qHold.t >= RADIAL_HOLD_S && !this._qHold.open) { + this._qHold.open = true; + // Unlock cursor for wedge aim + if (this.combatFocus?.focusEnabled) { + this.combatFocus.focusEnabled = false; + this.combatFocus.emit?.('focus', false); + } + setCursorIntent('default', { force: true }); + this.modeRadial.show({ + kind: 'mode', + current: this.activityMode, + aimId: this.activityMode === 'harvest' ? 'mode_harvest' : 'mode_combat' + }); + } + if (this._qHold.open) this.modeRadial.aimFromPointer(cx, cy); + } + + if (this._rHold?.armed || this._rHold?.open) { + this._rHold.t += dt; + if (this._rHold.t >= RADIAL_HOLD_S && !this._rHold.open) { + this._rHold.open = true; + if (this.combatFocus?.focusEnabled) { + this.combatFocus.focusEnabled = false; + this.combatFocus.emit?.('focus', false); + } + setCursorIntent('default', { force: true }); + this.modeRadial.show({ + kind: 'tool', + current: this.activityMode, + toolId: this.harvestToolId, + aimId: this.harvestToolId + }); + } + if (this._rHold.open) this.modeRadial.aimFromPointer(cx, cy); + } + } + + _onLmbAttack() { + // Focus + LMB: staff/wand → hotbar slot 1 normal attack (shared staff primary). + // Melee / other → weapon primary (F / residual path). + if (this._isRangedOrStaffEquipped?.()) { + const ok = + this.drc.useSkill?.(0) || + this.drc.useWeaponSkillF?.() || + this.drc.performQuickAction?.('primary'); + if (!ok) this.hud.showToast('Staff normal (slot 1)'); + return; + } + const ok = + this.drc.useMeleeStrike?.() || + this.drc.performQuickAction?.('primary') || + this.character.playWeaponCombat?.('attack') || + this.character.playWeaponCombat?.('cast'); + if (!ok) this.hud.showToast('Attack'); + } + + /** + * World ground reticle — only for aim / AoE / placement skills. + * Screen crosshair (HUD) is independent and only shows in focus mode. + * @returns {boolean} + */ + _shouldShowGroundAimMarker() { + if (settings.aim?.groundMarker === false) return false; + + // Staff path stroke = placement / wall / aoe / stream + if ( + settings.aim?.groundMarkerOnPathDraw !== false && + this.pathDrawer?.active + ) { + return true; + } + + // Active cast whose delivery needs a ground point + const cast = this.drc?._cast; + if (cast) { + const sk = cast.skill || cast.drcSkill || { + id: cast.skillId, + label: cast.label, + style: cast.style, + pathMode: cast.pathMode, + effects: cast.effects, + element: cast.element + }; + if (skillNeedsGroundMarker(sk)) return true; + if (cast.delivery && deliveryNeedsGroundMarker(cast.delivery)) return true; + if (cast.pathMode && deliveryNeedsGroundMarker(`path_${cast.pathMode}`)) { + return true; + } + } + + // Hotbar skill being charged / selected for placement (1–4) + // Only while LMB held or cast bar active — not idle focus look + const lmb = + this.input?.keys?.has?.('Mouse0') || + this.input?.pointerButtons?.has?.(0) || + this.input?.mouseDown === true; + if (lmb || this.drc?.isCasting) { + // Prefer cast skill; else F / last slot skill if placement type + const fSkill = skillForFKey?.() || null; + if (fSkill && skillNeedsGroundMarker(fSkill) && this.drc?.isCasting) { + return true; + } + for (let slot = 0; slot < 4; slot++) { + const sk = skillBySlot?.(slot); + if (sk && skillNeedsGroundMarker(sk) && this.drc?.isCasting) return true; + } + } + + // Explicit placement preview while drawing path is enough; idle focus = no ring + return false; + } + + /** + * Free aim: LMB selects target (soft lock). Mouse stays unlocked. + * @param {import('three').Vector2} ptr NDC + */ + _onLmbSelect(ptr) { + document.exitPointerLock?.(); + setCursorIntent('default'); + const picked = this.combatFocus?.pickFromNdc?.(this.camera, ptr); + if (picked) { + const t = this.combatFocus.selectedTarget; + const label = + t?.mesh?.userData?.displayName || + t?.mesh?.name || + (t?.kind === 'hostile' ? 'Hostile' : t?.kind) || + 'Target'; + if (t?.kind === 'hostile' || t?.mesh?.userData?.trainingDummy) { + setCursorIntent('attack'); + } + this.hud.showToast(`Target · ${label}`); + this.hud.setTargetFrame?.({ + name: label, + hp01: Number.isFinite(t?.mesh?.userData?.hp01) + ? t.mesh.userData.hp01 + : 1, + present: true + }); + } else { + this.combatFocus?.clearTarget?.(); + this.hud.showToast('No target'); + this.hud.setTargetFrame?.(null); + } + } + + /** + * Focus ON = remove OS mouse; mouse becomes look + center crosshair aim. + * Focus OFF = unlock cursor for free select / UI. + * @param {boolean} focusOn + */ + _applyMouseLockForFocus(focusOn) { + document.body?.classList.toggle('focus-aim', !!focusOn); + if (!focusOn) { + if (document.pointerLockElement) document.exitPointerLock?.(); + setCursorIntent('select', { + force: true, + label: 'Free aim', + lmb: 'Select target', + rmb: 'Focus look (toggle)' + }); + this.hud.setCrosshairVisible?.(false); + return; + } + // Hide cursor + tip; screen-center crosshair is the reticle + setCursorIntent('none', { force: true, tooltip: false }); + if (this.canvas) { + this.canvas.style.cursor = 'none'; + // Pointer lock (RMB toggle is a user gesture). Fallback: free mouse delta look. + const tryLock = () => { + try { + this.canvas.requestPointerLock?.(); + } catch { + /* policy */ + } + }; + tryLock(); + // Retry once after tick if browser delayed grant + window.setTimeout(() => { + if (this.combatFocus?.focusEnabled && document.pointerLockElement !== this.canvas) { + tryLock(); + } + }, 40); + } + this.hud.setCrosshairVisible?.(true); + } + + /** + * Unlocked cursor: harvest / loot / target drives pirate intent + LMB/RMB tip. + * Cursors are ~28px (baked). Focus lock hides cursor (crosshair only). + */ + _updateInteractCursor() { + if (this.combatFocus?.focusEnabled) { + setCursorIntent('none', { force: true, tooltip: false }); + return; + } + if (document.pointerLockElement) return; + const pos = this.character?.position || this.character?.root?.position; + if (!pos) return; + + const lmbMode = this._lmbMode?.() || 'select'; + const rmbAlways = 'Focus look (toggle)'; + + // World drop in pickup range + if (this.worldDrops?.items?.length) { + let nearDrop = false; + for (const it of this.worldDrops.items) { + if (it.throw) continue; + const dx = it.root.position.x - pos.x; + const dz = it.root.position.z - pos.z; + if (dx * dx + dz * dz <= 2.4 * 2.4) { + nearDrop = true; + break; + } + } + if (nearDrop) { + setCursorIntent('pickup', { + label: 'Loot drop', + lmb: lmbMode === 'attack' ? 'Attack' : 'Select drop', + rmb: rmbAlways, + extra: 'F · pick up' + }); + return; + } + } + + // Harvest nearest ≤5 m + if (this.worldHarvest?.nearestAlive) { + const hit = this.worldHarvest.nearestAlive(pos, HARVEST_RANGE_M); + if (hit?.node) { + const name = hit.node.def?.label || hit.node.def?.classId || 'node'; + setCursorIntent('harvest', { + label: name, + lmb: lmbMode === 'draw' ? 'Draw path' : 'Select node', + rmb: rmbAlways, + extra: `F · harvest (${hit.dist.toFixed(1)} m)` + }); + return; + } + } + + // Soft-lock hostile → attack cursor + const t = this.combatFocus?.selectedTarget; + if (t && (t.kind === 'hostile' || t.mesh?.userData?.trainingDummy)) { + setCursorIntent('attack', { + label: t.mesh?.userData?.displayName || t.mesh?.name || 'Hostile', + lmb: lmbMode === 'attack' ? 'Attack (focus on)' : 'Select target', + rmb: rmbAlways + }); + return; + } + + // LMB mode drives default / draw / select tips + if (lmbMode === 'draw') { + setCursorIntent('draw', { + label: this.session?.freeriding ? 'Freeride cast' : 'Path draw', + lmb: this.session?.mode === 'walk' && !this.session?.freeriding + ? 'Draw windsurf course' + : 'Draw cast path', + rmb: rmbAlways + }); + return; + } + if (lmbMode === 'attack') { + setCursorIntent('slash', { + label: 'Combat', + lmb: 'Primary attack', + rmb: rmbAlways + }); + return; + } + + setCursorIntent('select', { + label: this.activityMode === 'harvest' ? 'Harvest mode' : 'Ready', + lmb: 'Select target', + rmb: rmbAlways, + extra: + this.activityMode === 'harvest' + ? 'Hold R · tools · F harvest' + : 'Tap Q weapon swap · Hold Q mode' + }); + } + + /** Push honest name/race into HUD + tight bar. */ + _syncPlayerFrame() { + const id = this._playerIdentity; + const raceId = this.character?.raceId || id?.raceId || 'WK'; + const name = + id?.displayName || + displayNameForKit(raceId, id?.roleId || this.character?.presetId) || + 'Warlord'; + const maxM = this.drc?.maxMana || 100; + const maxS = this.drc?.maxStamina || 100; + const maxH = this.drc?.maxHealth || 100; + const hp01 = + this.drc?.health != null + ? Math.max(0, Math.min(1, this.drc.health / maxH)) + : 1; + this.hud.setPlayerFrame?.({ + name, + raceId, + hp01, + mana01: (this.drc?.mana ?? maxM) / maxM, + sta01: (this.drc?.stamina ?? maxS) / maxS + }); + } + + /** + * Walk mode: + * - Space (edge) while **not** riding → deploy windsurf vehicle (frontflip + board) + * - E (edge) while freeride/ride → get off: unparent, remove board, land loco + * Gate: session.gates.freerideDeploy for Space; riding for E + */ + _pollWindsurfDeploy() { + const keys = this.input.keys; + // ` auto traverse: hold forward while freeride / land sprint-run + if (this.drc?.isAutoTraverse?.()) { + keys.add('KeyW'); + this._autoInjectedW = true; + } else if (this._autoInjectedW) { + keys.delete('KeyW'); + this._autoInjectedW = false; + } + const space = keys.has('Space'); + const eKey = keys.has('KeyE'); + const spacePressed = space && !this._wasWalkSpace; + const ePressed = eKey && !this._wasWalkE; + this._wasWalkSpace = space; + this._wasWalkE = eKey; + + // Get off vehicle — board removed, controller back to normal + if ( + ePressed && + this.walk?.active && + (this.walk.freeriding || this.walk.phase === 'ride' || this.walk.phase === 'freeride') + ) { + if (this.walk.requestDismount?.()) { + this.hud.showToast('Windsurf · dismount · board stowed'); + } + return; + } + + // Space while freeriding = hop (WalkController freerideJump) — do not redeploy + if ( + this.walk?.freeriding || + this.walk?.phase === 'freeride' || + this.session?.freeriding + ) { + return; + } + + if (!this.session.gates.freerideDeploy) return; + if (!spacePressed) return; + if (!this.walk.scooter?.ready && this._assets) { + this.walk.load(this._assets).catch(() => {}); + } + // Water-only: WalkController rejects dry land + const ok = this.walk.beginFreeride({ yaw: this.character.facing }); + if (ok) { + this.hud.showToast( + 'Windsurf · water vehicle · board/sail/front/engine · E = get off' + ); + } else { + this.hud.showToast( + 'Windsurf is water-only — move to ocean (not dry island)' + ); + } + } + + /** + * Interaction mode via SessionState (settings.mode mirrored for editor). + * Side effects: session.change → _onSessionChange. + */ + setMode(mode) { + this.session.setMode(mode); } clearEffects() { this.walk.cancel(); this.abilities.clear(); + this.linearSkills?.clear?.(); this.particles.reset(); this.decals.clear(); this.bursts.clear(); @@ -231,22 +1858,382 @@ export class App { /** Load assets, warm the shader cache, then start the loop. */ async load() { const assets = new AssetLoader(); + // KTX2 needs GPU detectSupport — bind before any compressed-texture GLB + try { + const gl = this.renderer?.gl || this.renderer?.renderer; + if (gl) { + const ok = assets.bindRenderer(gl); + console.info( + '[App] glTF pipeline', + assets.pipelineStatus?.() || {}, + ok ? 'KTX2 on' : 'KTX2 off (WebP/PNG only)' + ); + } + } catch (e) { + console.warn('[App] KTX2 bind skipped', e); + } + + this.loading.setProgress(0.05, 'Init Rapier physics…'); + try { + await this.physics.init(); + // Land heightfield + water sensor layer (must learn: one height source) + if (this.islandTerrain) { + const ok = this.physics.addHeightfield(this.islandTerrain.rapierDesc(), { + landHeightAt: this.terrain?.sample || null, + waterHeightAt: (x, z, t) => + this.water?.sampleHeight?.(x, z, t ?? this.elapsed) ?? WORLD.waterY + }); + console.info('[App] terrain heightfield physics', ok ? 'ok' : 'fallback flat'); + } + this.physics.addWaterLayer({ + waterY: WORLD.waterY, + deepY: WORLD.oceanFloorY ?? -50, + halfXZ: WORLD.physicsGroundHalf * 1.6 + }); + this.drc.setPhysics(this.physics); + this.walk.setPhysics?.(this.physics); + const y0 = this.islandTerrain?.sample?.(0, 0) ?? 0; + this.physics.setPlayerFeet(0, y0, 0); + } catch (err) { + console.warn('[App] Rapier init failed — kinematic fallback', err); + } - this.loading.setProgress(0.05, 'Loading environment…'); + this.loading.setProgress(0.15, 'Loading environment…'); const hdr = await assets.loadHDR(HDR_URL); await this.environment.loadEnvironment(hdr); frame.uEnvMap.value = this.environment.equirect; - this.loading.setProgress(0.5, 'Loading character…'); - await this.character.load(assets); + // Identity from URL / Foundry handoff — never hardcode "Hero" + const id = resolvePlayerIdentity(); + this._playerIdentity = id; + this.loading.setProgress( + 0.45, + `Loading ${id.raceLabel} kit…` + ); + await this.character.load(assets, { + raceId: id.raceId, + presetId: id.roleId || 'mage' + }); + // Feet on heightfield at spawn + { + const y0 = this.islandTerrain?.sample?.(0, 0) ?? 0; + this.character.placeAt?.(0, y0, 0); + this.character.resetPlacement?.(); + this.physics?.setPlayerFeet?.(0, y0, 0); + } + this.hud.setPlayerFrame?.({ + name: id.displayName, + raceId: this.character.raceId || id.raceId, + hp01: 1, + mana01: 1, + sta01: 1 + }); + this.hud.setAllies?.([]); + this.hud.setTargetFrame?.(null); + this.hud.refreshSkillLabels?.(); + this.inventory.refresh(); + if (id.source === 'url' || id.source === 'handoff') { + this.hud.showToast?.(`${id.displayName} · ${id.raceLabel}`, 2200); + } + // Prefetch master weapon skills for Showcase (non-blocking) + loadWeaponSkillsCatalog() + .then((cat) => { + console.info(`[App] weapon skills catalog v${cat.version} · ${cat.totalSkills} skills`); + const binds = loadSkillBindings(); + console.info('[App] skill bindings', binds); + }) + .catch((err) => console.warn('[App] weapon skills catalog', err)); + + // Windsurf package always available for walk mode (RideIK + deck sockets) + this._assets = assets; + this.walk.ctx.assets = assets; + this.loading.setProgress(0.7, 'Loading windsurf board…'); + try { + await this.walk.load(assets); + console.info('[App] windsurf ready=', this.walk.scooter?.ready); + } catch (err) { + console.warn('[App] ride asset load failed', err); + } + + // World drops (prefab icon + glow + model on terrain/ocean) + this.worldDrops = new WorldDrops({ + scene: this.scene, + camera: this.camera, + assets, + waterY: WORLD.waterY, + groundY: 0, + heightSample: this.terrain?.sample || null, + onToast: (m) => this.hud.showToast(m) + }); + loadPrefabCatalog() + .then((cat) => { + this._prefabCatalog = cat; + console.info(`[App] weapon prefabs ${cat.total} (icon/model presentation)`); + }) + .catch((err) => console.warn('[App] prefab catalog', err)); + + // L2 vegetation via mountTerrainLayers (one height sample — forest + grass) + if (this.islandTerrain) { + try { + const layers = mountTerrainLayers({ + scene: this.scene, + heightfield: this.islandTerrain, + forest: settings.terrain?.forestEnabled !== false, + grass: settings.terrain?.grassEnabled !== false, + onToast: (m) => this.hud.showToast(m) + }); + // heightfield mesh already added at boot — avoid double-add + this.growingForest = layers.forest; + this.stylizedGrass = layers.grass; + if (this.growingForest) { + const fs = this.growingForest.getStats?.() || { + trees: this.growingForest.trees.length + }; + console.info( + `[App] L2 harvest forest ×${fs.trees} mature=${fs.mature ?? '?'} branches=${fs.branches ?? '?'} leaves=${fs.leaves ?? '?'} · forestoutline · ${TERRAIN_LAYER.L2_VEGETATION}` + ); + } + if (this.stylizedGrass) { + console.info( + `[App] L2 grass ×${this.stylizedGrass.count} · three-stylized · layers=${layers.layerIds?.join('+')}` + ); + } + } catch (e) { + console.warn('[App] mountTerrainLayers', e); + } + } + + // Training Room · DevIsland: production load chain (storage → published → builtin) + this.loading.setProgress(0.74, 'Training Room…'); + try { + const boot = await loadTrainingRoomLayoutForPlay(); + this.mapLayoutSource = boot.source; + this.mapPublishedUrl = boot.publishedUrl; + this.worldHarvest = new DevIslandHarvest({ + scene: this.scene, + assets, + character: this.character, + combatFocus: this.combatFocus, + worldDrops: this.worldDrops, + dropBag: this.dropBag, + onToast: (m) => this.hud.showToast(m), + islandRadius: WORLD.islandRadius, + rangeM: HARVEST_RANGE_M, + heightSample: this.terrain?.sample || null + }); + // Prefer authored/published layout when present; empty nodes → default catalog + const useLayout = + boot.layout?.nodes?.length && boot.source !== 'builtin' + ? boot.layout + : boot.source === 'published' || boot.source === 'localStorage' + ? boot.layout + : null; + await this.worldHarvest.init({ layout: useLayout }); + // Enemy overhead bars (overhead_health_003 + fillers) + this.overheadBars?.bindDummies?.(this.worldHarvest.dummies || []); + const src = this.worldHarvest.layoutSource || boot.source || 'default'; + console.info( + `[App] ${TRAINING_ROOM_LABEL} · harvest=${this.worldHarvest.nodeCount} decor=${this.worldHarvest.decorCount} dummies=${this.worldHarvest.dummies?.length || 0} layout=${boot.source} F≤${HARVEST_RANGE_M}m padR=${WORLD.islandRadius.toFixed(0)}` + ); + console.info('[App] fleet deploy', fleetDeploySnapshot().authority); + this.hud.showToast?.( + `${TRAINING_ROOM_LABEL} · ${this.worldHarvest.nodeCount} nodes (${boot.source}) · /devnode.html`, + 3600 + ); + } catch (err) { + console.warn('[App] Training Room harvest failed', err); + this.worldHarvest = null; + this.hud.showToast?.('Training Room map failed — check /models/dev-island', 4000); + } + + // Horizon islands (CDN) welded to seafloorY=-5 · water surface 0 + this.loading.setProgress(0.76, 'Open sea shells…'); + try { + this.openSea = new OpenSeaShells({ scene: this.scene, assets }); + await this.openSea.init(); + } catch (err) { + console.warn('[App] OpenSeaShells', err); + this.openSea = null; + } + + // Riverside hamlet ON play island (sand/timber — not white snow GLB / missing scenery module) + // Planted on heightfield; ?town=0 disables + this.loading.setProgress(0.78, 'Island town…'); + try { + const townOff = /[?&]town=0\b/.test(location.search); + this.homeScenery = null; + if (!townOff) { + this.islandTown = new IslandTown({ + sampleHeight: (x, z) => this.terrain?.sample?.(x, z) ?? 0, + islandRadius: WORLD.islandRadius + }); + this.islandTown.build(); + this.scene.add(this.islandTown.group); + // Softer ocean near pad when town dress is present + if (this.water?.uniforms?.uStorm) { + this.water._stormTarget = Math.min(this.water._stormTarget ?? 0.35, 0.22); + } + } + } catch (err) { + console.warn('[App] IslandTown', err); + this.islandTown = null; + } + + this.generatedCatalog = null; + if (/[?&]props=1\b/.test(location.search)) { + this.loading.setProgress(0.78, 'Generated props catalog…'); + try { + const cat = await loadGeneratedCatalog(); + this.generatedCatalog = cat; + if (cat.assets?.length) { + await spawnGeneratedProp(assets, this.scene, { + name: 'rock', + position: [3.5, 0, -2] + }); + } + } catch (err) { + console.warn('[App] generated props catalog unavailable', err); + } + } this.loading.setProgress(0.85, 'Compiling shaders…'); - // Compile everything up front so the first cast never stutters. await this.renderer.gl.compileAsync(this.scene, this.camera); - this.loading.setProgress(1, 'Ready'); + // Game state: combat + physics capsule at feet + TPS shoulder camera on hero + // Feet on heightfield — never hardcode y=0 (floating / underground bug) + this.drc.setSession('combat'); + { + const ySpawn = this.islandTerrain?.sample?.(0, 0) ?? this.physics?.sampleLandY?.(0, 0) ?? 0; + this.physics?.setPlayerFeet?.(0, ySpawn, 0); + this.character.placeAt?.(0, ySpawn, 0); + this.rig.snapToCharacter?.(0, ySpawn, 0, this.character.facing); + this.rig.setAnchor(0, ySpawn, 0); + this.rig.setViewMode?.('tps'); + } + // Backflip setup: hold camera yaw (do not follow reverse body) + if (this.character?.isBackflip || this.drc?._flipHoldYaw != null) { + const hold = + this.character?._flipCameraHoldYaw ?? + this.drc?._flipHoldYaw ?? + this.character?.facing ?? + 0; + this.rig.setHoldCharacterYaw?.(hold); + } else { + this.rig.setHoldCharacterYaw?.(null); + this.rig.setCharacterYaw(this.character?.facing ?? 0); + } + + const vis = this.character._countVisibleSkinned?.() ?? -1; + console.info( + `[App] spawn visSkinned=${vis} root=`, + this.character.position.x, + this.character.position.y, + this.character.position.z, + 'height=', + this.character.height + ); + + this.loading.setProgress(1, 'Ready — Dev Island'); this.loading.hide(); + this.hud.showToast( + `Dev Island · F harvest ≤${HARVEST_RANGE_M}m (tool) · pickup · combat skill · Tab targets`, + 3800 + ); + + // Fishing profession (main-hand pole · Palworld bar · SCUM snag) + try { + this.fishing = new FishingController({ + scene: this.scene, + character: this.character, + camera: this.camera, + mouseAim: this.mouseAim, + combatFocus: this.combatFocus, + assets: this._assets || assets, + terrain: this.terrain, + waterY: WORLD.waterY, + onToast: (m) => this.hud.showToast(m), + getEquippedWeaponId: () => getEquippedWeapon?.()?.id || this.character?.weaponId || null, + getRaceId: () => + this.character?.raceId || this._playerIdentity?.raceId || null, + onCatch: (loot) => { + try { + this.inventory?.addItem?.(loot) || this.worldDrops?.spawnNearPlayer?.(loot); + } catch { + /* bag optional */ + } + this.hud.showToast?.(`Bag + ${loot.name}`, 2800); + } + }); + if (typeof window !== 'undefined') { + window.__castingFishing = this.fishing; + window.__castingApp = this; + } + console.info('[App] fishing profession ready · Shift+F · rods/tree/meals · nautical freeride'); + } catch (e) { + console.warn('[App] fishing', e); + this.fishing = null; + } + + // Gameplay / UX gates (feet · anim · shoulder TPS · skills · loco) + try { + const report = logGameplayGates(runGameplayGates(this)); + this._gameplayGates = report; + if (typeof window !== 'undefined') window.__gameplayGates = report; + if (!report.ok) { + this.hud.showToast?.(`Gates · ${report.summary}`, 4200); + } + } catch (e) { + console.warn('[App] gameplay gates', e); + } + + // Weapon equip: URL override or lab admin default — skill tree always `equipped` + { + const admin = loadLabAdmin(); + console.info(`[App] ${labAdminStatusLine()}`); + const m = location.search.match(/[?&]t0=([a-z0-9-]+)/i); + const legacy = + /[?&]sword=1\b/.test(location.search) + ? 't0-sword' + : /[?&]wand=1\b/.test(location.search) + ? 't0-wand' + : /[?&]sapling=1\b/.test(location.search) + ? 't0-nature-staff' + : null; + const t0Id = m?.[1] || legacy || (admin.admin ? admin.defaultWeaponId : null); + if (t0Id) { + try { + const id = T0_ALL_WEAPON_IDS.includes(t0Id) + ? t0Id + : t0Id.startsWith('t0-') || t0Id.length > 2 + ? t0Id + : null; + if (id) { + const result = await equipWeaponById(id, { + character: this.character, + onToast: (m) => this.hud.showToast(m) + }); + setActiveSkillTree('equipped'); + this.drc.skills = getActiveSkills(); + this.hud.refreshSkillLabels?.(); + const labels = (result.hotbar || []).map((s) => s.label).join(' · '); + const icons = (result.hotbar || []).filter((s) => s.iconUrl).length; + this.hud.showToast( + `${result.weapon?.name || id} T${result.weapon?.tier ?? 0} · ${labels} · ${icons}/${result.hotbar?.length || 0} icons`, + 4000 + ); + console.info( + `[App] equipped ${id} · tree=equipped · hotbar=${labels} · icons=${icons}` + ); + } + } catch (err) { + console.warn('[App] weapon equip', t0Id, err); + } + } + } + console.info( + `[App] casting-lab sdk ${CASTING_LAB_CONTRACT.version} · contract ${CASTING_LAB_CONTRACT.export.contractJson}` + ); this.start(); } @@ -283,34 +2270,308 @@ export class App { /* ---- simulation ---- */ this.renderer.syncSettings(); - // The editor and the preset system write `settings.mode` directly. - if (settings.mode !== this._mode) this.setMode(settings.mode); + // Editor / presets may write settings.mode + if (settings.mode !== this.session.mode) this.session.syncFromSettings(); this.environment.setFocus(this.character.position.x, this.character.position.z); this.environment.update(); - // Walk mode places the character; the controller then animates him there. - this.walk.update(dt); + + // Soft ocean wind indications (wind-attack silk + motes, nearly invisible) + { + const feet = + this.character.getWorldPosition?.(new Vector3()) || this.character.position; + const windYaw = + (settings.walk?.oceanWindYaw ?? 0.75) + + (this.walk?.active ? (this.character?.facing ?? 0) * 0.05 : 0); + this.oceanWind?.update?.(dt, feet, { + waterY: WORLD.waterY, + islandRadius: WORLD.islandRadius, + windYaw, + windSpeed: settings.walk?.oceanWindSpeed ?? 4, + enabled: settings.walk?.oceanWindIndicators !== false + }); + } + + // Mouse aim + soft lock — focus works in combat or when focus already on + const aimOn = + settings.aim?.enabled !== false && + (this.drc.inCombat || !!this.combatFocus?.focusEnabled); + const feetPos = this.character.getWorldPosition?.() || this.character.position; + if (aimOn) { + // Focus: camera-center ray → hit + soft-lock cone (auto-aim help). + // Free: NDC pointer for select / ground pick. + if (this.combatFocus?.focusEnabled) { + let softPt = this.combatFocus.getSoftLockPoint?.() || null; + // Keep soft-lock sticky: re-acquire directional best if lost + if (!softPt && settings.aim?.softLockOnFocus !== false) { + const fwd0 = this.rig.getCameraForward?.(new Vector3()); + this.combatFocus.acquireBest?.(feetPos, fwd0); + softPt = this.combatFocus.getSoftLockPoint?.() || null; + } + // Pistol / flintlock: stronger magnetic soft-lock assist (homemade inaccurate) + const pistolAim = this.character?.animPackId === 'pistol'; + this.mouseAim.updateFocusAim?.(feetPos, { + softTarget: softPt, + softBlend: pistolAim + ? (settings.aim?.pistolSoftLockBlend ?? 0.82) + : settings.aim?.softLockBlend, + maxSoftAngleDeg: pistolAim + ? (settings.aim?.pistolSoftLockMaxAngleDeg ?? 34) + : settings.aim?.softLockMaxAngleDeg + }) || this.mouseAim.updateFromCenter?.(feetPos); + // Body / launch XZ from camera look (action TPS) + this.rig?.getCameraForward?.(this.mouseAim.forward); + if (this.mouseAim.forward.lengthSq() > 1e-6) { + this.mouseAim.forward.y = 0; + if (this.mouseAim.forward.lengthSq() > 1e-6) { + this.mouseAim.forward.normalize(); + this.mouseAim.yaw = Math.atan2(this.mouseAim.forward.x, this.mouseAim.forward.z); + this.mouseAim.right.set(this.mouseAim.forward.z, 0, -this.mouseAim.forward.x); + } + } + this.mouseAim._refreshLaunch?.(feetPos); + } else { + this.mouseAim.updateFromNdc(this.input.pointer, feetPos); + if (this.combatFocus?.selectedTarget) { + this.combatFocus.resolveAimPoint(feetPos, this.mouseAim.point, this.mouseAim.point); + this.mouseAim._fromPlayer?.(feetPos); + this.mouseAim._refreshLaunch?.(feetPos); + } + } + // Ground ring ≠ screen crosshair. Only for placement / AoE / path skills. + if (this.aimMarker) { + const showGround = this._shouldShowGroundAimMarker(); + this.aimMarker.visible = showGround && !!this.mouseAim.valid; + if (this.aimMarker.visible) { + this.aimMarker.position.x = this.mouseAim.point.x; + this.aimMarker.position.z = this.mouseAim.point.z; + const gy = + this.terrain?.sample?.(this.mouseAim.point.x, this.mouseAim.point.z) ?? + this.mouseAim.point.y ?? + 0; + this.aimMarker.position.y = gy + 0.05; + const d = this.mouseAim.distanceTo(feetPos); + const s = MathUtils.clamp(0.7 + d * 0.04, 0.7, 1.6); + this.aimMarker.scale.setScalar(s); + this.aimMarker.material.opacity = this.combatFocus?.focusEnabled ? 0.95 : 0.75; + this.aimMarker.material.color?.setHex?.( + this.combatFocus?.selectedTarget ? 0xff6a55 : 0x7fd6ff + ); + } + } + // Screen-center HUD only in focus (or settings.crosshair force) — not the ground ring + const showXh = + settings.aim?.crosshair !== false && !!this.combatFocus?.focusEnabled; + this.hud.setCrosshairVisible?.(!!showXh); + const soft = !!this.combatFocus?.selectedTarget; + const pistolAim = this.character?.animPackId === 'pistol'; + // Spread opens slightly when free-running / closes when soft-lock snug + // Flintlock: slightly wider free spread (loud not accurate), tight when soft-lock + const moving = + this.drc?._grounded && + (this.input.keys.has('KeyW') || + this.input.keys.has('KeyA') || + this.input.keys.has('KeyS') || + this.input.keys.has('KeyD')); + const sprinting = !!this.drc?._sprinting; + const spread = soft + ? pistolAim + ? 0.06 + : 0.1 + : moving + ? sprinting + ? pistolAim + ? 0.48 + : 0.55 + : pistolAim + ? 0.28 + : 0.32 + : pistolAim + ? 0.18 + : 0.06; + // Range ring vs soft-lock target (animator rangeState) + let rangeState = 'none'; + if (soft && this.combatFocus?.selectedTarget?.point && feetPos) { + const tp = this.combatFocus.selectedTarget.point; + const dx = tp.x - feetPos.x; + const dz = tp.z - feetPos.z; + const dist = Math.hypot(dx, dz); + const optMin = pistolAim + ? (settings.aim?.pistolOptimalRangeMin ?? 3) + : settings.aim?.optimalRangeMin ?? 2.5; + const optMax = pistolAim + ? (settings.aim?.pistolOptimalRangeMax ?? 18) + : settings.aim?.optimalRangeMax ?? 12; + if (dist < optMin) rangeState = 'close'; + else if (dist > optMax) rangeState = 'far'; + else rangeState = 'optimal'; + } + this.hud.setCrosshairState?.({ + focus: !!this.combatFocus?.focusEnabled, + softLock: soft, + fire: !!this.drc?._cast, + spread, + rangeState + }); + this.rig?.setSprinting?.(sprinting); + // XState loco tag (anim / harvest gates read actor snapshot) + { + let loco = 'idle'; + if (this.drc?._cast) loco = 'cast'; + else if (!this.drc?._grounded) loco = 'jump'; + else if (sprinting) loco = 'sprint'; + else if (moving) loco = 'run'; + else if (this.activityMode === 'harvest' && this.worldHarvest?.lastSwingAt) { + loco = 'harvest_swing'; + } + if (this._lastLoco !== loco) { + this._lastLoco = loco; + this.activityActor?.send?.({ type: 'SET_LOCO', loco }); + } + } + this.hud.root?.classList.toggle('hud--focus', !!this.combatFocus?.focusEnabled); + this.hud.root?.classList.toggle('hud--softlock', soft); + } else { + if (this.aimMarker) this.aimMarker.visible = false; + this.hud.setCrosshairVisible?.(false); + this.hud.root?.classList.remove('hud--focus', 'hud--softlock'); + } + + // Editor may flip settings.mode / settings.drc.session — pull into session + if (settings.mode !== this.session.mode) this.session.syncFromSettings(); + + // Windsurf: keys + Space deploy (gates); camera/keys already from session.change + this.walk.setKeys?.(this.input.keys); + if (this.session.mode === INTERACTION_MODE.WALK) { + this._pollWindsurfDeploy(); + } + + // SSOT order: walk.update → character.update (mixer) → walk.applyRiderIk + if (this.session.mode === INTERACTION_MODE.WALK || this.walk.active) this.walk.update(dt); + this.drc.update(dt, this.input.keys); this.character.update(dt); + if (this.session.mode === INTERACTION_MODE.WALK || this.walk.active) { + this.walk.applyRiderIk?.(dt); + } + this.worldDrops?.update?.(dt); + this.worldHarvest?.update?.( + dt, + this.character?.position || this.character?.root?.position + ); + // Overhead nameplates project after units move + { + const cam = this.camera; + const feet = this.character?.position || this.character?.root?.position; + const w = window.innerWidth; + const h = window.innerHeight; + this.overheadBars?.update?.(cam, w, h, feet); + } + this.growingForest?.update?.(dt, this.elapsed); + this.stylizedGrass?.update?.(dt, settings.terrain?.grassWind); + // Pirate cursor intents when mouse unlocked (harvest / loot / attack) + this._updateInteractCursor?.(); + this._tickRadials?.(dt); - this.ground.update(this.elapsed); + this.ground?.update?.(this.elapsed); + this.water?.update?.(this.elapsed); this.dust.update(this.elapsed, this.character.position); - this.pathDrawer.update(raw); // the preview keeps animating while paused + this.pathDrawer.update(raw); this.abilities.update(dt); + // Linear skillshots (MOBA aim + pooled VFX) + { + const feet = this.character?.position || this.character?.root?.position; + this.linearSkills?.update?.(dt, feet, this.input.pointer); + } + + // Cast channel owns pose while bar is active; ability travel can keep cast soft-lock + const channeling = this.drc.isCasting; + const focusAbility = this.abilities.focus; + const abilityTravel = this.abilities.active.length > 0; + if (channeling) { + const st = this.drc.getCastBarState?.(); + this.character.setCasting?.(true, this.drc._cast?.aim || null); + this.hud.setCastBar?.(st); + } else if (abilityTravel && focusAbility?.position) { + this.character.setCasting?.(true); + this.hud.setCastBar?.(null); + } else { + // Do not clear cast pose mid-frame if DRC just finished (ability may start next frame) + if (!channeling) this.character.setCasting?.(false); + this.hud.setCastBar?.(null); + } + this.particles.flush(); this.decals.update(dt); this.bursts.update(dt); this.lights.update(dt); + try { + this.fishing?.update?.(dt, this.input?.keys); + } catch { + /* fishing optional */ + } - /* ---- camera ---- */ + /* ---- camera: feet + soft-lock look (production TPS angles) ---- */ + const feet = this.character.getWorldPosition?.() || this.character.position; + const px = feet.x; + const py = feet.y; + const pz = feet.z; const focus = this.abilities.focus; if (focus) this.rig.lookAt(focus.position, MathUtils.clamp(1 - focus.u * 0.4, 0, 1)); - this.rig.setAnchor(this.character.position.x, 0, this.character.position.z); + this.rig.setAnchor(px, py, pz); + // Camera yaw ownership: + // · Focus ON → mouse owns camera (characterYaw); body lag-follows look + // · Focus OFF → body facing drives orbit base (free / tank) + if (this.character?.isBackflip || this.drc?._flipHoldYaw != null) { + const hold = + this.character?._flipCameraHoldYaw ?? + this.drc?._flipHoldYaw ?? + this.character?.facing ?? + 0; + this.rig.setHoldCharacterYaw?.(hold); + } else { + this.rig.setHoldCharacterYaw?.(null); + if (!this.combatFocus?.focusEnabled) { + this.rig.setCharacterYaw(this.character?.facing ?? 0); + } + } + + // Soft lock ON in focus: frame target + subtle yaw assist (action auto-aim) + const softPtCam = this.combatFocus?.getSoftLockPoint?.(); + if (this.combatFocus?.focusEnabled && softPtCam) { + this.rig.setSoftLock?.(softPtCam, 1); + this.rig.applySoftLockYawAssist?.(dt, feet); + this.hud.root?.classList.add('hud--softlock'); + const t = this.combatFocus.selectedTarget; + if (t) { + this.hud.setTargetFrame?.({ + name: + t.mesh?.userData?.displayName || + t.mesh?.name || + t.kind || + 'Target', + hp01: Number.isFinite(t.mesh?.userData?.hp01) ? t.mesh.userData.hp01 : 1, + present: true + }); + } + } else if ( + this.combatFocus?.focusEnabled && + settings.aim?.softLockOnFocus !== false + ) { + const fwd = this.rig.getCameraForward?.(new Vector3()); + this.combatFocus.acquireBest?.(feet, fwd); + const p2 = this.combatFocus?.getSoftLockPoint?.(); + this.rig.setSoftLock?.(p2 || null, p2 ? 1 : 0); + if (p2) this.rig.applySoftLockYawAssist?.(dt, feet); + } else { + this.rig.setSoftLock?.(null, 0); + this.hud.root?.classList.remove('hud--softlock'); + } this.shake.update(raw); this.flash.update(raw); this.rig.update(raw); - this.contactShadows.setPosition(this.character.position.x, this.character.position.z); + this.contactShadows.setPosition(px, pz); this.contactShadows.render(this.scene); /* ---- render ---- */ @@ -319,10 +2580,58 @@ export class App { this.post.sync(this.elapsed, this.flash); this.post.render(); + const maxM = this.drc.maxMana || 100; + const maxS = this.drc.maxStamina || 100; + const maxH = this.drc.maxHealth || 100; + const hp01 = + this.drc.health != null + ? Math.max(0, Math.min(1, this.drc.health / maxH)) + : 1; + const id = this._playerIdentity; + const displayName = + id?.displayName || + displayNameForKit(this.character.raceId, this.character.presetId) || + 'Warlord'; + + // Live soft-lock target (was hard-null — frame lied as always empty) + let targetInfo = null; + const st = this.combatFocus?.selectedTarget; + if (st) { + const label = + st.mesh?.userData?.displayName || + st.mesh?.name || + (st.kind === 'hostile' ? 'Hostile' : st.kind) || + 'Target'; + targetInfo = { + name: label, + hp01: Number.isFinite(st.mesh?.userData?.hp01) + ? st.mesh.userData.hp01 + : 1, + present: true + }; + } + this.hud.update(raw, () => ({ particles: this.particles.countLive(this.elapsed), calls: gl.info.render.calls, - abilities: this.abilities.active.length + abilities: this.abilities.active.length, + stamina: this.drc.stamina, + mana: this.drc.mana, + castIntensity: this.drc.lastCastIntensity ?? 1, + cooldown01: (slot) => { + const skill = this.drc.skills.find((s) => s.slot === slot); + return skill ? this.drc.cooldown01(skill.id) : 0; + }, + meleeCd01: this.drc.cooldown01?.('drc_melee_strike') ?? 0, + quickCd01: (actionId) => this.drc.quickCd01?.(actionId) ?? 0, + player: { + name: displayName, + raceId: this.character.raceId || id?.raceId, + hp01, + mana01: (this.drc.mana ?? maxM) / maxM, + sta01: (this.drc.stamina ?? maxS) / maxS + }, + target: targetInfo })); } @@ -339,11 +2648,17 @@ export class App { this.lights.dispose(); this.walk.dispose(); this.character.dispose(); - this.ground.dispose(); + this.water?.dispose?.(); + this.seafloor?.dispose?.(); + this.islandTown?.dispose?.(); + this.openSea?.dispose?.(); + this.ground?.dispose?.(); + this.islandTerrain?.dispose?.(); this.dust.dispose(); this.contactShadows.dispose(); this.post.dispose(); this.environment.dispose(); + this.vfxStudio?.dispose?.(); this.editor.dispose(); this.rig.dispose(); this.renderer.dispose(); diff --git a/src/core/CameraRig.js b/src/core/CameraRig.js index 2744d7c..2ac5ad4 100644 --- a/src/core/CameraRig.js +++ b/src/core/CameraRig.js @@ -2,20 +2,24 @@ import { PerspectiveCamera, Vector3, MathUtils, MOUSE, TOUCH } from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { settings } from '../config/settings.js'; import { clamp, damp } from '../utils/math.js'; -import { LAYER } from './Layers.js'; +import { LAYER, enableMainCameraLayers } from './Layers.js'; const _dir = new Vector3(); const _desiredTarget = new Vector3(); +const _desiredPos = new Vector3(); +const _look = new Vector3(); +const _shoulder = new Vector3(); +const _soft = new Vector3(); /** - * Third-person orbit rig. + * Camera rig: orbit (sandbox) or combat TPS follow. * - * - Left mouse is reserved for drawing, so orbiting is bound to right-drag. - * - The distance always resolves back to `settings.camera.distance`, so framing - * stays consistent no matter where the orbit target drifts. The wheel zooms by - * writing that same setting, which means zoom keeps working while the rig is - * following an ability, and the editor slider stays the single source of truth. - * - The rig gently drifts its look-at point toward whatever ability is casting. + * Combat TPS angles from grudge-third-person-controller (shoulder + pitch/FOV) + * while keeping fleet rule: **OrbitControls never writes camera in TPS**. + * + * Soft lock: optional look bias toward CombatFocus.selectedTarget (not hard snap). + * + * @see docs/COMBAT_CAMERA_FOCUS_SSOT.md */ export class CameraRig { constructor(domElement) { @@ -26,48 +30,145 @@ export class CameraRig { 400 ); this.camera.position.set(-6.5, 6.0, 9.5); - this.camera.layers.enable(LAYER.VFX); + // WORLD (default) + VFX — single camera, no second combat camera + enableMainCameraLayers(this.camera); this.controls = new OrbitControls(this.camera, domElement); this.controls.enableDamping = true; this.controls.dampingFactor = 0.075; this.controls.enablePan = false; - this.controls.enableZoom = false; // the wheel drives `settings.camera.distance` instead + this.controls.enableZoom = false; this.controls.minPolarAngle = settings.camera.minPolar; this.controls.maxPolarAngle = settings.camera.maxPolar; this.controls.rotateSpeed = 0.65; - // Free the left button for path drawing. this.controls.mouseButtons = { LEFT: null, MIDDLE: null, RIGHT: MOUSE.ROTATE }; this.controls.touches = { ONE: null, TWO: TOUCH.DOLLY_ROTATE }; - this.anchor = new Vector3(0, 0, 0); // the character - this.focus = new Vector3(0, 0, 0); // point of interest (ability head) + this.anchor = new Vector3(0, 0, 0); + this.focus = new Vector3(0, 0, 0); this.focusWeight = 0; this.shakeOffset = new Vector3(); this.shakeRoll = 0; + /** Soft-lock world point (optional) */ + this.softLockPoint = null; + this.softLockWeight = 0; + + /** @type {'orbit'|'tps'} */ + this.viewMode = 'orbit'; + this.characterYaw = 0; + this._tpsYawOffset = 0; + this._tpsPitch = settings.camera.tpsDefaultPitch ?? 0.38; + this.controls.target.set(0, settings.camera.targetHeight, 0); this.controls.update(); - // Actual distance, eased toward `settings.camera.distance` so a wheel flick - // glides instead of snapping. this.distance = settings.camera.distance; + this._fov = settings.camera.fov; + /** GRUDOX animator FOV kick — extra degrees on top of sprint FOV (setFovKick) */ + this._externalFovKick = 0; + this._sprinting = false; + /** When set, TPS yaw ignores body reverse (backflip setup) */ + this._holdCharacterYaw = null; this.domElement = domElement; this._onWheel = this._onWheel.bind(this); + this._onPointerMove = this._onPointerMove.bind(this); + this._onPointerDown = this._onPointerDown.bind(this); + this._onPointerUp = this._onPointerUp.bind(this); + this._rmb = false; + this._lastX = 0; + this._lastY = 0; + /** Optional CombatFocus for RMB orbit + toggle handoff */ + this.combatFocus = null; + domElement.addEventListener('wheel', this._onWheel, { passive: false }); + window.addEventListener('pointerdown', this._onPointerDown); + window.addEventListener('pointermove', this._onPointerMove); + window.addEventListener('pointerup', this._onPointerUp); + window.addEventListener('blur', () => { + this._rmb = false; + this.combatFocus?.clearHeld?.(); + }); + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + this._rmb = false; + this.combatFocus?.clearHeld?.(); + } + }); + } + + /** + * @param {import('../combat/CombatFocus.js').CombatFocus|null} focus + */ + setCombatFocus(focus) { + this.combatFocus = focus || null; + } + + /** + * Soft-lock look target (world). Weight 0..1 from focus mode. + * @param {Vector3|null} point + * @param {number} [weight] + */ + setSoftLock(point, weight = 0) { + if (!point || weight <= 0) { + this.softLockPoint = null; + this.softLockWeight = 0; + return; + } + if (!this.softLockPoint) this.softLockPoint = new Vector3(); + this.softLockPoint.copy(point); + this.softLockWeight = MathUtils.clamp(weight, 0, 1); + } + + /** + * @param {'orbit'|'tps'} mode + */ + setViewMode(mode) { + const next = mode === 'tps' ? 'tps' : 'orbit'; + if (this.viewMode === next) return; + this.viewMode = next; + // Fleet hard rule: OrbitControls never writes camera during combat TPS + this.controls.enabled = next === 'orbit'; + if (next === 'tps') { + this._tpsYawOffset = 0; + this._tpsPitch = settings.camera.tpsDefaultPitch ?? 0.42; + // Fortnite free distance default; focus will damp to focusDistance + const freeD = settings.camera.distance ?? 6; + this.distance = Math.min(this.distance || freeD, freeD * 1.15); + if (settings.camera.fov) { + this.camera.fov = settings.camera.fov; + this._fov = settings.camera.fov; + this.camera.updateProjectionMatrix(); + } + } + } + + /** Camera yaw for body/soft-lock movement (radians). */ + getTpsYaw() { + return this.characterYaw + this._tpsYawOffset; + } + + getCameraForward(out = new Vector3()) { + this.camera.getWorldDirection(out); + out.y = 0; + if (out.lengthSq() < 1e-6) out.set(0, 0, 1); + else out.normalize(); + return out; + } + + getCameraRight(out = new Vector3()) { + this.getCameraForward(_dir); + out.set(_dir.z, 0, -_dir.x); + return out; } - /** Wheel zoom. Multiplicative, so each notch feels the same at any distance. */ _onWheel(event) { event.preventDefault(); - const cam = settings.camera; - // Firefox reports lines (deltaMode 1) and pages (2) rather than pixels. const scale = event.deltaMode === 1 ? 16 : event.deltaMode === 2 ? 100 : 1; const delta = (event.deltaY * scale) / 100; - cam.distance = clamp( cam.distance * Math.exp(delta * 0.12 * cam.zoomSpeed), cam.minDistance, @@ -75,28 +176,193 @@ export class CameraRig { ); } - /** Point the rig should orbit around (character position). */ setAnchor(x, y, z) { this.anchor.set(x, y, z); } - /** Nudge the look-at point toward an ability. `weight` 0..1, decays on its own. */ + setCharacterYaw(yaw) { + // Backflip setup: freeze body-driven cam orbit (look stays where it was) + if (this._holdCharacterYaw != null && Number.isFinite(this._holdCharacterYaw)) { + this.characterYaw = this._holdCharacterYaw; + return; + } + this.characterYaw = yaw; + } + + /** + * Hold TPS body yaw (e.g. backflip) — camera does not whip with reverse dash facing. + * @param {number|null} yaw + */ + setHoldCharacterYaw(yaw) { + this._holdCharacterYaw = Number.isFinite(yaw) ? yaw : null; + if (this._holdCharacterYaw != null) this.characterYaw = this._holdCharacterYaw; + } + lookAt(point, weight = 1) { this.focus.copy(point); this.focusWeight = Math.max(this.focusWeight, weight); } + _onPointerDown = (event) => { + if (event.button === 2) { + event.preventDefault?.(); + this.combatFocus?.onPointerDown?.(event); + } + if (this.viewMode !== 'tps') return; + if (event.button !== 2) return; + this._rmb = true; + this._lastX = event.clientX; + this._lastY = event.clientY; + }; + + _onPointerUp = (event) => { + this.combatFocus?.onPointerUp?.(event); + this._rmb = false; + }; + + /** + * TPS look: + * - Focus ON → mouse is aim always (pointer-lock movementX/Y, or client delta fallback) + * - Focus OFF → RMB hold only (orbit), unlocked cursor for select + */ + _onPointerMove = (event) => { + this.combatFocus?.onPointerMove?.(event); + if (this.viewMode !== 'tps') return; + + const focusOn = !!this.combatFocus?.focusEnabled; + const locked = document.pointerLockElement === this.domElement; + // Focus: always look with mouse. Free: only while RMB held (orbit). + if (!focusOn && !this._rmb) { + this._lastX = event.clientX; + this._lastY = event.clientY; + return; + } + + const base = settings.camera.orbitSensitivity ?? 0.0038; + const lookMul = settings.controls?.lookSensitivity ?? 1; + const sens = base * lookMul; + const invertY = !!settings.controls?.invertLookY; + let dx = 0; + let dy = 0; + if (locked) { + dx = event.movementX || 0; + dy = event.movementY || 0; + } else if (focusOn) { + // Focus without lock: client delta (movementX is often 0 when unlocked) + dx = event.movementX || event.clientX - this._lastX; + dy = event.movementY || event.clientY - this._lastY; + } else { + dx = event.clientX - this._lastX; + dy = event.clientY - this._lastY; + } + this._lastX = event.clientX; + this._lastY = event.clientY; + if (dx === 0 && dy === 0) return; + + // Focus: fold look into characterYaw so body lag and soft-lock share one orbit base + if (focusOn) { + this.characterYaw -= dx * sens; + // Keep offset near 0 while focused (camera yaw = characterYaw) + this._tpsYawOffset *= 0.85; + } else { + this._tpsYawOffset -= dx * sens; + } + const minP = settings.camera.minPitch ?? 0.08; + const maxP = settings.camera.maxPitch ?? 1.25; + const dySign = invertY ? -1 : 1; + this._tpsPitch = clamp(this._tpsPitch + dy * sens * 0.85 * dySign, minP, maxP); + }; + + /** + * Enter focus: fold yaw offset into base so look continues smoothly; enable assist. + */ + enterFocusLook() { + this.characterYaw = this.getTpsYaw(); + this._tpsYawOffset = 0; + this._lastX = 0; + this._lastY = 0; + } + + /** + * GRUDOX / DGS FOV kick — additive degrees on top of base/sprint FOV. + * @param {number} extraDeg + */ + setFovKick(extraDeg = 0) { + this._externalFovKick = Number.isFinite(extraDeg) ? extraDeg : 0; + } + + /** + * @param {boolean} on + */ + setSprinting(on) { + this._sprinting = !!on; + } + + /** + * Screen-centre aim ray (animator aimRay) — camera origin along forward. + * @returns {{ origin: Vector3, direction: Vector3 }} + */ + aimRay() { + const origin = new Vector3(); + const direction = new Vector3(); + this.camera.getWorldPosition(origin); + this.camera.getWorldDirection(direction); + direction.normalize(); + return { origin, direction }; + } + + /** + * Soft-lock yaw assist — **purged** for focus mode. + * Camera yaw is player mouse only; soft lock still biases aim point / reticle + * (CombatFocus + MouseAim), not auto camera angles. + * @param {number} _dt + * @param {import('three').Vector3} _feet + */ + applySoftLockYawAssist(_dt, _feet) { + // no-op — action-angle / auto-yaw system removed + } + update(dt) { const cam = settings.camera; - if (this.camera.fov !== cam.fov) { - this.camera.fov = cam.fov; - this.camera.updateProjectionMatrix(); + // GRUDOX animator: base FOV + sprint FOV ease (not action zoom). + if (this.viewMode === 'tps') { + const base = cam.fov ?? 70; + const sprintFov = cam.sprintFov ?? base + 8; + const wantFov = + (this._sprinting ? sprintFov : base) + (this._externalFovKick || 0); + this._fov = damp(this._fov, wantFov, cam.fovDamping ?? 0.14, dt); + if (Math.abs(this.camera.fov - this._fov) > 0.05) { + this.camera.fov = this._fov; + this.camera.updateProjectionMatrix(); + } } + + // Focus pulls distance + shoulder tighter (Fortnite 5.5 / 0.8) + const focusOn = !!this.combatFocus?.focusEnabled; + const wantDist = focusOn + ? (cam.focusDistance ?? cam.distance ?? 5.5) + : (cam.distance ?? 6); + this.distance = damp(this.distance, wantDist, cam.zoomDamping, dt); + this.focusWeight = damp(this.focusWeight, 0, 0.08, dt); + + if (this.viewMode === 'tps') { + this._updateTps(dt, cam); + } else { + this._updateOrbit(dt, cam); + } + + if (this.shakeOffset.lengthSq() > 0) { + this.camera.position.add(this.shakeOffset); + this.camera.rotateZ(this.shakeRoll); + } + } + + _updateOrbit(dt, cam) { + this.controls.enabled = true; this.controls.minPolarAngle = cam.minPolar; this.controls.maxPolarAngle = cam.maxPolar; - // Blend the orbit target between the character and any active ability. const blend = MathUtils.clamp(this.focusWeight * cam.autoFrame, 0, 0.85); _desiredTarget.copy(this.anchor); _desiredTarget.y += cam.targetHeight; @@ -108,21 +374,106 @@ export class CameraRig { damp(this.controls.target.z, _desiredTarget.z, cam.damping, dt) ); - this.focusWeight = damp(this.focusWeight, 0, 0.08, dt); - this.controls.update(); - // Enforce the orbit distance (zoom and the editor slider both land here). - this.distance = damp(this.distance, cam.distance, cam.zoomDamping, dt); _dir.copy(this.camera.position).sub(this.controls.target); const len = _dir.length() || 1; _dir.multiplyScalar(1 / len); this.camera.position.copy(this.controls.target).addScaledVector(_dir, this.distance); + } - // Camera shake is additive and applied after the controls have settled. - if (this.shakeOffset.lengthSq() > 0) { - this.camera.position.add(this.shakeOffset); - this.camera.rotateZ(this.shakeRoll); + /** + * Combat TPS: shoulder offset + spherical pitch (ref Fortnite/WoW blend). + * Soft lock only biases lookAt — never snaps camera to target. + */ + _updateTps(dt, cam, snap = false) { + this.controls.enabled = false; + + const yaw = this.characterYaw + this._tpsYawOffset; + const pitch = this._tpsPitch; + const dist = this.distance * (cam.tpsDistanceScale ?? 1); + const focusOn = !!this.combatFocus?.focusEnabled; + const shoulder = focusOn + ? (cam.focusShoulderOffset ?? cam.shoulderOffset ?? 0.8) + : (cam.shoulderOffset ?? 0.72); + + // Look target: chest + optional soft lock + ability frame + _desiredTarget.copy(this.anchor); + _desiredTarget.y += cam.targetHeight; + + // Over-the-shoulder pivot (side from settings.camera.shoulderSide) + const side = Math.sign(cam.shoulderSide ?? 1) || 1; + _shoulder.set(Math.cos(yaw) * shoulder * side, 0, -Math.sin(yaw) * shoulder * side); + _desiredTarget.add(_shoulder); + + if (this.focusWeight > 0.05) { + _desiredTarget.lerp(this.focus, Math.min(0.45, this.focusWeight * cam.autoFrame)); + } + + // Soft lock bias — stronger in focus (soft lock ON); never hard snap + // Subtle look-at pull toward target chest for framing / trajectory + const softBase = focusOn + ? (cam.softLockLookFocus ?? cam.softLockLook ?? 0.48) + : (cam.softLockLook ?? 0.22); + const softW = softBase * (this.softLockWeight || 0); + // Soft look-at bias only (mild). Auto pitch/yaw angle systems purged. + if (this.softLockPoint && softW > 0.01) { + _soft.copy(this.softLockPoint); + _desiredTarget.lerp(_soft, softW); + } + + // Spherical offset behind shoulder pivot (ref grudge-third-person-controller) + const cosP = Math.cos(pitch); + const sinP = Math.sin(pitch); + _desiredPos.set( + _desiredTarget.x - Math.sin(yaw) * dist * cosP, + _desiredTarget.y + Math.sin(pitch) * dist * 0.92 + cam.targetHeight * 0.15, + _desiredTarget.z - Math.cos(yaw) * dist * cosP + ); + + if (snap || dt <= 0) { + this.camera.position.copy(_desiredPos); + } else { + const dampT = cam.tpsDamping ?? 0.16; + this.camera.position.x = damp(this.camera.position.x, _desiredPos.x, dampT, dt); + this.camera.position.y = damp(this.camera.position.y, _desiredPos.y, dampT, dt); + this.camera.position.z = damp(this.camera.position.z, _desiredPos.z, dampT, dt); + } + + _look.copy(_desiredTarget); + this.camera.lookAt(_look); + this.controls.target.copy(_look); + } + + /** + * Snap TPS shoulder rig to character (spawn / combat enter). + * Uses free distance unless focus already ON → focusDistance (Fortnite 5.5). + */ + snapToCharacter(x, y, z, yaw = 0) { + this.setAnchor(x, y ?? 0, z); + this.setCharacterYaw(yaw); + this.setViewMode('tps'); + const cam = settings.camera; + const focusOn = !!this.combatFocus?.focusEnabled; + this.distance = focusOn + ? (cam.focusDistance ?? 5.5) + : (cam.distance ?? 6.0); + this._tpsPitch = cam.tpsDefaultPitch ?? 0.42; + this._tpsYawOffset = 0; + this._updateTps(0, cam, true); + } + + /** + * Builder / equip: orbit allowed. Combat / freeride: always TPS shoulder. + * @param {'combat'|'equip'|'builder'|'freeride'} mode + */ + applyGameplayMode(mode) { + if (mode === 'combat' || mode === 'freeride') { + this.setViewMode('tps'); + } else if (mode === 'equip' || mode === 'builder') { + // Builder review — orbit; focus toggle still forces TPS via CombatFocus + if (!this.combatFocus?.focusEnabled) this.setViewMode('orbit'); + else this.setViewMode('tps'); } } @@ -133,6 +484,9 @@ export class CameraRig { dispose() { this.domElement.removeEventListener('wheel', this._onWheel); + window.removeEventListener('pointerdown', this._onPointerDown); + window.removeEventListener('pointermove', this._onPointerMove); + window.removeEventListener('pointerup', this._onPointerUp); this.controls.dispose(); } } diff --git a/src/core/Layers.js b/src/core/Layers.js index b40b89a..344aa24 100644 --- a/src/core/Layers.js +++ b/src/core/Layers.js @@ -1,13 +1,21 @@ /** - * Render layers. + * Three.js **render** layers (camera mask bits). One system only. * - * WORLD — opaque environment + character. Written to the depth prepass, - * receives shadows, is what soft particles fade against. - * VFX — every transparent ability mesh and particle system. - * DISTORTION — invisible-to-the-main-pass proxies that write screen-space UV - * offsets for heat shimmer / water refraction. - * CONTACT — additional layer flag on the character only, so the contact - * shadow pass captures it without also capturing grass or VFX. + * | Bit | Name | Who | + * |-----|-------------|-----| + * | 0 | WORLD | Terrain, water, props, character body | + * | 1 | VFX | Abilities, particles, trails | + * | 2 | DISTORTION | Heat/refraction proxies (distortion prepass only) | + * | 3 | CONTACT | Character flag for contact-shadow pass | + * + * Camera default sees 0; CameraRig also enables VFX (1). + * Character = WORLD + CONTACT (mask 9). Depth prepass = WORLD only. + * + * **Not the same as** `TERRAIN_LAYER` L0–L3 in `terrainLayers.js` + * (authoring labels: height / surface / vegetation / detail — not camera bits). + * **Not** HTML dummy overhead bars (`OverheadNameplates`) — those are DOM, not layers. + * + * Do not invent a second Layer enum or dual cameras for combat. */ export const LAYER = Object.freeze({ WORLD: 0, @@ -21,3 +29,11 @@ export function setLayerRecursive(object, layer) { object.traverse((node) => node.layers.set(layer)); return object; } + +/** Enable WORLD+VFX on a camera (main combat / lab view). */ +export function enableMainCameraLayers(camera) { + if (!camera?.layers) return camera; + camera.layers.enable(LAYER.WORLD); + camera.layers.enable(LAYER.VFX); + return camera; +} diff --git a/src/core/Renderer.js b/src/core/Renderer.js index dc633e8..e85fd65 100644 --- a/src/core/Renderer.js +++ b/src/core/Renderer.js @@ -1,6 +1,6 @@ import { WebGLRenderer, - PCFSoftShadowMap, + PCFShadowMap, ACESFilmicToneMapping, SRGBColorSpace } from 'three'; @@ -24,7 +24,8 @@ export class Renderer { this.gl.setSize(window.innerWidth, window.innerHeight, false); this.gl.shadowMap.enabled = true; - this.gl.shadowMap.type = PCFSoftShadowMap; + // three r185+: PCFSoftShadowMap deprecated → PCFShadowMap + this.gl.shadowMap.type = PCFShadowMap; // The frame renders the scene several times (depth prepass, distortion, // contact shadows, main pass). Automatic updates would rebuild the cascade // shadow maps for every one of them, so the app flags a single update per diff --git a/src/core/SessionState.js b/src/core/SessionState.js new file mode 100644 index 0000000..6fae229 --- /dev/null +++ b/src/core/SessionState.js @@ -0,0 +1,289 @@ +import { EventEmitter } from '../utils/EventEmitter.js'; +import { settings, ELEMENTS, MODES } from '../config/settings.js'; + +/** + * Lab session orchestration SSOT. + * + * **Two layers (do not collapse):** + * | Layer | Owns | File | + * |-------|------|------| + * | **Tweaks** | Numbers, colors, speeds — editor binds live | `settings.js` | + * | **Session** | Who owns input, path meaning, camera, ride | **this file** | + * + * **Pattern (consistent usage):** + * 1. Controllers own *local* phase machines (Walk leap/ride, Ability travel/impact). + * 2. Controllers **report** phase / session to `SessionState` via setters. + * 3. App applies *side effects* once in `on('change')` (camera, inventory, HUD). + * 4. Systems **read gates** (`session.gates.*`) instead of ad-hoc `if (ride && combat)`. + * 5. Never invent a second store for mode/session/ride — extend this. + * + * settings.mode / settings.drc.session are **mirrors** for lil-gui + presets. + * + * @see docs/SESSION_STATE_SSOT.md + */ + +/** @typedef {'casting'|'walk'} InteractionMode */ +/** @typedef {'combat'|'equip'} DrcSession */ +/** @typedef {'idle'|'leap'|'ride'|'freeride'|'dismount'} RidePhase */ + +export const INTERACTION_MODE = Object.freeze({ + CASTING: 'casting', + WALK: 'walk' +}); + +export const DRC_SESSION = Object.freeze({ + COMBAT: 'combat', + EQUIP: 'equip' +}); + +/** Mirrors WalkController Phase */ +export const RIDE_PHASE = Object.freeze({ + IDLE: 'idle', + LEAP: 'leap', + RIDE: 'ride', + FREERIDE: 'freeride', + DISMOUNT: 'dismount' +}); + +/** + * @typedef {object} SessionSnapshot + * @property {InteractionMode} mode + * @property {DrcSession} drc + * @property {RidePhase} ridePhase + * @property {string} element + * @property {boolean} paused + * @property {boolean} riding + * @property {boolean} freeriding + * @property {import('./SessionState.js').SessionGates} gates + */ + +/** + * @typedef {object} SessionGates + * @property {boolean} landLoco WASD land combat locomotion + * @property {boolean} combatSkills 1–4 / F skills + * @property {boolean} pathIsCast stroke → AbilityManager + * @property {boolean} pathIsRide stroke → WalkController + * @property {boolean} freerideDeploy Space deploy windsurf in walk mode + * @property {boolean} rideParented character under board seat + * @property {boolean} combatKeys Danger Room combat hotkeys + * @property {boolean} tpsCamera third-person follow + * @property {boolean} orbitCamera free orbit (equip / path draw) + * @property {boolean} inventoryOk equip panel allowed + */ + +export class SessionState extends EventEmitter { + constructor() { + super(); + /** @type {InteractionMode} */ + this._mode = MODES.includes(settings.mode) ? settings.mode : INTERACTION_MODE.CASTING; + /** @type {DrcSession} */ + this._drc = settings.drc?.session === 'equip' ? DRC_SESSION.EQUIP : DRC_SESSION.COMBAT; + /** @type {RidePhase} */ + this._ridePhase = RIDE_PHASE.IDLE; + /** @type {string} */ + this._element = ELEMENTS[0] || 'fire'; + this._paused = false; + /** @type {SessionSnapshot|null} */ + this._lastSnap = null; + } + + /* ── raw fields ───────────────────────────────────────────── */ + + get mode() { + return this._mode; + } + get drc() { + return this._drc; + } + get ridePhase() { + return this._ridePhase; + } + get element() { + return this._element; + } + get paused() { + return this._paused; + } + + get riding() { + return this._ridePhase !== RIDE_PHASE.IDLE; + } + + get freeriding() { + return this._ridePhase === RIDE_PHASE.FREERIDE; + } + + get inCombat() { + return this._drc === DRC_SESSION.COMBAT; + } + + /* ── capability gates (read these everywhere) ─────────────── */ + + /** + * Derived permissions from mode × drc × ride. + * @returns {SessionGates} + */ + get gates() { + const walk = this._mode === INTERACTION_MODE.WALK; + const combat = this._drc === DRC_SESSION.COMBAT; + const equip = this._drc === DRC_SESSION.EQUIP; + const riding = this.riding; + const freeride = this.freeriding; + const leapOrRide = + this._ridePhase === RIDE_PHASE.LEAP || + this._ridePhase === RIDE_PHASE.RIDE || + this._ridePhase === RIDE_PHASE.FREERIDE; + const skillsWhileRide = settings.walk?.skillsWhileRide !== false; + + return { + landLoco: combat && !walk && !riding, + combatSkills: combat && (!riding || (skillsWhileRide && freeride) || (skillsWhileRide && leapOrRide)), + // Path draw: cast on land casting OR freeride ranged; ride course only walk + not mounted + pathIsCast: !walk || freeride, + pathIsRide: walk && !riding, + freerideDeploy: walk && !riding, + rideParented: leapOrRide, + combatKeys: combat, + /** Freeride path cast without focus lock (ranged/staff) */ + freerideRangedCast: freeride && skillsWhileRide && settings.walk?.freerideRangedCast !== false, + // TPS: combat land OR freeride; orbit: equip or walk path-draw idle + tpsCamera: (combat && !walk) || freeride || (walk && riding && this._ridePhase !== RIDE_PHASE.LEAP), + orbitCamera: equip || (walk && !riding), + inventoryOk: equip && !riding + }; + } + + /* ── setters (only path that mutates + emits) ─────────────── */ + + /** + * @param {InteractionMode|string} mode + * @param {{ silent?: boolean }} [opts] + */ + setMode(mode, opts = {}) { + const next = MODES.includes(mode) ? mode : INTERACTION_MODE.CASTING; + if (next === this._mode) return false; + this._mode = next; + settings.mode = next; + // Leaving walk ends ride report (controller still cancel() from App) + if (next !== INTERACTION_MODE.WALK && this._ridePhase !== RIDE_PHASE.IDLE) { + this._ridePhase = RIDE_PHASE.IDLE; + } + if (!opts.silent) this._emitChange('mode'); + return true; + } + + /** + * @param {DrcSession|string} session + * @param {{ silent?: boolean }} [opts] + */ + setDrc(session, opts = {}) { + const next = session === DRC_SESSION.EQUIP ? DRC_SESSION.EQUIP : DRC_SESSION.COMBAT; + if (next === this._drc) return false; + this._drc = next; + settings.drc = settings.drc || {}; + settings.drc.session = next; + // Equip cancels ride intent + if (next === DRC_SESSION.EQUIP && this._ridePhase !== RIDE_PHASE.IDLE) { + this._ridePhase = RIDE_PHASE.IDLE; + } + if (!opts.silent) this._emitChange('drc'); + return true; + } + + /** + * WalkController reports phase here — does not cancel ride itself. + * @param {RidePhase|string} phase + * @param {{ silent?: boolean }} [opts] + */ + setRidePhase(phase, opts = {}) { + const allowed = Object.values(RIDE_PHASE); + const next = allowed.includes(phase) ? phase : RIDE_PHASE.IDLE; + if (next === this._ridePhase) return false; + this._ridePhase = next; + if (!opts.silent) this._emitChange('ridePhase'); + return true; + } + + /** + * @param {string} element + * @param {{ silent?: boolean }} [opts] + */ + setElement(element, opts = {}) { + const next = ELEMENTS.includes(element) ? element : ELEMENTS[0]; + if (next === this._element) return false; + this._element = next; + if (!opts.silent) this._emitChange('element'); + return true; + } + + /** + * @param {boolean} on + * @param {{ silent?: boolean }} [opts] + */ + setPaused(on, opts = {}) { + const next = !!on; + if (next === this._paused) return false; + this._paused = next; + if (!opts.silent) this._emitChange('paused'); + return true; + } + + /** Pull mode/session from settings after preset import (editor). */ + syncFromSettings() { + let dirty = false; + if (MODES.includes(settings.mode) && settings.mode !== this._mode) { + this._mode = settings.mode; + dirty = true; + } + const want = settings.drc?.session === 'equip' ? DRC_SESSION.EQUIP : DRC_SESSION.COMBAT; + if (want !== this._drc) { + this._drc = want; + dirty = true; + } + if (dirty) this._emitChange('sync'); + } + + /** Full snapshot for HUD / debug / agents. */ + snapshot() { + return { + mode: this._mode, + drc: this._drc, + ridePhase: this._ridePhase, + element: this._element, + paused: this._paused, + riding: this.riding, + freeriding: this.freeriding, + gates: this.gates + }; + } + + /** + * Human blurb for title strip. + * @returns {string} + */ + blurb() { + const g = this.gates; + if (this._mode === INTERACTION_MODE.WALK) { + if (this.freeriding) return 'Windsurf vehicle · WASD · E get off · Space hop'; + if (this.riding) return `Windsurf · ${this._ridePhase} · E get off`; + return 'Surf · Space deploy · draw path = course'; + } + if (this._drc === DRC_SESSION.EQUIP) return 'Equip / Lab · race · mesh · packs'; + return 'Aim · WASD · 1–4 staff skills · LMB path cast'; + } + + /* ── private ──────────────────────────────────────────────── */ + + _emitChange(reason) { + const snap = this.snapshot(); + const prev = this._lastSnap; + this._lastSnap = snap; + this.emit('change', snap, prev, reason); + this.emit(reason, snap, prev); + } +} + +/** Singleton optional — App owns instance; export factory for tests. */ +export function createSessionState() { + return new SessionState(); +} diff --git a/src/core/gameplayGates.js b/src/core/gameplayGates.js new file mode 100644 index 0000000..b3afc8d --- /dev/null +++ b/src/core/gameplayGates.js @@ -0,0 +1,158 @@ +/** + * Gameplay + UX gate checks — Casting lab production readiness. + * + * Run after boot (window.app) or in CI smoke. Does not invent systems — + * only verifies feet, anim state, camera, weapons, locomotion hooks. + * + * @see docs/GAMEPLAY_UX_GATES_SSOT.md + */ + +import { settings as _settings } from '../config/settings.js'; + +/** + * @typedef {{ + * id: string, + * ok: boolean, + * detail?: string + * }} GateResult + */ + +/** + * @param {import('./App.js').App} app + * @returns {{ ok: boolean, gates: GateResult[], summary: string }} + */ +export function runGameplayGates(app) { + /** @type {GateResult[]} */ + const gates = []; + const push = (id, ok, detail) => gates.push({ id, ok: !!ok, detail }); + + if (!app) { + return { ok: false, gates: [{ id: 'app', ok: false, detail: 'no app' }], summary: 'FAIL no app' }; + } + + const ch = app.character; + const rig = app.rig; + const drc = app.drc; + const phys = app.physics; + const terrain = app.terrain || app.islandTerrain; + + // —— Feet on ground —— + const feet = ch?.getWorldPosition?.() || ch?.position; + const sampleY = + typeof terrain?.sample === 'function' && feet + ? terrain.sample(feet.x, feet.z) + : typeof phys?.sampleLandY === 'function' && feet + ? phys.sampleLandY(feet.x, feet.z) + : null; + const feetY = feet?.y ?? 0; + const feetDelta = + sampleY != null && Number.isFinite(sampleY) ? Math.abs(feetY - sampleY) : null; + push( + 'feet_grounded', + feetDelta == null || feetDelta < 0.35, + feetDelta == null + ? `y=${feetY.toFixed(2)} (no sample)` + : `Δy=${feetDelta.toFixed(3)} sample=${sampleY.toFixed(2)} feet=${feetY.toFixed(2)}` + ); + + const look = ch?.diagnoseLook?.(); + push( + 'feet_mesh_ground', + !look || look.feetOk !== false, + look ? `feetMinY=${look.feetMinY} height=${look.heightM}` : 'no diagnose' + ); + + // —— Anim state —— + const animState = ch?.animState || 'unknown'; + const gaitLocked = !!ch?._gaitLocked; + const hasMixer = !!ch?.mixer; + const hasIdle = ch?.actions?.has?.('idle') || ch?.actions?.has?.('standing-idle'); + push('anim_mixer', hasMixer, hasMixer ? 'one mixer' : 'missing mixer'); + push( + 'anim_state', + typeof animState === 'string' && animState.length > 0, + `state=${animState} gaitLocked=${gaitLocked}` + ); + push('anim_idle_clip', !!hasIdle, hasIdle ? 'idle present' : 'no idle action'); + + // —— Camera shoulder TPS —— + const viewMode = rig?.viewMode; + const inCombat = !!drc?.inCombat; + const focusOn = !!app.combatFocus?.focusEnabled; + const dist = rig?.distance ?? settingsSafe().camera?.distance; + const orbitEnabled = !!rig?.controls?.enabled; + push( + 'camera_tps_combat', + !inCombat || viewMode === 'tps', + `view=${viewMode} combat=${inCombat}` + ); + push( + 'camera_orbit_off_in_tps', + viewMode !== 'tps' || !orbitEnabled, + `orbit.enabled=${orbitEnabled}` + ); + push( + 'camera_fortnite_distance', + dist != null && dist >= 2.5 && dist <= 10, + `distance=${Number(dist).toFixed(2)} (want 5.5–6 free/focus, not map-scale 14)` + ); + push( + 'camera_single', + !!rig?.camera && !app.secondCamera, + 'one PerspectiveCamera' + ); + + // —— Weapon skills —— + const skills = drc?.skills; + const skillCount = Array.isArray(skills) ? skills.length : 0; + push('weapon_skills_bar', skillCount > 0, `skills=${skillCount}`); + const pack = ch?.animPackId || ch?.weaponHoldKind || 'none'; + push('weapon_pack', true, `pack=${pack}`); + + // —— Locomotion / traversal hooks —— + push('loco_input', !!app.input, 'InputManager'); + push('loco_drc', !!drc, 'DrcCombatController'); + push( + 'loco_physics', + !!phys?.ready || !!phys?.playerBody, + phys?.ready ? 'Rapier CCT' : 'physics pending/fallback' + ); + push( + 'traversal_mobility', + typeof drc?.parry === 'function', + typeof drc?.parry === 'function' ? 'parry/dodge/slide stack' : 'missing mobility API' + ); + + // —— UX / HUD —— + push('ux_hud', !!app.hud, 'HUD mounted'); + push( + 'ux_focus_crosshair', + typeof app.hud?.setCrosshairVisible === 'function', + 'crosshair API' + ); + + const failed = gates.filter((g) => !g.ok); + const ok = failed.length === 0; + const summary = ok + ? `PASS ${gates.length} gates` + : `FAIL ${failed.map((f) => f.id).join(', ')}`; + + return { ok, gates, summary }; +} + +function settingsSafe() { + return _settings || {}; +} + +/** + * Console-friendly report. + * @param {ReturnType} report + */ +export function logGameplayGates(report) { + const tag = report.ok ? '[gates] ✓' : '[gates] ✗'; + console.info(tag, report.summary); + for (const g of report.gates) { + console.info(` ${g.ok ? '✓' : '✗'} ${g.id}`, g.detail || ''); + } + return report; +} diff --git a/src/devnode/DevNodeEditor.js b/src/devnode/DevNodeEditor.js new file mode 100644 index 0000000..d78f7be --- /dev/null +++ b/src/devnode/DevNodeEditor.js @@ -0,0 +1,344 @@ +/** + * DevNode 3D viewport — place palette nodes on heightfield. + * Preview only (no full combat/harvest runtime). + */ + +import { + AmbientLight, + BoxGeometry, + Color, + ConeGeometry, + CylinderGeometry, + DirectionalLight, + GridHelper, + Group, + Mesh, + MeshStandardMaterial, + PerspectiveCamera, + MOUSE, + Raycaster, + Scene, + SphereGeometry, + Vector2, + Vector3, + WebGLRenderer +} from 'three'; +import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; +import { IslandHeightfield } from '../world/IslandHeightfield.js'; +import { projectToTerrain, terrainHandle } from '../world/terrainGround.js'; +import { + createEmptyNodeLayout, + paletteEntry +} from '../world/nodePalette.js'; +import { sharedGltfLoader } from '../loaders/gltfPipeline.js'; + +const _ndc = new Vector2(); +const _hit = new Vector3(); + +export class DevNodeEditor { + /** + * @param {HTMLCanvasElement} canvas + */ + constructor(canvas) { + this.canvas = canvas; + this.scene = new Scene(); + this.scene.background = new Color(0x0a1218); + + this.camera = new PerspectiveCamera(55, 1, 0.1, 400); + this.camera.position.set(18, 14, 22); + + this.renderer = new WebGLRenderer({ canvas, antialias: true }); + this.renderer.setPixelRatio(Math.min(2, window.devicePixelRatio || 1)); + + this.controls = new OrbitControls(this.camera, canvas); + this.controls.enableDamping = true; + this.controls.target.set(0, 0.5, 0); + // LMB place · MMB orbit · RMB remove (not orbit) + this.controls.mouseButtons = { + LEFT: null, + MIDDLE: MOUSE.ROTATE, + RIGHT: null + }; + this.controls.enablePan = true; + + this.scene.add(new AmbientLight(0xb0c4d8, 0.55)); + const sun = new DirectionalLight(0xfff2dd, 1.1); + sun.position.set(12, 22, 8); + this.scene.add(sun); + + this.terrain = new IslandHeightfield({ amp: 0.75, flatCore: 9 }); + this.scene.add(this.terrain.mesh); + this.terrainHandle = terrainHandle(this.terrain); + + const grid = new GridHelper(80, 40, 0x2a4050, 0x1a2830); + grid.position.y = 0.02; + this.scene.add(grid); + + this.nodeGroup = new Group(); + this.nodeGroup.name = 'DevNodePlaced'; + this.scene.add(this.nodeGroup); + + this.raycaster = new Raycaster(); + this.layout = createEmptyNodeLayout(); + /** @type {Map} */ + this.markers = new Map(); + this.selectedPaletteId = 'node.rock_boulder'; + this.placeMode = true; + this._meshCache = new Map(); + + this._onResize = () => this.resize(); + window.addEventListener('resize', this._onResize); + canvas.addEventListener('pointerdown', (e) => this._onPointer(e)); + this.resize(); + this._raf = 0; + this._loop = this._loop.bind(this); + this._loop(); + } + + resize() { + const w = this.canvas.clientWidth || this.canvas.parentElement?.clientWidth || 800; + const h = this.canvas.clientHeight || this.canvas.parentElement?.clientHeight || 600; + this.camera.aspect = w / Math.max(1, h); + this.camera.updateProjectionMatrix(); + this.renderer.setSize(w, h, false); + } + + setBiomeTerrain(terrainOpts) { + if (!terrainOpts) return; + // Rebuild terrain with biome knobs (simple — dispose old mesh) + this.scene.remove(this.terrain.mesh); + this.terrain.dispose(); + this.terrain = new IslandHeightfield({ + seed: terrainOpts.seed ?? 17, + amp: terrainOpts.amp ?? 0.75, + flatCore: terrainOpts.flatCore ?? 9 + }); + this.scene.add(this.terrain.mesh); + this.terrainHandle = terrainHandle(this.terrain); + this.layout.terrain = { ...terrainOpts }; + this._reliftAll(); + } + + setSelectedPalette(id) { + this.selectedPaletteId = id; + } + + get nodeCount() { + return this.layout.nodes.length; + } + + clearNodes() { + for (const m of this.markers.values()) { + this.nodeGroup.remove(m); + } + this.markers.clear(); + this.layout.nodes = []; + } + + /** + * @param {object} layout + */ + loadLayout(layout) { + this.clearNodes(); + this.layout = { + ...createEmptyNodeLayout(layout.biomeId), + ...layout, + nodes: [] + }; + if (layout.terrain) this.setBiomeTerrain(layout.terrain); + for (const n of layout.nodes || []) { + this._addNode(n.paletteId, n.x, n.z, { + yaw: n.yaw, + scale: n.scale, + id: n.id + }); + } + } + + exportLayout() { + return { + ...this.layout, + mapId: this.layout.mapId || 'training_room', + mapLabel: this.layout.mapLabel || 'Training Room · DevIsland', + nodes: this.layout.nodes.map((n) => ({ ...n })), + exportedAt: new Date().toISOString() + }; + } + + _reliftAll() { + for (const n of this.layout.nodes) { + const y = this.terrain.sample(n.x, n.z); + n.y = y; + const m = this.markers.get(n.id); + if (m) m.position.set(n.x, y, n.z); + } + } + + _onPointer(e) { + if (e.button === 2) { + e.preventDefault(); + this._removeNearest(e); + return; + } + if (e.button !== 0) return; + // Shift+LMB = orbit only (don't place) + if (e.shiftKey) return; + + const rect = this.canvas.getBoundingClientRect(); + _ndc.set( + ((e.clientX - rect.left) / rect.width) * 2 - 1, + -((e.clientY - rect.top) / rect.height) * 2 + 1 + ); + this.raycaster.setFromCamera(_ndc, this.camera); + if (!projectToTerrain(this.raycaster, _hit, this.terrainHandle)) return; + this._addNode(this.selectedPaletteId, _hit.x, _hit.z); + } + + _removeNearest(e) { + const rect = this.canvas.getBoundingClientRect(); + _ndc.set( + ((e.clientX - rect.left) / rect.width) * 2 - 1, + -((e.clientY - rect.top) / rect.height) * 2 + 1 + ); + this.raycaster.setFromCamera(_ndc, this.camera); + if (!projectToTerrain(this.raycaster, _hit, this.terrainHandle)) return; + let best = null; + let bestD = 2.5 * 2.5; + for (const n of this.layout.nodes) { + const d = (n.x - _hit.x) ** 2 + (n.z - _hit.z) ** 2; + if (d < bestD) { + bestD = d; + best = n; + } + } + if (!best) return; + const m = this.markers.get(best.id); + if (m) this.nodeGroup.remove(m); + this.markers.delete(best.id); + this.layout.nodes = this.layout.nodes.filter((n) => n.id !== best.id); + } + + /** + * @param {string} paletteId + * @param {number} x + * @param {number} z + * @param {{ yaw?: number, scale?: number, id?: string }} [opts] + */ + async _addNode(paletteId, x, z, opts = {}) { + const entry = paletteEntry(paletteId); + if (!entry) return; + const id = opts.id || `n_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 6)}`; + const y = this.terrain.sample(x, z); + const yaw = opts.yaw ?? Math.random() * Math.PI * 2; + const scale = opts.scale ?? entry.defaultScale ?? 1; + + const marker = await this._makeMarker(entry, scale); + marker.position.set(x, y, z); + marker.rotation.y = yaw; + marker.userData.nodeId = id; + this.nodeGroup.add(marker); + this.markers.set(id, marker); + + this.layout.nodes.push({ + id, + paletteId, + x, + z, + y, + yaw, + scale + }); + } + + /** + * @param {import('../world/nodePalette.js').NodePaletteEntry} entry + * @param {number} scale + */ + async _makeMarker(entry, scale) { + const tint = new Color(entry.tint || '#88aacc'); + // Live mesh from pool + if (entry.meshPool?.length) { + const url = entry.meshPool[0]; + try { + let src = this._meshCache.get(url); + if (!src) { + const gltf = await sharedGltfLoader().loadAsync(url); + src = gltf.scene; + this._meshCache.set(url, src); + } + const g = src.clone(true); + g.scale.setScalar(scale); + g.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.receiveShadow = true; + } + }); + return g; + } catch { + /* fall through stub */ + } + } + // Procedural / creature stubs + if (entry.family === 'tree') { + const g = new Group(); + const trunk = new Mesh( + new CylinderGeometry(0.12 * scale, 0.22 * scale, 1.2 * scale, 6), + new MeshStandardMaterial({ color: 0x4a3220, roughness: 1 }) + ); + trunk.position.y = 0.6 * scale; + const crown = new Mesh( + new ConeGeometry(0.55 * scale, 1.2 * scale, 7), + new MeshStandardMaterial({ color: tint, roughness: 0.9 }) + ); + crown.position.y = 1.5 * scale; + g.add(trunk, crown); + return g; + } + if (entry.family === 'flower' || entry.family === 'hemp' || entry.family === 'herb') { + const g = new Group(); + const stem = new Mesh( + new CylinderGeometry(0.02, 0.03, 0.35 * scale, 4), + new MeshStandardMaterial({ color: 0x3a6a30 }) + ); + stem.position.y = 0.18 * scale; + const head = new Mesh( + new SphereGeometry(0.12 * scale, 8, 6), + new MeshStandardMaterial({ color: tint }) + ); + head.position.y = 0.38 * scale; + g.add(stem, head); + return g; + } + if (entry.family === 'animal_passive' || entry.family === 'pve_mob') { + const body = new Mesh( + new BoxGeometry(0.5 * scale, 0.7 * scale, 0.35 * scale), + new MeshStandardMaterial({ color: tint, roughness: 0.85 }) + ); + body.position.y = 0.35 * scale; + return body; + } + // Default rock-ish stub + const rock = new Mesh( + new SphereGeometry(0.35 * scale, 7, 5), + new MeshStandardMaterial({ color: tint, roughness: 0.95 }) + ); + rock.position.y = 0.25 * scale; + rock.scale.set(1.2, 0.7, 1); + return rock; + } + + _loop() { + this._raf = requestAnimationFrame(this._loop); + this.controls.update(); + this.renderer.render(this.scene, this.camera); + } + + dispose() { + cancelAnimationFrame(this._raf); + window.removeEventListener('resize', this._onResize); + this.controls.dispose(); + this.renderer.dispose(); + this.terrain.dispose(); + } +} diff --git a/src/devnode/devnode.css b/src/devnode/devnode.css new file mode 100644 index 0000000..6591781 --- /dev/null +++ b/src/devnode/devnode.css @@ -0,0 +1,165 @@ +/* DevNode — compact authoring chrome (not a second CraftPix HUD) */ +* { + box-sizing: border-box; +} +html, +body { + margin: 0; + height: 100%; + background: #0c1014; + color: #d8e2ec; + font-family: 'Segoe UI', system-ui, sans-serif; + overflow: hidden; +} +#devnode-app { + display: grid; + grid-template-columns: 280px 1fr; + height: 100%; +} +.dn-panel { + background: #121820; + border-right: 1px solid #243040; + padding: 12px 14px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 8px; +} +.dn-head h1 { + margin: 0; + font-size: 1.15rem; + letter-spacing: 0.04em; +} +.dn-sub { + margin: 2px 0 6px; + font-size: 0.72rem; + opacity: 0.65; +} +.dn-link { + color: #7ec8ff; + font-size: 0.8rem; + text-decoration: none; +} +.dn-link:hover { + text-decoration: underline; +} +.dn-label { + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.08em; + opacity: 0.55; + margin-top: 6px; +} +.dn-select { + width: 100%; + background: #0e141c; + color: #e8f0f8; + border: 1px solid #2a3a4c; + border-radius: 6px; + padding: 6px 8px; +} +.dn-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; +} +.dn-chip { + border: 1px solid #2a3a4c; + background: #0e141c; + color: #c0d0e0; + border-radius: 999px; + padding: 3px 8px; + font-size: 0.68rem; + cursor: pointer; +} +.dn-chip.is-on { + border-color: #4a9ad4; + background: #1a3048; + color: #e8f4ff; +} +.dn-palette { + display: flex; + flex-direction: column; + gap: 4px; + max-height: 42vh; + overflow: auto; +} +.dn-item { + text-align: left; + border: 1px solid #243040; + background: #0e141c; + color: #d0dce8; + border-radius: 6px; + padding: 6px 8px; + cursor: pointer; + font-size: 0.78rem; +} +.dn-item small { + display: block; + opacity: 0.5; + font-size: 0.65rem; + margin-top: 2px; +} +.dn-item.is-sel { + border-color: #5ab0e8; + box-shadow: 0 0 0 1px #5ab0e855; +} +.dn-item.is-stub { + opacity: 0.7; + border-style: dashed; +} +.dn-actions { + display: flex; + flex-direction: column; + gap: 6px; + margin-top: 8px; +} +.dn-btn { + border: none; + border-radius: 6px; + padding: 8px 10px; + background: #2a6ea8; + color: #fff; + font-weight: 600; + cursor: pointer; + font-size: 0.8rem; +} +.dn-btn--ghost { + background: transparent; + border: 1px solid #2a3a4c; + color: #b0c4d8; +} +.dn-btn:hover { + filter: brightness(1.08); +} +.dn-hint { + font-size: 0.68rem; + opacity: 0.55; + line-height: 1.4; + margin: 4px 0 0; +} +.dn-stats { + font-size: 0.65rem; + opacity: 0.7; + background: #0a0e14; + padding: 6px 8px; + border-radius: 4px; + margin: 0; + white-space: pre-wrap; +} +#dn-canvas { + width: 100%; + height: 100%; + display: block; + touch-action: none; +} +@media (max-width: 720px) { + #devnode-app { + grid-template-columns: 1fr; + grid-template-rows: 40vh 1fr; + } + .dn-panel { + border-right: none; + border-bottom: 1px solid #243040; + } +} diff --git a/src/devnode/main.js b/src/devnode/main.js new file mode 100644 index 0000000..b485ae4 --- /dev/null +++ b/src/devnode/main.js @@ -0,0 +1,208 @@ +/** + * /devnode entry — author the same Training Room · DevIsland map as play. + * @see docs/DEVNODE_SSOT.md · docs/TRAINING_ROOM_SSOT.md + */ + +import { + BIOME_PRESETS, + NODE_FAMILIES, + NODE_PALETTE, + createEmptyNodeLayout, + validateNodeLayout +} from '../world/nodePalette.js'; +import { + TRAINING_ROOM_LABEL, + TRAINING_ROOM_MAP_ID, + createTrainingRoomLayout, + loadTrainingRoomLayoutFromStorage, + saveTrainingRoomLayoutToStorage, + stampTrainingRoomLayout, + trainingRoomTerrain +} from '../world/trainingRoomMap.js'; +import { buildTrainingRoomPromotePackage } from '../world/trainingRoomDeploy.js'; +import { fleetDeploySnapshot } from '../config/fleetEnv.js'; +import { DevNodeEditor } from './DevNodeEditor.js'; + +const el = { + canvas: document.getElementById('dn-canvas'), + biome: document.getElementById('dn-biome'), + families: document.getElementById('dn-families'), + palette: document.getElementById('dn-palette'), + export: document.getElementById('dn-export'), + import: document.getElementById('dn-import'), + clear: document.getElementById('dn-clear'), + file: document.getElementById('dn-file'), + stats: document.getElementById('dn-stats'), + hint: document.getElementById('dn-hint') +}; + +// Prevent browser context menu on canvas (RMB remove) +el.canvas?.addEventListener('contextmenu', (e) => e.preventDefault()); + +const editor = new DevNodeEditor(/** @type {HTMLCanvasElement} */ (el.canvas)); +// Match play island terrain knobs (Training Room · DevIsland) +editor.setBiomeTerrain(trainingRoomTerrain()); + +/** @type {string} */ +let familyFilter = 'all'; +/** @type {string} */ +let selectedId = 'node.rock_boulder'; + +function fillBiomes() { + el.biome.innerHTML = BIOME_PRESETS.map( + (b) => `` + ).join(''); +} + +function fillFamilies() { + const ids = ['all', ...Object.keys(NODE_FAMILIES)]; + el.families.innerHTML = ids + .map((id) => { + const label = id === 'all' ? 'All' : NODE_FAMILIES[id]?.label || id; + return ``; + }) + .join(''); + el.families.querySelectorAll('[data-fam]').forEach((btn) => { + btn.addEventListener('click', () => { + familyFilter = btn.getAttribute('data-fam') || 'all'; + fillFamilies(); + fillPalette(); + }); + }); +} + +function fillPalette() { + const list = + familyFilter === 'all' + ? NODE_PALETTE + : NODE_PALETTE.filter((e) => e.family === familyFilter); + el.palette.innerHTML = list + .map((e) => { + const fam = NODE_FAMILIES[e.family]?.label || e.family; + const stub = e.ready === false ? 'is-stub' : ''; + const sel = e.id === selectedId ? 'is-sel' : ''; + return ``; + }) + .join(''); + el.palette.querySelectorAll('[data-id]').forEach((btn) => { + btn.addEventListener('click', () => { + selectedId = btn.getAttribute('data-id') || selectedId; + editor.setSelectedPalette(selectedId); + fillPalette(); + refreshStats(); + }); + }); +} + +function refreshStats() { + const L = editor.exportLayout(); + el.stats.textContent = [ + `map: ${TRAINING_ROOM_MAP_ID}`, + `nodes: ${L.nodes.length}`, + `biome: ${L.biomeId || '—'}`, + `sel: ${selectedId}`, + `amp: ${L.terrain?.amp ?? '—'} seed: ${L.terrain?.seed ?? '—'}` + ].join('\n'); +} + +el.biome.addEventListener('change', () => { + const b = BIOME_PRESETS.find((x) => x.id === el.biome.value) || BIOME_PRESETS[0]; + editor.layout.biomeId = b.id; + editor.setBiomeTerrain(b.terrain); + // Prefer families from biome + if (b.families?.length) { + familyFilter = b.families[0]; + fillFamilies(); + fillPalette(); + } + refreshStats(); +}); + +el.export.addEventListener('click', () => { + const data = stampTrainingRoomLayout({ + ...editor.exportLayout(), + biomeId: el.biome.value + }); + saveTrainingRoomLayoutToStorage(data); + // Layout for play handoff + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `training-room-${data.biomeId || 'layout'}-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(a.href); + // Promote package (D1 index rows + world mesh nodes + R2 keys) — operators / CI + try { + const promote = buildTrainingRoomPromotePackage(data); + const pb = new Blob([JSON.stringify(promote, null, 2)], { type: 'application/json' }); + const ap = document.createElement('a'); + ap.href = URL.createObjectURL(pb); + ap.download = `training-room-promote-${Date.now()}.json`; + ap.click(); + URL.revokeObjectURL(ap.href); + } catch (err) { + console.warn('[DevNode] promote package', err); + } + el.hint.textContent = `Saved ${TRAINING_ROOM_LABEL} · layout + promote package (R2/D1 keys)`; + refreshStats(); +}); + +el.import.addEventListener('click', () => el.file.click()); +el.file.addEventListener('change', async () => { + const f = el.file.files?.[0]; + if (!f) return; + try { + const text = await f.text(); + const raw = JSON.parse(text); + const v = validateNodeLayout(raw); + if (!v.ok) { + el.hint.textContent = `Import failed: ${v.error}`; + return; + } + const stamped = stampTrainingRoomLayout(v.layout); + editor.loadLayout(stamped); + saveTrainingRoomLayoutToStorage(stamped); + if (stamped.biomeId) el.biome.value = stamped.biomeId; + el.hint.textContent = `Imported ${editor.nodeCount} nodes → ${TRAINING_ROOM_LABEL}`; + refreshStats(); + } catch (err) { + el.hint.textContent = `Import error: ${err?.message || err}`; + } +}); + +el.clear.addEventListener('click', () => { + editor.clearNodes(); + refreshStats(); +}); + +// Boot — same Training Room map as play (index.html) +fillBiomes(); +fillFamilies(); +fillPalette(); +editor.setSelectedPalette(selectedId); + +const stored = loadTrainingRoomLayoutFromStorage(); +if (stored?.nodes?.length) { + editor.loadLayout(stored); + if (stored.biomeId) el.biome.value = stored.biomeId; + el.hint.textContent = `Restored ${editor.nodeCount} nodes · ${TRAINING_ROOM_LABEL}`; +} else { + const builtIn = createTrainingRoomLayout(); + editor.loadLayout(builtIn); + el.hint.textContent = `Loaded built-in ${TRAINING_ROOM_LABEL} · export to play`; +} + +// Poll stats while placing +setInterval(refreshStats, 500); +refreshStats(); + +console.info( + `[DevNode] ${TRAINING_ROOM_LABEL} · palette`, + NODE_PALETTE.length, + '· mapId', + TRAINING_ROOM_MAP_ID, + fleetDeploySnapshot().authority +); diff --git a/src/effects/CameraShake.js b/src/effects/CameraShake.js index a1debdf..8400a44 100644 --- a/src/effects/CameraShake.js +++ b/src/effects/CameraShake.js @@ -53,9 +53,10 @@ export class CameraShake { const ny = Math.sin(t * 1.37 + 4.2) * 0.6 + Math.sin(t * 2.79 + 0.3) * 0.4; const nz = Math.sin(t * 0.83 + 2.9) * 0.6 + Math.sin(t * 3.11 + 5.1) * 0.4; - this._offset.set(nx, ny * 0.7, nz).multiplyScalar(shake * 0.55); + // Soft lab default — was 0.55 pos / 0.035 roll (felt like a phone on vibrate) + this._offset.set(nx, ny * 0.7, nz).multiplyScalar(shake * 0.28); this.rig.shakeOffset.copy(this._offset); - this.rig.shakeRoll = nz * shake * 0.035; + this.rig.shakeRoll = nz * shake * 0.018; this.trauma = Math.max(0, this.trauma - this.decay * dt); } diff --git a/src/effects/GroundDecals.js b/src/effects/GroundDecals.js index 2738fc6..0a0d6e3 100644 --- a/src/effects/GroundDecals.js +++ b/src/effects/GroundDecals.js @@ -19,7 +19,11 @@ export const DecalType = Object.freeze({ CRACK: 2, // radial fractures with hot glow (earth) SHOCKWAVE: 3, // thin expanding ring (all impacts) DUSTRING: 4, // soft ground-hugging dust puff (earth / wind) - FOAM: 5 // spreading sea foam over wet stone (water) + FOAM: 5, // spreading sea foam over wet stone (water) + /** Linear skillshot ice rime (LinearAbilityCasting) */ + FROST: 6, + /** Linear skillshot electric burn (LinearAbilityCasting) */ + ARC: 7 }); const DECAL_VERTEX = /* glsl */ ` @@ -93,6 +97,26 @@ const DECAL_FRAGMENT = /* glsl */ ` alpha = puff * (1.0 - uAge) * 0.7; color = mix(uColorA, uColorB, n * 0.5 + 0.5); + #elif DECAL == 6 /* FROST — skillshot rime */ + float n = fbm3(vec3(c * 2.2, uSeed * 17.0)); + float crust = smoothstep(1.05, 0.2, d + n * 0.35); + vec2 cell = voronoi2(c * 5.5 + uSeed * 9.0); + float plates = smoothstep(0.45, 0.05, cell.x); + float rim = smoothstep(0.12, 0.0, abs(d - mix(0.35, 0.95, sqrt(uAge)))); + alpha = clamp(crust * (0.45 + plates * 0.55) + rim * 0.35, 0.0, 1.0) * fadeOut; + color = mix(uColorA, uColorB, plates * 0.6 + rim * 0.4); + color += uColorB * plates * 0.35 * (1.0 - uAge); + + #elif DECAL == 7 /* ARC — electric burn */ + float ang = atan(c.y, c.x); + float branch = ridged(vec3(cos(ang * 3.0), sin(ang * 2.0), uSeed * 11.0) * 3.2, 5); + float n = snoise(vec3(c * 8.0, uSeed * 5.0 + uTime * 2.5)); + float burn = smoothstep(0.35, 0.9, branch + n * 0.2) * smoothstep(1.0, 0.15, d); + float flash = pow(max(0.0, n), 3.0) * (1.0 - smoothstep(0.0, 0.5, uAge)); + alpha = clamp(burn * 0.9 + flash * 0.5, 0.0, 1.0) * fadeOut; + color = mix(uColorA, uColorB, flash + burn * 0.5); + color += uColorB * flash * 2.2; + #else /* FOAM */ // A sheet of foam thrown outward by the impact, which then drains from // the middle and dries back into a ring, over wet stone that darkens and diff --git a/src/effects/HoverboardRide.js b/src/effects/HoverboardRide.js new file mode 100644 index 0000000..37cd9ee --- /dev/null +++ b/src/effects/HoverboardRide.js @@ -0,0 +1,590 @@ +import { + Box3, + Group, + MathUtils, + Mesh, + MeshBasicMaterial, + SphereGeometry, + Vector3 +} from 'three'; +import { clone as skeletonClone } from 'three/addons/utils/SkeletonUtils.js'; +import { ParticleShape } from '../particles/ParticleSystem.js'; +import { RateEmitter } from '../particles/ParticleEngine.js'; +import { BurstMode } from './BurstSphere.js'; +import { DecalType } from './GroundDecals.js'; +import { LAYER } from '../core/Layers.js'; +import { frame } from '../core/FrameUniforms.js'; +import { settings } from '../config/settings.js'; +import { getColor } from '../utils/color.js'; +import { Easing, saturate } from '../utils/math.js'; +import { + applySailClothMaterials, + setSailClothMode, + updateSailCloth +} from '../materials/SailCloth.js'; +import { WINDSURF_PARTS } from '../config/backSlotMobilitySsot.js'; + +const UP = new Vector3(0, 1, 0); +const _pos = new Vector3(); +const _dir = new Vector3(); +const _side = new Vector3(); +const _fwd = new Vector3(); +const _local = new Vector3(); +const _emit = {}; +const TAU = Math.PI * 2; + +const BIRTH_TIME = 0.32; +const DEATH_TIME = 0.5; + +const MANIFEST_URL = './models/ride/ride.manifest.json'; + +/** + * Replaces the procedural air ball with SI-scaled windsurf/hoverboard package. + * + * - Rigid body = deck under Toon RTS feet (sockets footL/footR from IK graph) + * - Sail boom/rail sockets for hand IK + * - Left↔right bank + sway while riding + * + * Same lifecycle as AirScooter: spawn → update → release / cancel. + */ +export class HoverboardRide { + /** + * @param {object} ctx { scene, particles, lights, decals, bursts, shake } + * @param {import('../loaders/AssetLoader.js').AssetLoader} [assets] + */ + constructor(ctx, assets = null) { + this.ctx = ctx; + this.assets = assets; + this.group = new Group(); + this.group.name = 'HoverboardRide'; + this.group.visible = false; + + this.boardRoot = new Group(); + this.boardRoot.name = 'RideBody'; + this.group.add(this.boardRoot); + + this.socketGroup = new Group(); + this.socketGroup.name = 'RideSockets'; + this.boardRoot.add(this.socketGroup); + + /** + * Unscaled rider seat — sibling of boardRoot so birth/death scale never + * multiplies the character (that "ripped away" / giant / micro bug). + * Bank/sway match the visual board; scale always identity. + */ + this.seatRoot = new Group(); + this.seatRoot.name = 'RideSeat'; + this.group.add(this.seatRoot); + + /** @type {Record} */ + this.sockets = {}; + this.manifest = null; + this.mesh = null; + this._ready = false; + this._loadPromise = null; + + this.dust = ctx.particles.get('walk.dust', { + capacity: 900, + shape: ParticleShape.SOFT, + additive: true, + swirl: true, + softFade: 0.4 + }); + this.dust.uniforms.uGravity.value.set(0, -0.35, 0); + this.dust.uniforms.uDrag.value = 0.9; + this.dust.uniforms.uEndSize.value = 0.6; + this.dustEmitter = new RateEmitter(); + + this.light = null; + this._birth = 0; + this._death = 0; + this._releasing = false; + this._bank = 0; + this._swayT = 0; + this._yaw = 0; + + this.debugHelpers = []; + } + + get active() { + return this.group.visible; + } + + get ready() { + return this._ready; + } + + /** Load windsurf (primary) or hoverboard package + socket empties. */ + async load(assets) { + if (this._loadPromise) return this._loadPromise; + this.assets = assets || this.assets; + this._loadPromise = this._loadInner(); + return this._loadPromise; + } + + async _loadInner() { + let manifest; + try { + const res = await fetch(MANIFEST_URL, { cache: 'no-cache' }); + manifest = await res.json(); + } catch (err) { + console.warn('[HoverboardRide] manifest fail', err); + manifest = { + primary: 'windsurf', + windsurf: { + file: './models/ride/windsurf_package.glb', + deckY: 0.06, + sockets: { + footL: [-0.18, 0.07, -0.08], + footR: [0.18, 0.07, -0.08], + sailRail: [0, 0.34, 0.18], + sailBoomL: [-0.22, 0.32, 0.12], + deckCenter: [0, 0.06, 0] + }, + motion: { bankMaxDeg: 18, swayHz: 0.55, swayM: 0.04 } + } + }; + } + this.manifest = manifest; + const key = manifest.primary === 'hoverboard' ? 'hoverboard' : 'windsurf'; + const pack = manifest[key] || manifest.windsurf; + const url = pack.file.startsWith('.') ? pack.file.replace(/^\.\//, './models/ride/').replace('models/ride/models/ride/', 'models/ride/') : pack.file; + // normalize path + const glbUrl = pack.file.includes('models/ride') + ? pack.file.replace(/^\.\//, './') + : `./models/ride/${pack.file.replace(/^\.\//, '')}`; + + try { + const gltf = await this.assets.loadGLTF(glbUrl); + const scene = skeletonClone(gltf.scene); + scene.name = 'RideMesh'; + // Fit package: keep author SI bake; ensure deck roughly at y=0 if needed + this._normalizeMesh(scene, pack); + // Art yaw vs travel +Z (WalkController: +Z = cos(yaw), +X = sin(yaw)). + // 0 = package forward matches travel; 180 = stern-first (wrong freeride feel). + const artYawDeg = + Number.isFinite(pack.artYawDeg) + ? pack.artYawDeg + : Number.isFinite(settings.walk?.boardArtYawDeg) + ? settings.walk.boardArtYawDeg + : 0; + scene.rotation.y = (artYawDeg * Math.PI) / 180; + // Sail cloth: sRGB maps + double-sided fabric + vertex wind (no soft-body engine) + applySailClothMaterials(scene, { forceCloth: false }); + setSailClothMode(scene, 'ride'); + this.boardRoot.add(scene); + this.mesh = scene; + this._artYawDeg = artYawDeg; + console.info(`[HoverboardRide] mesh artYaw=${artYawDeg}° travel=+Z cloth=on`); + } catch (err) { + console.error('[HoverboardRide] GLB load failed', glbUrl, err); + throw err; + } + + // Sockets stay in TRAVEL frame (+Z forward), not mesh art frame + const sockets = pack.sockets || {}; + for (const [name, xyz] of Object.entries(sockets)) { + const g = new Group(); + g.name = `socket_${name}`; + g.position.set(xyz[0], xyz[1], xyz[2]); + g.userData.socketName = name; + this.socketGroup.add(g); + this.sockets[name] = g; + } + // IK socket deckCenter (on scaled board for visual pad markers) + if (!this.sockets.deckCenter) { + const deck = new Group(); + deck.name = 'socket_deckCenter'; + deck.position.set(0, pack.deckY ?? 0.06, 0); + deck.userData.socketName = 'deckCenter'; + this.socketGroup.add(deck); + this.sockets.deckCenter = deck; + } + + // Tag logical parts (front / sail / board / engine) — mesh is often one Sketchfab Tube + this._tagWindsurfParts(pack); + + // Character parents to unscaled seatRoot — local stand only (see WalkController) + this.seat = this.seatRoot; + this.pack = pack; + this._ready = true; + + if (settings.walk?.debugSockets || settings.walk?.debugParts) this._buildDebugSpheres(); + return this; + } + + /** + * Identify board / front / sail / engine via manifest sockets (SI). + * Author GLB may be a single Para_Coat mesh — parts are **logical**, not node names. + * @param {object} pack + */ + _tagWindsurfParts(pack) { + /** @type {Record} */ + this.parts = {}; + const partDefs = pack.parts || WINDSURF_PARTS; + for (const [id, def] of Object.entries(partDefs)) { + const sockNames = def.sockets || []; + const groups = sockNames.map((n) => this.sockets[n]).filter(Boolean); + this.parts[id] = { + id, + label: def.label || id, + sockets: sockNames, + color: def.color ?? 0xffffff, + notes: def.notes || '', + /** Primary socket Object3D for this part */ + primary: groups[0] || null + }; + for (const g of groups) { + g.userData.partId = id; + g.userData.partLabel = def.label || id; + } + } + // Tag mesh root for agents / inspector + if (this.mesh) { + this.mesh.userData.windsurfParts = Object.keys(this.parts); + this.mesh.userData.partMap = Object.fromEntries( + Object.entries(this.parts).map(([k, v]) => [k, v.label]) + ); + } + console.info( + '[HoverboardRide] parts', + Object.entries(this.parts) + .map(([k, v]) => `${k}=${v.sockets.join('+')}`) + .join(' · ') + ); + } + + /** + * World position of a named part (socket primary). + * @param {'board'|'front'|'sail'|'engine'|string} partId + * @param {import('three').Vector3} [out] + */ + getPartWorldPosition(partId, out = new Vector3()) { + const p = this.parts?.[partId]?.primary || this.sockets?.[partId]; + if (p) { + p.getWorldPosition(out); + return out; + } + this.boardRoot.getWorldPosition(out); + return out; + } + + /** Part map for HUD / lab toast */ + describeParts() { + return Object.values(this.parts || {}).map((p) => ({ + id: p.id, + label: p.label, + sockets: p.sockets, + notes: p.notes + })); + } + + _normalizeMesh(scene, pack) { + scene.updateMatrixWorld(true); + const box = new Box3().setFromObject(scene); + const size = new Vector3(); + box.getSize(size); + // Target board length ~2.4–2.8 m if oversized, or scale up if tiny + const len = Math.max(size.x, size.z); + if (len > 4.5) { + const s = 2.6 / len; + scene.scale.multiplyScalar(s); + scene.updateMatrixWorld(true); + } else if (len < 0.4) { + const s = 2.4 / Math.max(len, 1e-3); + scene.scale.multiplyScalar(s); + scene.updateMatrixWorld(true); + } + // Ground bottom of mesh near y=0 under boardRoot + box.setFromObject(scene); + scene.position.y -= box.min.y; + scene.position.x -= (box.min.x + box.max.x) * 0.5; + scene.position.z -= (box.min.z + box.max.z) * 0.5; + } + + _buildDebugSpheres() { + // Color by part (board blue · front green · sail amber · engine red) + const partColor = (sock) => { + const pid = sock?.userData?.partId; + if (pid && this.parts?.[pid]?.color != null) return this.parts[pid].color; + return 0x3aa0ff; + }; + for (const g of Object.values(this.sockets)) { + const geo = new SphereGeometry(0.045, 10, 8); + const mat = new MeshBasicMaterial({ + color: partColor(g), + depthWrite: false, + transparent: true, + opacity: 0.9 + }); + const m = new Mesh(geo, mat); + m.name = `debug_${g.userData?.socketName || g.name}`; + m.userData.partLabel = g.userData?.partLabel || ''; + m.layers.set(LAYER.VFX); + g.add(m); + this.debugHelpers.push(m); + } + } + + /** Deck / seat height above world ground for the rider root. */ + get deckHeight() { + return this.pack?.deckY ?? settings.walk.hover ?? 0.06; + } + + /** Seat Object3D for mounting the character (unscaled, banked with board). */ + getSeat() { + return this.seatRoot || this.seat || this.sockets.deckCenter || this.boardRoot; + } + + /** + * Snap birth scale to full size before parenting a rider. + * Call from WalkController._mountRider so the character never inherits 0.01. + */ + forceFullSize() { + this._birth = 1; + this._death = 0; + this._releasing = false; + this.boardRoot.scale.setScalar(1); + this.seatRoot.scale.set(1, 1, 1); + } + + /** + * Current board bank about local Z (radians), for matching character lean + * when not reparented. + */ + get bank() { + return this._bank || 0; + } + + /** + * World position of a named socket (footL, sailRail, …). + * @param {string} name + * @param {Vector3} [out] + */ + getSocketWorld(name, out = new Vector3()) { + const s = this.sockets[name]; + if (s) { + s.getWorldPosition(out); + return out; + } + this.boardRoot.getWorldPosition(out); + return out; + } + + /** Snapshot of IK sockets in world space for RideIK. */ + getIkWorldTargets() { + const out = {}; + for (const name of Object.keys(this.sockets)) { + out[name] = this.getSocketWorld(name, new Vector3()); + } + return out; + } + + /** + * Puff the board into existence under the rider. + * @param {Vector3|{x:number,y?:number,z:number}} position + * @param {number} [yaw] world heading so board points travel direction on spawn + */ + spawn(position, yaw = 0) { + const c = settings.walk; + this._birth = 0; + this._death = 0; + this._releasing = false; + this._bank = 0; + this._swayT = 0; + this.dustEmitter.reset(); + + this.group.visible = true; + this.group.position.set(position.x, position.y || 0, position.z); + this._yaw = Number.isFinite(yaw) ? yaw : 0; + this.group.rotation.y = this._yaw; + this.boardRoot.position.set(0, this.deckHeight, 0); + this.boardRoot.scale.setScalar(0.01); + this.light = this.light ?? this.ctx.lights.acquire(); + + const inner = getColor(c.colorInner); + const outer = getColor(c.colorOuter); + this.ctx.bursts.spawn(BurstMode.AIR, position, { + radius: 0.4, + endRadius: 2.8, + life: 0.4, + intensity: 0.85 * settings.global.glow, + fresnel: c.fresnel, + displace: 0.25, + colorA: outer, + colorB: inner, + colorC: inner + }); + _pos.set(position.x, 0.02, position.z); + this.ctx.decals.spawn(DecalType.DUSTRING, _pos, { + radius: 2.2, + life: 1.0, + intensity: 0.65, + colorA: inner, + colorB: outer + }); + this.ctx.shake.add(0.16 * c.landShake * settings.global.explosionIntensity, 0.85, 20); + } + + /** + * @param {number} dt + * @param {Vector3} position ride anchor (deck under rider), world + * @param {Vector3} side rider left (horizontal) + * @param {number} distance metres ridden + * @param {number} speed m/s + * @param {number} [yaw] world heading about Y + * @param {number} [turnRate] rad/s signed (left +) for bank + */ + update(dt, position, side, distance, speed, yaw = 0, turnRate = 0) { + if (!this.group.visible) return; + const c = settings.walk; + const motion = this.pack?.motion || { bankMaxDeg: 18, swayHz: 0.55, swayM: 0.04 }; + // Sail flap with speed (visual cloth only) + if (this.mesh) { + const wind = MathUtils.clamp(0.7 + (speed || 0) * 0.08, 0.7, 2.0); + updateSailCloth(this.mesh, dt, { wind, speed: 1 }); + } + + if (this._releasing) { + this._death = saturate(this._death + dt / DEATH_TIME); + if (this._death >= 1) { + this._retire(); + return; + } + } else { + this._birth = saturate(this._birth + dt / BIRTH_TIME); + } + + const birth = Easing.outBack(this._birth); + const fade = 1 - Easing.outQuad(this._death); + + // Full world anchor (incl. freeride wave Y) — rider is parented under seat + // so they ride with this group, not via separate character world transforms. + const py = Number.isFinite(position.y) ? position.y : 0; + this.group.position.set(position.x, py, position.z); + this._yaw = yaw; + this.group.rotation.y = yaw; + + // Left ↔ right bank from turn + mild periodic sway (sail feel) + this._swayT += dt; + const bankTarget = + -MathUtils.clamp(turnRate / Math.max(0.4, c.leanRate || 2), -1, 1) * + (motion.bankMaxDeg || 18) * + MathUtils.DEG2RAD + + Math.sin(this._swayT * (motion.swayHz || 0.55) * TAU) * 0.06; + this._bank = MathUtils.damp(this._bank, bankTarget, 8, dt); + + const sway = + Math.sin(this._swayT * (motion.swayHz || 0.55) * TAU + 1.2) * (motion.swayM || 0.04); + const pitch = -MathUtils.clamp(speed * 0.012, 0, 0.12); + const deckY = this.deckHeight * birth; + // Deck height local to group (group already at water/path Y) + this.boardRoot.position.set(sway, deckY, 0); + this.boardRoot.rotation.z = this._bank; + this.boardRoot.rotation.x = pitch; + // Visual board only scales on birth/death — never the rider seat + this.boardRoot.scale.setScalar(Math.max(0.001, birth * fade)); + + // Rider seat: same bank/sway/height, always scale 1 (SI character intact) + this.seatRoot.position.set(sway, deckY, 0); + this.seatRoot.rotation.z = this._bank; + this.seatRoot.rotation.x = pitch; + this.seatRoot.scale.set(1, 1, 1); + this.seatRoot.visible = fade > 0.05; + + if (this.light) { + this.getSocketWorld('deckCenter', _pos); + if (!_pos.lengthSq()) _pos.set(position.x, this.deckHeight + 0.4, position.z); + this.ctx.lights.set( + this.light, + _pos, + getColor(c.lightColor), + c.lightIntensity * 0.55 * birth * fade, + c.lightRadius, + dt + ); + } + + // Dust from under deck + if (!this._releasing) { + const rate = c.dustRate * (0.25 + 0.75 * saturate(speed / Math.max(0.5, c.speed))); + const count = Math.round(this.dustEmitter.tick(dt, rate) * settings.global.particleCount); + if (count > 0) { + _pos.set(position.x, 0.05, position.z); + _fwd.set(Math.sin(yaw), 0, Math.cos(yaw)); + _dir.copy(_fwd).multiplyScalar(-1); + _emit.position = _pos; + _emit.radius = 0.55; + _emit.direction = _dir; + _emit.speed = speed * 0.4 + 0.5; + _emit.speedVariance = 0.5; + _emit.spread = 0.7; + _emit.inherit = null; + _emit.anchor = position; + _emit.size = 0.11; + _emit.sizeVariance = 0.5; + _emit.life = c.dustLifetime; + _emit.lifeVariance = 0.45; + _emit.spin = 0; + _emit.tint = null; + _emit.time = frame.uTime.value; + this.dust.emit(count, _emit); + } + } + } + + release() { + if (!this.group.visible || this._releasing) return; + const c = settings.walk; + this._releasing = true; + this._death = 0; + const inner = getColor(c.colorInner); + const outer = getColor(c.colorOuter); + _pos.copy(this.group.position).setY(this.deckHeight); + this.ctx.bursts.spawn(BurstMode.AIR, _pos, { + radius: 0.5, + endRadius: 3.5, + life: 0.55, + intensity: 1.0 * settings.global.glow, + fresnel: c.fresnel, + displace: 0.3, + colorA: outer, + colorB: inner, + colorC: inner + }); + this.ctx.shake.add(0.12 * c.landShake * settings.global.explosionIntensity, 0.75, 18); + } + + /** Instant remove — used on hard cancel / dismount complete. */ + cancel() { + this._retire(); + } + + /** + * Hide vehicle and clear VFX. Mesh stays loaded for next deploy; + * group is not in the playable scene (visible=false). + */ + _retire() { + this.group.visible = false; + this._releasing = false; + this._birth = 0; + this._death = 0; + this._bank = 0; + this.boardRoot.scale.setScalar(0.01); + if (this.light) { + this.ctx.lights.release(this.light); + this.light = null; + } + } + + dispose() { + this.cancel(); + this.group.traverse((o) => { + if (o.geometry) o.geometry.dispose?.(); + if (o.material) { + if (Array.isArray(o.material)) o.material.forEach((m) => m.dispose?.()); + else o.material.dispose?.(); + } + }); + } +} diff --git a/src/effects/OceanWindIndicators.js b/src/effects/OceanWindIndicators.js new file mode 100644 index 0000000..0dea2e8 --- /dev/null +++ b/src/effects/OceanWindIndicators.js @@ -0,0 +1,314 @@ +/** + * Ocean wind indications — soft, nearly invisible ambient gusts over water. + * + * Reuses **wind attack** primitives (same family as WindAbility / storm residual): + * - WindRibbonMaterial (silk hairline sheets) + * - Soft cyan motes (VfxDirector wind residual palette) + * + * Tuned for open ocean: low opacity, low glow, low height, sparse spawn — + * readable as air moving over the sea without competing with skills. + * + * @see materials/WindMaterial.js + * @see abilities/WindAbility.js + * @see vfx/VfxDirector.js wind residual + */ + +import { Group, Mesh, Vector3, Color, MathUtils, BufferAttribute } from 'three'; +import { RibbonGeometry, RibbonMode } from './RibbonGeometry.js'; +import { WindRibbonMaterial } from '../materials/WindMaterial.js'; +import { LAYER } from '../core/Layers.js'; +import { settings } from '../config/settings.js'; +import { ParticleShape } from '../particles/ParticleSystem.js'; +import { RateEmitter } from '../particles/ParticleEngine.js'; +import { WORLD } from '../config/worldScale.js'; + +const POINTS = 28; +const MAX_GUSTS = 6; +const _p = new Vector3(); +const _dir = new Vector3(); +const _side = new Vector3(); +const _emit = {}; + +/** + * @typedef {object} Gust + * @property {import('./RibbonGeometry.js').RibbonGeometry} ribbon + * @property {Mesh} mesh + * @property {Vector3[]} points + * @property {number} seed + * @property {number} age + * @property {number} life + * @property {number} speed + * @property {number} yaw + * @property {number} baseY + * @property {boolean} live + */ + +export class OceanWindIndicators { + /** + * @param {{ + * scene: import('three').Scene, + * particles?: import('../particles/ParticleEngine.js').ParticleEngine, + * }} ctx + */ + constructor(ctx) { + this.ctx = ctx; + this.group = new Group(); + this.group.name = 'OceanWindIndicators'; + this.group.frustumCulled = false; + ctx.scene.add(this.group); + + this.enabled = true; + this._spawnTimer = 0; + this._elapsed = 0; + + // Shared silk material — same shader as wind attacks, ghost opacity + this.material = new WindRibbonMaterial(); + this._applyGhostLook(); + + /** @type {Gust[]} */ + this.gusts = []; + for (let i = 0; i < MAX_GUSTS; i++) { + const ribbon = new RibbonGeometry(POINTS - 1); + const mesh = new Mesh(ribbon.geometry, this.material); + mesh.frustumCulled = false; + mesh.matrixAutoUpdate = false; + mesh.layers.set(LAYER.VFX); + mesh.renderOrder = 6; // under skill VFX + mesh.visible = false; + this.group.add(mesh); + + const seed = (i * 0.6180339887) % 1; + const seeds = new Float32Array(POINTS * 2).fill(seed); + ribbon.geometry.setAttribute('aSeed', new BufferAttribute(seeds, 1)); + + const points = []; + for (let p = 0; p < POINTS; p++) points.push(new Vector3()); + + this.gusts.push({ + ribbon, + mesh, + points, + seed, + age: 0, + life: 4, + speed: 3, + yaw: 0, + baseY: 0.12, + live: false + }); + } + + // Soft wind motes (same system id family as attack residual) + this.motes = null; + this.moteEmitter = new RateEmitter(); + if (ctx.particles) { + this.motes = ctx.particles.get('ocean.wind.motes', { + capacity: 400, + shape: ParticleShape.SOFT, + additive: true, + softFade: 0.55, + swirl: true + }); + if (this.motes.uniforms?.uGravity) this.motes.uniforms.uGravity.value.set(0, 0.02, 0); + if (this.motes.uniforms?.uDrag) this.motes.uniforms.uDrag.value = 0.96; + if (this.motes.uniforms?.uEndSize) this.motes.uniforms.uEndSize.value = 0.4; + } + } + + /** Nearly invisible silk — reads as heat/air shimmer, not a cast. */ + _applyGhostLook() { + const u = this.material.uniforms; + const w = settings.wind || {}; + // Steal wind attack palette, crush intensity + u.uColorInner.value.set(w.colorInner || '#f4fcff'); + u.uColorOuter.value.set(w.colorOuter || '#b6d8ea'); + u.uOpacity.value = 0.07; // nearly invisible + u.uGlow.value = 0.22; + u.uFresnel.value = 0.85; + u.uNoiseStrength.value = (w.noiseStrength ?? 0.71) * 0.55; + u.uNoiseFrequency.value = (w.noiseFrequency ?? 0.9) * 0.7; + u.uSwirlSpeed.value = (w.swirlSpeed ?? 2.2) * 0.45; + u.uFilaments.value = Math.max(12, Math.min(22, (w.filamentCount ?? 28) * 0.55)); + u.uFilamentSharp.value = Math.min(0.5, w.filamentSharpness ?? 0.56); + u.uTurbulence.value = (w.turbulence ?? 0.8) * 0.4; + u.uHaze.value = 0.55; + u.uTailFade.value = 0.28; + this.material.needsUpdate = true; + } + + /** + * @param {number} dt + * @param {import('three').Vector3} focus player / camera focus XZ + * @param {{ + * waterY?: number, + * islandRadius?: number, + * windYaw?: number, + * windSpeed?: number, + * enabled?: boolean + * }} [opts] + */ + update(dt, focus, opts = {}) { + if (opts.enabled === false || settings.walk?.oceanWindIndicators === false) { + this._hideAll(); + return; + } + if (!(dt > 0) || !focus) return; + + this._elapsed += dt; + this._applyGhostLook(); // live editor knobs via settings.wind + + const waterY = opts.waterY ?? WORLD.waterY ?? -0.04; + const islandR = opts.islandRadius ?? WORLD.islandRadius ?? 14; + const windYaw = + Number.isFinite(opts.windYaw) + ? opts.windYaw + : (settings.walk?.oceanWindYaw ?? 0.7) + Math.sin(this._elapsed * 0.07) * 0.25; + const windSpeed = opts.windSpeed ?? settings.walk?.oceanWindSpeed ?? 4.2; + + // Spawn cadence — sparse so ocean stays soft + this._spawnTimer -= dt; + if (this._spawnTimer <= 0) { + this._spawnTimer = MathUtils.randFloat(1.4, 3.2); + this._trySpawn(focus, waterY, islandR, windYaw, windSpeed); + } + + // Soft mote field around player over water (not on island pad) + this._emitMotes(dt, focus, waterY, islandR, windYaw, windSpeed); + + for (const g of this.gusts) { + if (!g.live) continue; + g.age += dt; + if (g.age >= g.life) { + g.live = false; + g.mesh.visible = false; + continue; + } + + // Advect polyline along wind + gentle sin meander + const t = g.age / g.life; + const fade = Math.sin(Math.PI * Math.min(1, t * 1.15)) * (1 - t * 0.35); + const step = g.speed * dt; + _dir.set(Math.sin(g.yaw), 0, Math.cos(g.yaw)); + _side.set(_dir.z, 0, -_dir.x); + + for (let i = 0; i < g.points.length; i++) { + const pt = g.points[i]; + const along = i / (g.points.length - 1); + pt.addScaledVector(_dir, step); + // Soft lateral meander (wind attack turbulence, reduced) + const meander = + Math.sin(this._elapsed * 1.1 + along * 4.5 + g.seed * 12) * 0.012 * dt * 60; + pt.addScaledVector(_side, meander); + // Stay slightly above water, rise/fall with wave feel + pt.y = + g.baseY + + waterY + + Math.sin(this._elapsed * 1.4 + along * 3.0 + g.seed * 8) * 0.04; + } + + // Width profile: thin whisker ends (WindAbility WIDTH_PROFILE lite) + const widthAt = (u) => { + const open = Math.min(1, u / 0.25); + const close = Math.min(1, (1 - u) / 0.3); + const body = open * open * (3 - 2 * open) * close * close * (3 - 2 * close); + return (0.35 + 0.9 * body) * fade; // metres — soft low curtain + }; + + g.ribbon.build(g.points, { + mode: RibbonMode.UPRIGHT, + width: 0.55 * fade, + widthProfile: widthAt + }); + g.mesh.visible = fade > 0.02; + g.mesh.matrixWorld.identity(); + } + } + + /** + * @param {Vector3} focus + * @param {number} waterY + * @param {number} islandR + * @param {number} windYaw + * @param {number} windSpeed + */ + _trySpawn(focus, waterY, islandR, windYaw, windSpeed) { + const free = this.gusts.find((g) => !g.live); + if (!free) return; + + // Spawn ring outside island pad so streaks read over ocean + const dist = MathUtils.randFloat(islandR + 2.5, islandR + 18); + const ang = Math.random() * Math.PI * 2; + const ox = focus.x + Math.cos(ang) * dist; + const oz = focus.z + Math.sin(ang) * dist; + + // Prefer not spawning on dry pad + if (Math.hypot(ox - focus.x, oz - focus.z) < islandR * 0.85) return; + + free.live = true; + free.age = 0; + free.life = MathUtils.randFloat(3.2, 6.5); + free.speed = windSpeed * MathUtils.randFloat(0.75, 1.15); + free.yaw = windYaw + MathUtils.randFloat(-0.35, 0.35); + free.baseY = MathUtils.randFloat(0.08, 0.35); + + _dir.set(Math.sin(free.yaw), 0, Math.cos(free.yaw)); + _side.set(_dir.z, 0, -_dir.x); + const len = MathUtils.randFloat(6, 14); + for (let i = 0; i < free.points.length; i++) { + const u = i / (free.points.length - 1); + free.points[i].set( + ox + _dir.x * u * len + _side.x * Math.sin(u * 4 + free.seed * 6) * 0.35, + waterY + free.baseY, + oz + _dir.z * u * len + _side.z * Math.sin(u * 4 + free.seed * 6) * 0.35 + ); + } + free.mesh.visible = true; + } + + _emitMotes(dt, focus, waterY, islandR, windYaw, windSpeed) { + if (!this.motes) return; + // Very low rate — soft air indication only + const rate = settings.walk?.oceanWindMoteRate ?? 8; + const n = this.moteEmitter.tick(dt, rate); + if (n < 1) return; + + _dir.set(Math.sin(windYaw), 0.05, Math.cos(windYaw)).normalize(); + // Ring just outside pad / over near water + const r = islandR + MathUtils.randFloat(1, 12); + const a = Math.random() * Math.PI * 2; + _p.set(focus.x + Math.cos(a) * r, waterY + MathUtils.randFloat(0.15, 1.1), focus.z + Math.sin(a) * r); + + _emit.position = _p; + _emit.radius = 1.8; + _emit.direction = _dir; + _emit.speed = windSpeed * 0.35; + _emit.speedVariance = 0.4; + _emit.spread = 0.55; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.06; + _emit.sizeVariance = 0.5; + _emit.life = 1.4; + _emit.lifeVariance = 0.5; + _emit.spin = 0.4; + _emit.tint = new Color(0xc9f0ff); + _emit.time = this._elapsed; + this.motes.emit(Math.min(6, n), _emit); + } + + _hideAll() { + for (const g of this.gusts) { + g.live = false; + g.mesh.visible = false; + } + } + + dispose() { + this._hideAll(); + this.group.removeFromParent(); + this.material.dispose?.(); + for (const g of this.gusts) { + g.ribbon.geometry?.dispose?.(); + } + } +} diff --git a/src/equipment/meshAppearance.js b/src/equipment/meshAppearance.js new file mode 100644 index 0000000..aa048e4 --- /dev/null +++ b/src/equipment/meshAppearance.js @@ -0,0 +1,176 @@ +/** + * Mesh appearance lab — color · scale · rotate · offset on equipped weapon/armour. + * + * Extends WeaponMeshAttach / kit meshes — not a second equip system. + * Persist per item id in localStorage for casting.* develop loop. + * + * @see docs/GAME_ITEM_PREFAB_PRODUCTION_SSOT.md + */ + +import { Color, MathUtils } from 'three'; + +const LS_KEY = 'casting.meshAppearance.v1'; + +/** + * @typedef {object} MeshAppearance + * @property {string} [color] hex #rrggbb + * @property {number} [emissive] 0..1 + * @property {number} [scale] uniform + * @property {number[]} [eulerDeg] [x,y,z] + * @property {number[]} [offset] [x,y,z] local metres + * @property {number} [metalness] + * @property {number} [roughness] + */ + +/** @returns {Record} */ +export function loadAppearanceMap() { + try { + return JSON.parse(localStorage.getItem(LS_KEY) || '{}') || {}; + } catch { + return {}; + } +} + +/** @param {Record} map */ +export function saveAppearanceMap(map) { + localStorage.setItem(LS_KEY, JSON.stringify(map || {})); +} + +/** + * @param {string} itemId + * @returns {MeshAppearance} + */ +export function getAppearance(itemId) { + if (!itemId) return {}; + return loadAppearanceMap()[itemId] || {}; +} + +/** + * @param {string} itemId + * @param {MeshAppearance} patch + */ +export function setAppearance(itemId, patch) { + if (!itemId) return; + const map = loadAppearanceMap(); + map[itemId] = { ...(map[itemId] || {}), ...patch }; + saveAppearanceMap(map); + return map[itemId]; +} + +/** + * Apply appearance to a Three.js object tree (weapon attach / armour mesh). + * @param {import('three').Object3D|null} root + * @param {MeshAppearance} app + */ +export function applyMeshAppearance(root, app = {}) { + if (!root) return; + const color = app.color ? new Color(app.color) : null; + const scale = Number.isFinite(app.scale) ? app.scale : null; + const euler = app.eulerDeg; + const offset = app.offset; + + // Prefer WeaponAttach / mesh holder under hand + const holder = + root.userData?.weaponAttach || root.name === 'WeaponAttach' + ? root + : root.children?.find?.((c) => c.userData?.weaponAttach || c.name === 'WeaponAttach') || + root; + + if (scale != null && scale > 0.05) { + // Base = post-fit attach scale (usually 1); lab multiplies for SI edit + if (holder.userData._appBaseScale == null) { + holder.userData._appBaseScale = holder.scale.x || 1; + } + const base = holder.userData._appBaseScale || 1; + holder.scale.setScalar(base * scale); + holder.userData.labScale = scale; + } + + if (Array.isArray(euler) && euler.length >= 3) { + holder.rotation.set( + MathUtils.degToRad(euler[0] || 0), + MathUtils.degToRad(euler[1] || 0), + MathUtils.degToRad(euler[2] || 0) + ); + } + + if (Array.isArray(offset) && offset.length >= 3) { + holder.position.set(offset[0] || 0, offset[1] || 0, offset[2] || 0); + } + + if (color || app.metalness != null || app.roughness != null || app.emissive != null) { + holder.traverse((o) => { + if (!o.isMesh || !o.material) return; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (let i = 0; i < mats.length; i++) { + let m = mats[i]; + if (!m) continue; + // Clone once so we don't paint shared materials + if (!m.userData?._appCloned) { + m = m.clone(); + m.userData = { ...(m.userData || {}), _appCloned: true }; + if (Array.isArray(o.material)) o.material[i] = m; + else o.material = m; + } + if (color && m.color) m.color.copy(color); + if (app.emissive != null && m.emissive) { + m.emissive.copy(color || m.color || new Color(0xffffff)); + m.emissiveIntensity = MathUtils.clamp(Number(app.emissive) || 0, 0, 2); + } + if (app.metalness != null && 'metalness' in m) m.metalness = Number(app.metalness); + if (app.roughness != null && 'roughness' in m) m.roughness = Number(app.roughness); + m.needsUpdate = true; + } + }); + } +} + +/** + * Find weapon attach under character R_hand. + * @param {import('../animation/CharacterController.js').CharacterController} character + */ +export function getWeaponAttachRoot(character) { + return ( + character?.weaponAttach || + character?.bones?.rHand?.children?.find?.( + (c) => c.userData?.weaponAttach || c.name === 'WeaponAttach' + ) || + null + ); +} + +/** + * Apply saved appearance for equipped weapon id. + * @param {import('../animation/CharacterController.js').CharacterController} character + * @param {string} itemId + */ +export function applyWeaponAppearance(character, itemId) { + const root = getWeaponAttachRoot(character); + if (!root || !itemId) return false; + applyMeshAppearance(root, getAppearance(itemId)); + return true; +} + +/** + * Apply tint to kit mesh_ids parts for an armour mesh slot. + * @param {import('../animation/CharacterController.js').CharacterController} character + * @param {string} meshSlot head|body|arms|legs|… + * @param {string} itemId + */ +export function applyArmorAppearance(character, meshSlot, itemId) { + const eq = character?.equipment; + if (!eq || !meshSlot) return false; + const app = getAppearance(itemId); + // EquipmentManager may expose meshes by slot — soft apply via model traverse name match + const model = character.model; + if (!model) return false; + const needle = String(meshSlot).toLowerCase(); + model.traverse((o) => { + if (!o.isMesh) return; + const n = (o.name || '').toLowerCase(); + if (!n.includes(needle) && !n.includes(needle.replace(/s$/, ''))) return; + if (!o.visible) return; + applyMeshAppearance(o, app); + }); + return true; +} diff --git a/src/equipment/weaponPrefabLab.js b/src/equipment/weaponPrefabLab.js new file mode 100644 index 0000000..6074b3e --- /dev/null +++ b/src/equipment/weaponPrefabLab.js @@ -0,0 +1,197 @@ +/** + * Weapon prefab lab — import GLB, assign modelUrl, live scale/edit, measure SI length. + * + * Used by Admin Hub F4 Prefabs. Appearance persists via meshAppearance (per item id). + * Draft modelUrl lives on prefab drafts (localStorage). + * + * @see equipment/meshAppearance.js · character/WeaponMeshAttach.js + */ + +import { Box3, Vector3 } from 'three'; +import { + getAppearance, + setAppearance, + applyWeaponAppearance, + getWeaponAttachRoot +} from './meshAppearance.js'; +import { attachWeaponModel, clearWeaponAttach } from '../character/WeaponMeshAttach.js'; +import { saveDraft, getDraft } from '../api/prefabDraftStore.js'; +import { + ensureWeaponCatalog, + equipWeaponById, + listEquippableWeapons, + getEquippedWeapon +} from '../combat/equippedWeaponRuntime.js'; +import { normalizeHoldKind } from '../character/weaponHoldPose.js'; + +const _box = new Box3(); +const _size = new Vector3(); + +/** Local blob URL registry for imported GLBs (session) */ +const _imports = new Map(); // id → { url, name, sizeBytes } + +/** + * Measure world-space AABB of equipped weapon attach (metres). + * @param {import('../animation/CharacterController.js').CharacterController|null} character + * @returns {{ lengthM: number, widthM: number, heightM: number, scale: number }|null} + */ +export function measureWeaponScale(character) { + const root = getWeaponAttachRoot(character); + if (!root) return null; + root.updateWorldMatrix?.(true, true); + _box.setFromObject(root); + _box.getSize(_size); + const lengthM = Math.max(_size.x, _size.y, _size.z); + const scale = root.scale?.x ?? 1; + return { + lengthM, + widthM: Math.min(_size.x, _size.z), + heightM: _size.y, + scale, + fitScale: root.userData?._fitScale ?? root.userData?._appBaseScale ?? scale, + profile: root.userData?.profile || 'melee', + modelUrl: root.userData?.modelUrl || null + }; +} + +/** + * Import a local GLB/GLTF File → blob URL, register under item id. + * @param {File} file + * @param {string} itemId + * @returns {Promise<{ url: string, name: string, sizeBytes: number }>} + */ +export async function importWeaponGlb(file, itemId) { + if (!file) throw new Error('No file'); + const name = file.name || 'weapon.glb'; + if (!/\.(glb|gltf)$/i.test(name)) throw new Error('Need .glb or .gltf'); + const prev = _imports.get(itemId); + if (prev?.url?.startsWith('blob:')) { + try { + URL.revokeObjectURL(prev.url); + } catch { + /* ok */ + } + } + const url = URL.createObjectURL(file); + const rec = { url, name, sizeBytes: file.size || 0, importedAt: Date.now() }; + _imports.set(itemId, rec); + return rec; +} + +/** + * Assign modelUrl (CDN or blob) onto a local prefab draft and persist. + * @param {string} draftId + * @param {string} modelUrl + * @param {{ name?: string, maxLengthM?: number, scale?: number }} [meta] + */ +export function assignModelToDraft(draftId, modelUrl, meta = {}) { + const d = getDraft(draftId); + if (!d) throw new Error('Draft not found'); + d.modelUrl = modelUrl; + d.assets = { ...(d.assets || {}), modelUrl, modelName: meta.name || d.assets?.modelName }; + if (meta.maxLengthM != null) d.maxLengthM = meta.maxLengthM; + if (meta.scale != null) { + d.meshScale = meta.scale; + setAppearance(d.id, { scale: meta.scale }); + } + d.layers = d.layers || {}; + d.layers.assets = { + ...(d.layers.assets || {}), + modelUrl, + iconUrl: d.iconUrl || d.layers.assets?.iconUrl || null + }; + return saveDraft(d); +} + +/** + * Equip catalog weapon or draft (draft needs modelUrl + id). + * @param {string} weaponId + * @param {{ character: object, onToast?: (s:string)=>void }} ctx + * @param {{ modelUrl?: string, maxLengthM?: number, profile?: string }} [override] + */ +export async function equipWeaponForLab(weaponId, ctx, override = {}) { + await ensureWeaponCatalog(); + const catalog = listEquippableWeapons(); + let id = weaponId; + const draft = getDraft(weaponId); + + // Catalog equip path + if (catalog.some((w) => w.id === weaponId) && !override.modelUrl) { + await equipWeaponById(weaponId, ctx); + } else if (draft || override.modelUrl) { + // Manual attach from draft / import without full catalog entry + const character = ctx.character; + const bones = character?.equipment?.findBones?.() || character?.bones || {}; + const hand = bones.rHand || character?.bones?.rHand; + const modelUrl = override.modelUrl || draft?.modelUrl; + if (!hand || !modelUrl) throw new Error('Need hand bone + modelUrl'); + clearWeaponAttach(hand); + const profile = + override.profile || + (draft?.weaponType + ? /WAND/i.test(draft.weaponType) + ? 'wand' + : /STAFF/i.test(draft.weaponType) + ? 'staff' + : /BOW/i.test(draft.weaponType) + ? 'bow' + : /PISTOL|GUN/i.test(draft.weaponType) + ? 'pistol' + : 'melee' + : 'melee'); + const maxLengthM = override.maxLengthM ?? draft?.maxLengthM ?? 1.2; + const attach = await attachWeaponModel(hand, modelUrl, { profile, maxLengthM }); + if (character) { + character.weaponAttach = attach; + character.weaponHoldKind = normalizeHoldKind(draft?.weaponType || profile); + character.syncWeaponAttach?.(); + character.rebuildWeaponVolume?.({ debug: false }); + applyWeaponAppearance(character, draft?.id || weaponId); + } + id = draft?.id || weaponId; + } else { + await equipWeaponById(weaponId, ctx); + } + + // Apply appearance scale after attach + applyWeaponAppearance(ctx.character, id); + return { id, measure: measureWeaponScale(ctx.character), equipped: getEquippedWeapon() }; +} + +/** + * Live scale edit — writes meshAppearance + reapplies on hand. + * @param {string} itemId + * @param {number} scale + * @param {object} character + */ +export function setLiveWeaponScale(itemId, scale, character) { + const s = Math.max(0.15, Math.min(4, Number(scale) || 1)); + setAppearance(itemId, { scale: s }); + applyWeaponAppearance(character, itemId); + return measureWeaponScale(character); +} + +/** + * Full appearance patch (scale, euler, offset, color). + * @param {string} itemId + * @param {import('./meshAppearance.js').MeshAppearance} patch + * @param {object} character + */ +export function setLiveWeaponAppearance(itemId, patch, character) { + setAppearance(itemId, patch); + applyWeaponAppearance(character, itemId); + return { appearance: getAppearance(itemId), measure: measureWeaponScale(character) }; +} + +/** + * Format SI readout for UI. + * @param {ReturnType} m + */ +export function formatScaleReadout(m) { + if (!m) return 'No weapon mesh — equip or import first'; + return `length ${m.lengthM.toFixed(2)} m · scale ×${(m.scale || 1).toFixed(2)} · ${m.profile}`; +} + +export function listSessionImports() { + return [..._imports.entries()].map(([id, rec]) => ({ id, ...rec })); +} diff --git a/src/fishing/FishingController.js b/src/fishing/FishingController.js new file mode 100644 index 0000000..60cbbc8 --- /dev/null +++ b/src/fishing/FishingController.js @@ -0,0 +1,674 @@ +/** + * 3D fishing profession controller — main-hand pole · lure cast · SCUM snag · Palworld bar. + * + * Extends harvest profession path (not a second combat engine): + * - Equip fishing pole (TOOL / t0-fishing-pole … T5) + * - Skill tree + rod stats + SWG RGB meals → fight/cast/nautical mods + * - RMB aim · LMB cast · S/RMB snag · wheel reel/slack + * + * @see docs/FISHING_PROFESSION_SSOT.md + */ + +import { Vector3, Group, Mesh, MeshStandardMaterial, SphereGeometry } from 'three'; +import { + poleById, + lureById, + rollFishSpecies, + computeReelZoneWidth, + computeLineMax, + computeCastRange, + catchXp, + FISHING_POLES, + FISHING_LURES +} from './fishingCatalog.js'; +import { createFightState, stepFight, beginBite } from './fishingFight.js'; +import { + loadProfessionState, + saveProfessionState, + grantFishingXp, + resolveProfessionMods, + eatMeal, + unlockTreeNode +} from './professionState.js'; +import { applyCatchRewards } from './fishingRewards.js'; +import { rodById } from './fishingRodTypes.js'; +import { + applyFishWorldScale, + applyPoleWorldScale, + applyLureWorldScale, + resolveLengthM +} from './fishScale.js'; +import { FishingUi } from '../ui/fishingUi.js'; +import { WORLD } from '../config/worldScale.js'; + +const _v = new Vector3(); + +/** + * @typedef {'idle'|'aim'|'cast'|'waiting'|'bite'|'fight'|'won'|'lost'} FishingPhase + */ + +export class FishingController { + /** + * @param {{ + * scene: import('three').Scene, + * character: object, + * camera: import('three').Camera, + * mouseAim: object, + * combatFocus?: object, + * assets?: object, + * terrain?: { sample?: (x:number,z:number)=>number }, + * waterY?: number, + * onToast?: (s: string) => void, + * onCatch?: (loot: object) => void, + * getEquippedWeaponId?: () => string|null, + * getRaceId?: () => string|null + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.character = opts.character; + this.camera = opts.camera; + this.mouseAim = opts.mouseAim; + this.combatFocus = opts.combatFocus || null; + this.assets = opts.assets || null; + this.terrain = opts.terrain || null; + this.waterY = opts.waterY ?? WORLD.waterY ?? -0.04; + this.onToast = opts.onToast || (() => {}); + this.onCatch = opts.onCatch || (() => {}); + this.getEquippedWeaponId = opts.getEquippedWeaponId || (() => null); + this.getRaceId = opts.getRaceId || (() => null); + + this.prof = loadProfessionState(); + this._elapsed = 0; + + this.ui = new FishingUi(); + this.phase = /** @type {FishingPhase} */ ('idle'); + this.aiming = false; + this.poleId = this.prof.poleId || 't0-fishing-pole'; + this.lureId = this.prof.lureId || 'lure_basic'; + this.fight = null; + this.pendingFish = null; + this.biteTimer = 0; + this.castPos = new Vector3(); + this._lureMesh = null; + this._fishMesh = null; + this._line = null; + this._poleAttach = null; + this.group = new Group(); + this.group.name = 'FishingRuntime'; + this.scene.add(this.group); + + this._input = { + reelIn: false, + slack: false, + moveRight: 0, + moveLeft: 0, + snag: false, + lmbHeld: false, + rmbHeld: false + }; + this._lureBobT = 0; + } + + /** Combined tree + rod + meal modifiers */ + getMods() { + // Keep pole id in sync with equipped / profession state + if (this.prof.poleId) this.poleId = this.prof.poleId; + return resolveProfessionMods(this.prof, this._elapsed); + } + + /** Freeride / boat / swim speed multiplier (skill tree + meals + rod) */ + getNauticalSpeedMul() { + return this.getMods().nauticalSpeedMul || 1; + } + + /** True when pole is main-hand fishing tool */ + isPoleEquipped() { + const id = String(this.getEquippedWeaponId() || '').toLowerCase(); + if (id.includes('fish') || id.includes('pole') || id === 't0-fishing-pole') return true; + return FISHING_POLES.some((p) => p.id === id); + } + + get active() { + return this.phase !== 'idle' || this.aiming; + } + + get fishingSkill() { + return this.prof.level || 1; + } + + /** Profession mode: player chose to fish (or pole out near water) */ + beginProfession() { + const eq = this.getEquippedWeaponId?.(); + if (eq && (String(eq).toLowerCase().includes('fish') || FISHING_POLES.some((p) => p.id === eq))) { + this.poleId = eq; + this.prof.poleId = eq; + saveProfessionState(this.prof); + } else if (!this.isPoleEquipped()) { + this.poleId = this.prof.poleId || 't0-fishing-pole'; + this.onToast('Fishing lab pole · equip t0-fishing-pole when catalog ready'); + } + this.phase = 'aim'; + this.aiming = true; + try { + this.combatFocus.focusEnabled = true; + this.combatFocus.emit?.('focus', true); + } catch { + /* optional */ + } + this.ui.setActive(true); + this._renderHud('aim'); + const mods = this.getMods(); + this.onToast( + `Fishing Lv${mods.level} · nautical ×${mods.nauticalSpeedMul.toFixed(2)} · LMB cast · S snag` + ); + void this._ensurePoleMesh(); + return true; + } + + async _ensurePoleMesh() { + if (this._poleAttach || !this.assets?.loadGLTF) return; + const pole = poleById(this.poleId); + try { + const gltf = await this.assets.loadGLTF(pole.meshUrl); + const root = (gltf.scene || gltf.scenes?.[0])?.clone?.(true); + if (!root) return; + root.name = 'FishingPoleAttach'; + applyPoleWorldScale(root, pole.poleLengthM ?? rodById(this.poleId)?.poleLengthM ?? 1.6); + const hand = + this.character?.equipment?.findBones?.()?.rHand || + this.character?.bones?.rHand; + if (hand) { + hand.add(root); + root.position.set(0.02, 0.05, 0.08); + root.rotation.set(-0.4, 0.2, 0.1); + } else { + this.character?.root?.add?.(root); + root.position.set(0.25, 1.1, 0.15); + } + this._poleAttach = root; + } catch (e) { + console.warn('[Fishing] pole mesh', e); + } + } + + /** Equip lure if rod lureSlotTier allows */ + setLureId(id) { + const lure = lureById(id); + if (!lure) return false; + const pole = poleById(this.poleId); + const maxTier = pole.lureSlotTier ?? pole.tier ?? 0; + if ((lure.tier || 0) > maxTier) { + this.onToast(`${lure.label} needs rod lure tier ${lure.tier}+ (this rod: ${maxTier})`); + return false; + } + this.lureId = id; + this.prof.lureId = id; + saveProfessionState(this.prof); + this.onToast(`Lure · ${lure.label} · sizes ${lure.sizeClass?.join('/') || '?'}`); + return true; + } + + endProfession() { + this.phase = 'idle'; + this.aiming = false; + this.fight = null; + this.pendingFish = null; + this._clearMeshes(); + this.ui.setActive(false); + this.ui.render({ phase: 'idle' }); + } + + toggleAim() { + if (!this.isPoleEquipped() && this.phase === 'idle') { + // Lab allow Shift+F path even without equip + } + if (this.phase === 'fight' || this.phase === 'bite' || this.phase === 'waiting') return; + if (this.phase === 'aim' || this.aiming) { + this.aiming = false; + this.phase = 'idle'; + this.ui.setActive(false); + this.onToast('Fishing aim off'); + } else { + this.beginProfession(); + } + } + + onPrimaryDown() { + if (this.phase === 'fight') { + this._input.lmbHeld = true; + return true; + } + if (this.phase === 'aim' || this.aiming) { + this.castLure(); + return true; + } + return false; + } + + onPrimaryUp() { + this._input.lmbHeld = false; + } + + onSecondaryDown() { + if (this.phase === 'bite') { + this._input.snag = true; + return true; + } + if (this.phase === 'fight') { + this._input.rmbHeld = true; + return true; + } + if (this.phase === 'idle' || this.phase === 'aim') { + this.toggleAim(); + return true; + } + return false; + } + + onSecondaryUp() { + this._input.rmbHeld = false; + } + + onSnagKey() { + if (this.phase === 'bite') { + this._input.snag = true; + return true; + } + return false; + } + + onWheel(deltaY) { + if (this.phase !== 'fight') return false; + if (deltaY > 0) { + this._input.reelIn = true; + this._input.slack = false; + } else if (deltaY < 0) { + this._input.slack = true; + this._input.reelIn = false; + } + this._wheelPulse = 0.08; + return true; + } + + /** Set rod from profession UI / equip */ + setPoleId(id) { + if (!poleById(id)) return false; + this.poleId = id; + this.prof.poleId = id; + saveProfessionState(this.prof); + // Force remount next begin + if (this._poleAttach) { + this._poleAttach.removeFromParent?.(); + this._poleAttach = null; + } + return true; + } + + unlockNode(nodeId) { + const ok = unlockTreeNode(this.prof, nodeId); + if (ok) this.onToast(`Skill · ${nodeId}`); + return ok; + } + + eatMeal(mealId) { + const ok = eatMeal(this.prof, mealId, this._elapsed); + if (ok) { + const m = this.getMods(); + this.onToast(`Meal · nautical ×${m.nauticalSpeedMul.toFixed(2)}`); + this.ui.renderMealBuffs?.(m.mealBuffs, this.prof.meals); + } + return ok; + } + + castLure() { + const mods = this.getMods(); + const pole = poleById(this.poleId); + const abilities = pole.abilities || []; + const range = computeCastRange(pole, abilities); + const feet = this.character.getWorldPosition?.(_v) || this.character.position; + const aim = this.mouseAim?.point || feet; + const dx = aim.x - feet.x; + const dz = aim.z - feet.z; + const dist = Math.hypot(dx, dz); + if (dist < 1.2) { + this.onToast('Aim farther on the water'); + return; + } + if (dist > range) { + this.onToast(`Out of cast range (${range.toFixed(0)} m)`); + return; + } + const R = WORLD.islandRadius; + const rAim = Math.hypot(aim.x, aim.z); + if (rAim < R * 0.55) { + this.onToast('Cast toward lake / sea (shore or open water)'); + } + + const t = Math.min(1, range / Math.max(dist, 0.01)); + this.castPos.set(feet.x + dx * t, this.waterY + 0.05, feet.z + dz * t); + const landY = this.terrain?.sample?.(this.castPos.x, this.castPos.z); + if (Number.isFinite(landY) && landY > this.waterY + 0.35 && rAim < R * 0.85) { + this.castPos.y = this.waterY + 0.05; + } + + this.phase = 'cast'; + this.ui.setActive(true); + this._renderHud('cast'); + this._spawnLure(this.castPos); + this.character.requestOneShot?.('cast') || this.character.playWeaponCombat?.('cast'); + + setTimeout(() => { + if (this.phase !== 'cast') return; + this.phase = 'waiting'; + const lure = lureById(this.lureId); + const biteMul = (lure.biteMul || 1) * (mods.biteMul || 1); + const wait = (2.2 + Math.random() * 4.5) / biteMul; + this.biteTimer = wait; + this._renderHud('waiting', { hint: 'Waiting… keep still' }); + this.onToast('Lure in the water…'); + }, 450); + } + + _spawnLure(pos) { + this._clearLureOnly(); + const lure = lureById(this.lureId); + // Prefer GLB lure; fallback orange sphere + void this._spawnLureMesh(pos, lure); + } + + async _spawnLureMesh(pos, lure) { + this._clearLureOnly(); + if (this.assets?.loadGLTF && lure?.meshUrl) { + try { + const gltf = await this.assets.loadGLTF(lure.meshUrl); + const root = (gltf.scene || gltf.scenes?.[0])?.clone?.(true); + if (root) { + applyLureWorldScale(root, lure.meshLengthM ?? 0.12); + root.position.copy(pos); + root.name = 'FishingLure'; + this._lureMesh = root; + this.group.add(root); + return; + } + } catch (e) { + console.warn('[Fishing] lure mesh', e); + } + } + const geo = new SphereGeometry(0.06, 10, 10); + const mat = new MeshStandardMaterial({ + color: 0xff6a22, + emissive: 0x441100, + emissiveIntensity: 0.4, + roughness: 0.45 + }); + this._lureMesh = new Mesh(geo, mat); + this._lureMesh.position.copy(pos); + this._lureMesh.name = 'FishingLure'; + this.group.add(this._lureMesh); + } + + async _spawnFishVisual(species) { + this._clearFishOnly(); + if (!this.assets?.loadGLTF || !species?.meshUrl) return; + try { + const gltf = await this.assets.loadGLTF(species.meshUrl); + const root = (gltf.scene || gltf.scenes?.[0])?.clone?.(true); + if (!root) return; + // SI water size: lengthM, elongated silhouette + applyFishWorldScale(root, species); + const len = resolveLengthM(species); + root.position.copy(this.castPos); + // Sit slightly under surface; titans deeper + const depth = Math.min(0.45, 0.06 + len * 0.04); + root.position.y = this.waterY - depth * 0.35 + 0.05; + root.name = `Fish_${species.id}`; + this._fishMesh = root; + this.group.add(root); + } catch (e) { + console.warn('[Fishing] fish mesh', e); + } + } + + _clearLureOnly() { + if (this._lureMesh) { + this.group.remove(this._lureMesh); + // Sphere fallback only + this._lureMesh.geometry?.dispose?.(); + this._lureMesh.material?.dispose?.(); + this._lureMesh = null; + } + } + + _clearFishOnly() { + if (this._fishMesh) { + this.group.remove(this._fishMesh); + this._fishMesh = null; + } + } + + _clearMeshes() { + this._clearLureOnly(); + this._clearFishOnly(); + } + + _renderHud(phase, extra = {}) { + const mods = this.getMods(); + this.ui.render({ + phase, + fight: this.fight, + prof: { + level: mods.level, + xp: this.prof.xp, + skillPoints: this.prof.skillPoints, + pole: rodById(this.poleId)?.label || this.poleId, + lure: lureById(this.lureId)?.label || this.lureId, + nautical: mods.nauticalSpeedMul, + maxSize: mods.maxSizeRank, + meals: this.prof.meals + }, + ...extra + }); + } + + /** + * @param {number} dt + * @param {Set} keys + */ + update(dt, keys) { + this._elapsed += dt; + // Tick meal expiry quietly + if (this._elapsed % 2 < dt) { + resolveProfessionMods(this.prof, this._elapsed); + } + + if (this.phase === 'idle' && !this.aiming) return; + + if (this.phase === 'fight') { + this._input.moveRight = this._input.lmbHeld || keys?.has?.('KeyD') ? 1 : 0; + this._input.moveLeft = this._input.rmbHeld || keys?.has?.('KeyA') ? 1 : 0; + if (keys?.has?.('KeyW')) this._input.reelIn = true; + if (keys?.has?.('KeyS') && this.phase === 'fight') this._input.slack = true; + } + + if (this.phase === 'waiting') { + this.biteTimer -= dt; + this._lureBobT += dt; + if (this._lureMesh) { + this._lureMesh.position.y = this.waterY + 0.04 + Math.sin(this._lureBobT * 3) * 0.03; + } + if (this.biteTimer <= 0) { + const mods = this.getMods(); + const fish = rollFishSpecies({ + lureId: this.lureId, + poleId: this.poleId, + fishingSkill: this.prof.level, + rareBias: mods.rareBias, + legendaryBias: mods.legendaryBias, + treeMaxSizeRank: mods.tree?.maxSizeRank || mods.maxSizeRank || 1 + }); + this.pendingFish = fish; + const pole = poleById(this.poleId); + const rodMods = mods.rodMods || {}; + let biteWin = rodMods.biteWindowS || 0.75; + biteWin += Math.max(0, 0.2 - fish.difficulty * 0.15); + const treeZone = mods.tree?.zoneMul || 1; + // Bigger fish pull harder / slightly narrower zone already in catalog + this.fight = createFightState({ + zoneWidth: computeReelZoneWidth(pole, fish, pole.abilities, treeZone), + fishSpeed: fish.speed, + fishStrength: fish.strength, + lineMax: computeLineMax(pole, pole.abilities, mods.tree?.lineMul || 1), + control: pole.control || 1, + reelSpeed: rodMods.reelSpeed || 1, + preyOfLeviathans: !!fish.preyOfLeviathans, + behavior: fish.behavior || null, + hardCatch: !!fish.hardCatch + }); + beginBite(this.fight, biteWin); + this.phase = 'bite'; + this._renderHud('bite', { hint: 'BITE! S or RMB to snag' }); + const len = resolveLengthM(fish); + let toast = `Bite! ${fish.label} · ${fish.sizeClass} · ${len.toFixed(2)} m · ₡${fish.value}`; + if (fish.preyOfLeviathans) toast += ' · ⚠ leviathans hunt this'; + this.onToast(toast); + void this._spawnFishVisual(fish); + } else { + this._renderHud('waiting'); + } + } else if (this.phase === 'bite' || this.phase === 'fight') { + if (this.phase === 'bite' && this.fight) { + stepFight(this.fight, dt, { snag: this._input.snag }); + this._input.snag = false; + if (this.fight.phase === 'fight') { + this.phase = 'fight'; + this.onToast('Hooked! Wheel down reel · up slack · LMB/RMB zone'); + } else if (this.fight.phase === 'lost') { + this._lose(this.fight._loseReason); + } + } else if (this.phase === 'fight' && this.fight) { + const wheelOn = (this._wheelPulse || 0) > 0; + if (wheelOn) this._wheelPulse -= dt; + const mods = this.getMods(); + const reelMul = mods.rodMods?.reelSpeed || 1; + stepFight(this.fight, dt, { + reelIn: this._input.reelIn || (wheelOn && this._input.reelIn), + slack: this._input.slack, + moveRight: this._input.moveRight, + moveLeft: this._input.moveLeft, + reelSpeedMul: reelMul + }); + if (this.fight.leviathanEvent) { + this.onToast(this.fight.leviathanEvent); + this.fight.leviathanEvent = null; + } + if (!wheelOn) { + this._input.reelIn = keys?.has?.('KeyW') || false; + this._input.slack = keys?.has?.('KeyS') || false; + } + if (this.fight.phase === 'won') this._win(); + else if (this.fight.phase === 'lost') this._lose(this.fight._loseReason); + } + this._renderHud(this.phase); + } else if (this.phase === 'aim') { + this.ui.setActive(true); + this._renderHud('aim'); + } + + if (this._fishMesh && this.fight && this.phase === 'fight') { + const feet = this.character.getWorldPosition?.(_v) || this.character.position; + const t = this.fight.progress * 0.35; + this._fishMesh.position.lerpVectors(this.castPos, feet, t); + this._fishMesh.position.y = this.waterY + 0.1; + this._fishMesh.rotation.y += dt * 2; + } + } + + _win() { + const fish = this.pendingFish; + const mods = this.getMods(); + this.phase = 'won'; + const w = + fish.weightKg[0] + Math.random() * (fish.weightKg[1] - fish.weightKg[0]); + // Titan/huge never multi-catch + let qty = Math.max(1, Math.floor(mods.catchQty || 1)); + if (fish.sizeClass === 'huge' || fish.sizeClass === 'titan') qty = 1; + const len = resolveLengthM(fish); + const loot = { + id: `raw_${fish.id}`, + name: fish.label, + speciesId: fish.id, + weightKg: +w.toFixed(2), + lengthM: len, + sizeClass: fish.sizeClass, + rarity: fish.rarity, + value: fish.value * qty, + meshUrl: fish.meshUrl, + profession: 'fishing', + qty + }; + this._renderHud('won', { + catchLabel: `${fish.label} ×${qty} · ${len.toFixed(2)} m · ${loot.weightKg} kg · ${fish.rarity} · ₡${loot.value}` + }); + this.onToast(`Caught ${fish.label} (${len.toFixed(2)} m · ${loot.weightKg} kg)!`); + this.onCatch(loot); + + // Super-rare recipe / form / mount unlocks (Aetherwing turtle etc.) + try { + const raceId = + this.getRaceId?.() || + this.character?.raceId || + this.character?.race || + null; + const rw = applyCatchRewards(fish.id, { raceId }); + for (const m of rw.messages) this.onToast(m); + if (rw.unlocked.length) loot.unlocks = rw.unlocked; + } catch (e) { + console.warn('[Fishing] rewards', e); + } + + grantFishingXp(this.prof, catchXp(fish, qty)); + this.onToast(`Fishing Lv${this.prof.level} · SP ${this.prof.skillPoints}`); + + setTimeout(() => { + this._clearMeshes(); + this.phase = 'aim'; + this.aiming = true; + this.fight = null; + this.pendingFish = null; + this._renderHud('aim', { hint: 'Cast again · LMB' }); + }, 2200); + } + + _lose(reason) { + this.phase = 'lost'; + const msg = + reason === 'line_broke' + ? 'Line snapped!' + : reason === 'missed_snag' + ? 'Missed the snag' + : reason === 'leviathan_stole' + ? 'Leviathan stole your catch!' + : 'Got away'; + this.onToast(msg); + this._renderHud('lost', { catchLabel: msg }); + // Small fail XP so progress isn't zero + grantFishingXp(this.prof, 3); + setTimeout(() => { + this._clearMeshes(); + this.phase = 'aim'; + this.aiming = true; + this.fight = null; + this.pendingFish = null; + this._renderHud('aim'); + }, 1600); + } + + dispose() { + this.endProfession(); + this.ui.dispose(); + this.group.removeFromParent(); + } +} + +export default FishingController; diff --git a/src/fishing/dockBuild.js b/src/fishing/dockBuild.js new file mode 100644 index 0000000..d5a0313 --- /dev/null +++ b/src/fishing/dockBuild.js @@ -0,0 +1,177 @@ +/** + * Fishing docks as buildables — boat housing + claim-flag upgrade gates. + * + * T1 dock_t1: placeable as building anywhere (lab) to berth a boat. + * T2 / T3 upgrades: only when player is **inside own claim flag** (Open camp SSOT). + * + * Lab claim: localStorage grudge.casting.claim.v1 { planted: bool, x, z, radiusM, owner: 'self' } + * Production: Open camp claim_flag within radius (gameopen CampBuildSystem). + * + * @see fishingCatalog.js FISHING_BUILDABLES + * @see gameopen docs/CAMP_CLAIM_FLAG.md + */ + +const CLAIM_KEY = 'grudge.casting.claim.v1'; +const DOCK_KEY = 'grudge.casting.docks.v1'; + +/** + * @typedef {object} DockDef + * @property {string} id + * @property {string} label + * @property {string} meshUrl + * @property {number} tier 1|2|3 + * @property {number} spanM + * @property {boolean} boats can house boat + * @property {number} boatSlots + * @property {boolean} requiresOwnClaim T2/T3 upgrade gate + * @property {string|null} upgradesFrom + * @property {string} blurb + */ + +export const DOCK_TIERS = Object.freeze(/** @type {DockDef[]} */ ([ + { + id: 'dock_t1', + label: 'Dock T1 (Berth)', + meshUrl: './models/fish/docks/dock_t1.glb', + tier: 1, + spanM: 8, + boats: true, + boatSlots: 1, + requiresOwnClaim: false, + upgradesFrom: null, + blurb: 'Buildable building · place to house a boat · no claim required' + }, + { + id: 'dock_t2', + label: 'Dock T2 (Harbor)', + meshUrl: './models/fish/docks/dock_t2.glb', + tier: 2, + spanM: 12, + boats: true, + boatSlots: 2, + requiresOwnClaim: true, + upgradesFrom: 'dock_t1', + blurb: 'Upgrade inside **own claim flag** only' + }, + { + id: 'dock_t3', + label: 'Dock T3 (Port)', + meshUrl: './models/fish/docks/dock_t3.glb', + tier: 3, + spanM: 16, + boats: true, + boatSlots: 4, + requiresOwnClaim: true, + upgradesFrom: 'dock_t2', + blurb: 'Max berth · own claim flag required' + } +])); + +export function dockById(id) { + return DOCK_TIERS.find((d) => d.id === id) || null; +} + +/** Lab: plant a self claim at xz (dev tool / future build radial). */ +export function plantOwnClaim(x, z, radiusM = 40) { + const c = { planted: true, x, z, radiusM, owner: 'self', at: Date.now() }; + try { + localStorage.setItem(CLAIM_KEY, JSON.stringify(c)); + } catch { + /* */ + } + return c; +} + +export function loadOwnClaim() { + try { + const j = JSON.parse(localStorage.getItem(CLAIM_KEY) || 'null'); + return j?.planted ? j : null; + } catch { + return null; + } +} + +/** + * @param {number} x + * @param {number} z + * @param {{ claim?: object|null, getIsInsideOwnClaim?: (x:number,z:number)=>boolean }} [ctx] + */ +export function isInsideOwnClaim(x, z, ctx = {}) { + if (typeof ctx.getIsInsideOwnClaim === 'function') { + try { + return !!ctx.getIsInsideOwnClaim(x, z); + } catch { + /* fall through */ + } + } + const c = ctx.claim || loadOwnClaim(); + if (!c?.planted) return false; + const dx = x - c.x; + const dz = z - c.z; + return Math.hypot(dx, dz) <= (c.radiusM || 40); +} + +/** + * Can place or upgrade dock at world xz? + * @param {string} dockId + * @param {number} x + * @param {number} z + * @param {object} [ctx] + * @returns {{ ok: boolean, reason?: string, dock?: DockDef }} + */ +export function canPlaceOrUpgradeDock(dockId, x, z, ctx = {}) { + const dock = dockById(dockId); + if (!dock) return { ok: false, reason: 'Unknown dock' }; + if (dock.requiresOwnClaim && !isInsideOwnClaim(x, z, ctx)) { + return { + ok: false, + reason: `${dock.label} requires standing inside your own claim flag`, + dock + }; + } + if (dock.upgradesFrom) { + const placed = listPlacedDocks(); + const hasBase = placed.some( + (p) => p.dockId === dock.upgradesFrom || (dock.tier === 3 && p.dockId === 'dock_t2') + ); + // Lab: allow direct place of T2/T3 if in claim without existing T1 (dev); production should require upgrade path + if (!hasBase && !ctx.allowSkipUpgrade) { + // soft: still allow if claim OK — building catalog can require materials + } + } + return { ok: true, dock }; +} + +export function listPlacedDocks() { + try { + return JSON.parse(localStorage.getItem(DOCK_KEY) || '[]'); + } catch { + return []; + } +} + +/** + * @param {{ dockId: string, x: number, y?: number, z: number }} place + * @param {object} [ctx] + */ +export function placeDock(place, ctx = {}) { + const check = canPlaceOrUpgradeDock(place.dockId, place.x, place.z, ctx); + if (!check.ok) return check; + const list = listPlacedDocks(); + const row = { + id: `dock_${Date.now().toString(36)}`, + dockId: place.dockId, + x: place.x, + y: place.y ?? 0, + z: place.z, + boatSlots: check.dock?.boatSlots || 1, + at: Date.now() + }; + list.push(row); + try { + localStorage.setItem(DOCK_KEY, JSON.stringify(list)); + } catch { + /* */ + } + return { ok: true, dock: check.dock, placed: row }; +} diff --git a/src/fishing/fishScale.js b/src/fishing/fishScale.js new file mode 100644 index 0000000..80b5d97 --- /dev/null +++ b/src/fishing/fishScale.js @@ -0,0 +1,138 @@ +/** + * SI fish mesh fit — longer than tall/wide, sized for water play. + * + * Grudge rule: 1 unit = 1 m. Fish use **lengthM** (nose→tail / longest swim axis). + * Never cube-fit to max(x,y,z) alone without preferring horizontal length, and + * never leave a pack mesh as a 2 m tall goldfish or a 5 cm shark. + * + * @see fishingCatalog.js · docs/FISHING_PROFESSION_SSOT.md + */ + +import { Box3, Vector3 } from 'three'; + +const _box = new Box3(); +const _size = new Vector3(); +const _center = new Vector3(); + +/** + * Size class → typical SI length band (m) for catalog defaults. + * @type {Record} + */ +export const SIZE_CLASS = Object.freeze({ + tiny: { min: 0.06, max: 0.14, label: 'Tiny' }, + small: { min: 0.15, max: 0.35, label: 'Small' }, + medium: { min: 0.4, max: 0.9, label: 'Medium' }, + large: { min: 1.0, max: 2.2, label: 'Large' }, + huge: { min: 2.3, max: 4.5, label: 'Huge' }, + titan: { min: 5.0, max: 12, label: 'Titan' } +}); + +/** Rank for skill-tree / rod gates (higher = bigger game) */ +export const SIZE_RANK = Object.freeze({ + tiny: 0, + small: 1, + medium: 2, + large: 3, + huge: 4, + titan: 5 +}); + +/** + * After load: uniform scale to lengthM on longest axis, then mild stretch so + * swim length ≥ height and ≥ width (elongated fish silhouette). + * + * @param {import('three').Object3D} root + * @param {{ lengthM?: number, lengthAspect?: number, sizeClass?: string }} species + */ +export function applyFishWorldScale(root, species = {}) { + if (!root) return root; + const lengthM = resolveLengthM(species); + const aspect = species.lengthAspect ?? 1.25; + + root.updateMatrixWorld(true); + _box.setFromObject(root); + _box.getSize(_size); + const rawMax = Math.max(_size.x, _size.y, _size.z, 1e-4); + const u = lengthM / rawMax; + root.scale.multiplyScalar(u); + + // Prefer elongated swim shape: longest horizontal ≥ height * aspect and ≥ lateral width + root.updateMatrixWorld(true); + _box.setFromObject(root); + _box.getSize(_size); + const h = Math.max(_size.y, 1e-4); + const sx = Math.max(_size.x, 1e-4); + const sz = Math.max(_size.z, 1e-4); + const longH = Math.max(sx, sz); + const shortH = Math.min(sx, sz); + const needVsHeight = h * aspect; + const needVsWidth = shortH * aspect; + const needLen = Math.max(longH, needVsHeight, needVsWidth, lengthM * 0.98); + if (needLen > longH * 1.02) { + const stretch = needLen / longH; + // Stretch along dominant horizontal axis in local scale + if (sx >= sz) root.scale.x *= stretch; + else root.scale.z *= stretch; + } + + // Final uniform nudge if longest dim drifted from lengthM + root.updateMatrixWorld(true); + _box.setFromObject(root); + _box.getSize(_size); + const finalMax = Math.max(_size.x, _size.y, _size.z, 1e-4); + if (Math.abs(finalMax - lengthM) / lengthM > 0.12) { + root.scale.multiplyScalar(lengthM / finalMax); + } + + return root; +} + +/** + * @param {{ lengthM?: number, sizeClass?: string, weightKg?: [number, number] }} species + */ +export function resolveLengthM(species = {}) { + if (Number.isFinite(species.lengthM) && species.lengthM > 0) return species.lengthM; + const cls = species.sizeClass && SIZE_CLASS[species.sizeClass]; + if (cls) return (cls.min + cls.max) * 0.5; + // weight fallback: rough cube-root of mid kg → meters (lab heuristic) + const w = species.weightKg; + if (Array.isArray(w) && w.length >= 2) { + const mid = (w[0] + w[1]) * 0.5; + return Math.min(10, Math.max(0.08, 0.35 * Math.cbrt(Math.max(0.01, mid)))); + } + return 0.35; +} + +/** + * Lure mesh SI size (small bobber / bait — not fish-scale). + * @param {import('three').Object3D} root + * @param {number} [lengthM=0.12] + */ +export function applyLureWorldScale(root, lengthM = 0.12) { + if (!root) return root; + root.updateMatrixWorld(true); + _box.setFromObject(root); + _box.getSize(_size); + const rawMax = Math.max(_size.x, _size.y, _size.z, 1e-4); + root.scale.multiplyScalar(lengthM / rawMax); + return root; +} + +/** + * Pole mesh SI — rod length along primary axis (~1.4–2.2 m by tier). + * @param {import('three').Object3D} root + * @param {number} [lengthM=1.6] + */ +export function applyPoleWorldScale(root, lengthM = 1.6) { + if (!root) return root; + root.updateMatrixWorld(true); + _box.setFromObject(root); + _box.getSize(_size); + const rawMax = Math.max(_size.x, _size.y, _size.z, 1e-4); + root.scale.multiplyScalar(lengthM / rawMax); + return root; +} + +export function sizeRank(sizeClass) { + return SIZE_RANK[sizeClass] ?? 1; +} diff --git a/src/fishing/fishingCatalog.js b/src/fishing/fishingCatalog.js new file mode 100644 index 0000000..f3e4ba1 --- /dev/null +++ b/src/fishing/fishingCatalog.js @@ -0,0 +1,515 @@ +/** + * Fishing profession catalog — poles, lures, fish, buildables. + * + * Assets (no root clutter): + * - public/models/fish/species/ reef pack (v1) + * - public/models/fish/species/large/ game pack (v2 zip) — dolphin, shark, whale… + * - public/models/fish/poles|lures|docks + * + * SI: each species has lengthM + sizeClass; runtime fit via fishScale.js + * (longer than tall/wide). Lures/rods gate size class + profession values. + * + * @see docs/FISHING_PROFESSION_SSOT.md · fishScale.js · fishingRodTypes.js + */ + +import { + ROD_TYPES, + ROD_ABILITIES, + resolveRodMods, + rodById as rodTypeById +} from './fishingRodTypes.js'; +import { sizeRank } from './fishScale.js'; + +const FISH = './models/fish/species'; +const FISH_LG = './models/fish/species/large'; +const FISH_RARE = './models/fish/species/rare'; +const LURE = './models/fish/lures'; +const DOCK = './models/fish/docks'; + +/** + * @typedef {object} FishSpecies + * @property {string} id + * @property {string} label + * @property {string} meshUrl + * @property {'common'|'uncommon'|'rare'|'epic'|'legendary'} rarity + * @property {[number, number]} weightKg + * @property {number} lengthM SI nose→tail (water size) + * @property {'tiny'|'small'|'medium'|'large'|'huge'|'titan'} sizeClass + * @property {number} strength + * @property {number} speed + * @property {number} stamina + * @property {number} difficulty + * @property {number} zoneWidthBase + * @property {string[]} preferredLures + * @property {number} value profession coin / craft value + * @property {number} [xpMul] + * @property {number} [minLevel] + * @property {number} [minRodTier] + * @property {string} [pack] reef|game + */ + +/** @type {readonly FishSpecies[]} */ +export const FISH_SPECIES = Object.freeze([ + // —— Reef pack (species/) — SI length for aquarium/shore sizes —— + { id: 'tetra', label: 'Tetra', meshUrl: `${FISH}/tetra.glb`, pack: 'reef', rarity: 'common', sizeClass: 'tiny', lengthM: 0.08, weightKg: [0.02, 0.1], strength: 0.15, speed: 0.55, stamina: 12, difficulty: 0.15, zoneWidthBase: 0.24, preferredLures: ['worm', 'lure_basic'], value: 2, minLevel: 1, minRodTier: 0 }, + { id: 'goldfish', label: 'Goldfish', meshUrl: `${FISH}/goldfish.glb`, pack: 'reef', rarity: 'common', sizeClass: 'tiny', lengthM: 0.12, weightKg: [0.05, 0.25], strength: 0.2, speed: 0.4, stamina: 14, difficulty: 0.15, zoneWidthBase: 0.24, preferredLures: ['worm'], value: 3, minLevel: 1, minRodTier: 0 }, + { id: 'betta', label: 'Betta', meshUrl: `${FISH}/betta.glb`, pack: 'reef', rarity: 'common', sizeClass: 'tiny', lengthM: 0.1, weightKg: [0.03, 0.12], strength: 0.22, speed: 0.5, stamina: 15, difficulty: 0.2, zoneWidthBase: 0.22, preferredLures: ['lure_basic', 'worm'], value: 5, minLevel: 1, minRodTier: 0 }, + { id: 'clownfish', label: 'Clownfish', meshUrl: `${FISH}/clownfish.glb`, pack: 'reef', rarity: 'common', sizeClass: 'small', lengthM: 0.14, weightKg: [0.1, 0.4], strength: 0.25, speed: 0.45, stamina: 18, difficulty: 0.2, zoneWidthBase: 0.22, preferredLures: ['worm', 'lure_basic'], value: 4, minLevel: 1, minRodTier: 0 }, + { id: 'blue_tang', label: 'Blue Tang', meshUrl: `${FISH}/blue_tang.glb`, pack: 'reef', rarity: 'uncommon', sizeClass: 'small', lengthM: 0.28, weightKg: [0.3, 1.2], strength: 0.4, speed: 0.55, stamina: 22, difficulty: 0.35, zoneWidthBase: 0.18, preferredLures: ['lure_basic', 'lure_spinner'], value: 12, minLevel: 5, minRodTier: 0 }, + { id: 'yellow_tang', label: 'Yellow Tang', meshUrl: `${FISH}/yellow_tang.glb`, pack: 'reef', rarity: 'uncommon', sizeClass: 'small', lengthM: 0.26, weightKg: [0.25, 1.0], strength: 0.38, speed: 0.58, stamina: 20, difficulty: 0.34, zoneWidthBase: 0.18, preferredLures: ['lure_spinner'], value: 11, minLevel: 5, minRodTier: 0 }, + { id: 'puffer', label: 'Puffer', meshUrl: `${FISH}/puffer.glb`, pack: 'reef', rarity: 'uncommon', sizeClass: 'small', lengthM: 0.3, weightKg: [0.4, 1.5], strength: 0.5, speed: 0.3, stamina: 24, difficulty: 0.42, zoneWidthBase: 0.16, preferredLures: ['lure_basic', 'lure_spinner'], value: 15, minLevel: 8, minRodTier: 1 }, + { id: 'piranha', label: 'Piranha', meshUrl: `${FISH}/piranha.glb`, pack: 'reef', rarity: 'rare', sizeClass: 'small', lengthM: 0.35, weightKg: [0.5, 2.0], strength: 0.65, speed: 0.7, stamina: 26, difficulty: 0.55, zoneWidthBase: 0.14, preferredLures: ['lure_spinner', 'lure_deep'], value: 28, minLevel: 12, minRodTier: 1 }, + { id: 'lionfish', label: 'Lionfish', meshUrl: `${FISH}/lionfish.glb`, pack: 'reef', rarity: 'rare', sizeClass: 'small', lengthM: 0.38, weightKg: [0.4, 1.8], strength: 0.55, speed: 0.45, stamina: 30, difficulty: 0.58, zoneWidthBase: 0.13, preferredLures: ['lure_spinner'], value: 36, minLevel: 14, minRodTier: 1 }, + { id: 'koi', label: 'Koi', meshUrl: `${FISH}/koi.glb`, pack: 'reef', rarity: 'uncommon', sizeClass: 'medium', lengthM: 0.65, weightKg: [0.8, 3.5], strength: 0.45, speed: 0.35, stamina: 28, difficulty: 0.4, zoneWidthBase: 0.17, preferredLures: ['worm', 'lure_basic'], value: 18, minLevel: 10, minRodTier: 1 }, + { id: 'red_snapper', label: 'Red Snapper', meshUrl: `${FISH}/red_snapper.glb`, pack: 'reef', rarity: 'rare', sizeClass: 'medium', lengthM: 0.7, weightKg: [1.5, 6], strength: 0.6, speed: 0.5, stamina: 32, difficulty: 0.52, zoneWidthBase: 0.15, preferredLures: ['lure_deep', 'lure_spinner'], value: 32, minLevel: 15, minRodTier: 1 }, + { id: 'tuna', label: 'Tuna', meshUrl: `${FISH}/tuna.glb`, pack: 'reef', rarity: 'rare', sizeClass: 'large', lengthM: 1.6, weightKg: [5, 25], strength: 0.75, speed: 0.65, stamina: 40, difficulty: 0.65, zoneWidthBase: 0.12, preferredLures: ['lure_deep', 'lure_heavy', 'lure_game'], value: 48, xpMul: 1.2, minLevel: 20, minRodTier: 2 }, + { id: 'swordfish', label: 'Swordfish', meshUrl: `${FISH}/swordfish.glb`, pack: 'reef', rarity: 'epic', sizeClass: 'large', lengthM: 2.1, weightKg: [20, 90], strength: 0.88, speed: 0.8, stamina: 55, difficulty: 0.8, zoneWidthBase: 0.1, preferredLures: ['lure_heavy', 'lure_game'], value: 90, xpMul: 1.4, minLevel: 28, minRodTier: 3 }, + { id: 'shark', label: 'Reef Shark', meshUrl: `${FISH}/shark.glb`, pack: 'reef', rarity: 'epic', sizeClass: 'huge', lengthM: 2.6, weightKg: [40, 180], strength: 0.92, speed: 0.7, stamina: 60, difficulty: 0.85, zoneWidthBase: 0.09, preferredLures: ['lure_heavy', 'lure_game'], value: 120, xpMul: 1.5, minLevel: 32, minRodTier: 3 }, + { id: 'goblin_shark', label: 'Goblin Shark', meshUrl: `${FISH}/goblin_shark.glb`, pack: 'reef', rarity: 'legendary', sizeClass: 'huge', lengthM: 2.8, weightKg: [50, 200], strength: 0.95, speed: 0.55, stamina: 70, difficulty: 0.92, zoneWidthBase: 0.08, preferredLures: ['lure_heavy', 'lure_titan'], value: 200, xpMul: 1.8, minLevel: 40, minRodTier: 4 }, + { id: 'anglerfish', label: 'Anglerfish', meshUrl: `${FISH}/anglerfish.glb`, pack: 'reef', rarity: 'legendary', sizeClass: 'large', lengthM: 1.1, weightKg: [8, 40], strength: 0.85, speed: 0.4, stamina: 65, difficulty: 0.9, zoneWidthBase: 0.09, preferredLures: ['lure_deep', 'lure_heavy'], value: 180, xpMul: 1.7, minLevel: 38, minRodTier: 3 }, + + // —— Game pack (species/large/) — Animated Fish Bundle zip (1) —— + { id: 'fish_reef', label: 'Reef Runner', meshUrl: `${FISH_LG}/fish_reef.glb`, pack: 'game', rarity: 'common', sizeClass: 'medium', lengthM: 0.55, weightKg: [0.6, 2.5], strength: 0.42, speed: 0.52, stamina: 24, difficulty: 0.38, zoneWidthBase: 0.17, preferredLures: ['lure_basic', 'lure_spinner', 'worm'], value: 14, minLevel: 8, minRodTier: 1 }, + { id: 'fish_mid', label: 'Open-Water Fish', meshUrl: `${FISH_LG}/fish_mid.glb`, pack: 'game', rarity: 'uncommon', sizeClass: 'medium', lengthM: 0.75, weightKg: [1.2, 5], strength: 0.5, speed: 0.48, stamina: 28, difficulty: 0.45, zoneWidthBase: 0.16, preferredLures: ['lure_spinner', 'lure_deep'], value: 22, minLevel: 12, minRodTier: 1 }, + { id: 'fish_stream', label: 'Streamliner', meshUrl: `${FISH_LG}/fish_stream.glb`, pack: 'game', rarity: 'uncommon', sizeClass: 'medium', lengthM: 0.9, weightKg: [2, 8], strength: 0.55, speed: 0.62, stamina: 30, difficulty: 0.5, zoneWidthBase: 0.15, preferredLures: ['lure_deep', 'lure_spinner'], value: 30, minLevel: 15, minRodTier: 2 }, + { id: 'dolphin', label: 'Dolphin', meshUrl: `${FISH_LG}/dolphin.glb`, pack: 'game', rarity: 'epic', sizeClass: 'huge', lengthM: 2.4, weightKg: [80, 200], strength: 0.82, speed: 0.78, stamina: 52, difficulty: 0.78, zoneWidthBase: 0.1, preferredLures: ['lure_game', 'lure_heavy'], value: 140, xpMul: 1.55, minLevel: 30, minRodTier: 3 }, + { id: 'manta_ray', label: 'Manta Ray', meshUrl: `${FISH_LG}/manta_ray.glb`, pack: 'game', rarity: 'epic', sizeClass: 'huge', lengthM: 2.8, lengthAspect: 1.05, weightKg: [60, 160], strength: 0.8, speed: 0.42, stamina: 58, difficulty: 0.82, zoneWidthBase: 0.095, preferredLures: ['lure_game', 'lure_deep'], value: 155, xpMul: 1.6, minLevel: 34, minRodTier: 3 }, + { id: 'shark_game', label: 'Game Shark', meshUrl: `${FISH_LG}/shark_game.glb`, pack: 'game', rarity: 'legendary', sizeClass: 'huge', lengthM: 3.2, weightKg: [90, 280], strength: 0.94, speed: 0.72, stamina: 68, difficulty: 0.9, zoneWidthBase: 0.085, preferredLures: ['lure_heavy', 'lure_game', 'lure_titan'], value: 220, xpMul: 1.85, minLevel: 42, minRodTier: 4 }, + { id: 'whale', label: 'Whale', meshUrl: `${FISH_LG}/whale.glb`, pack: 'game', rarity: 'legendary', sizeClass: 'titan', lengthM: 8.5, weightKg: [2000, 12000], strength: 0.98, speed: 0.35, stamina: 90, difficulty: 0.97, zoneWidthBase: 0.07, preferredLures: ['lure_titan'], value: 500, xpMul: 2.5, minLevel: 50, minRodTier: 5 }, + + // —— Super rare / deep (species/rare/) — author meshes Documents + docks —— + { + id: 'pulbo_monstruo', + label: 'Pulbo Monstruo', + meshUrl: `${FISH_RARE}/pulbo_monstruo.glb`, + pack: 'rare', + rarity: 'rare', + sizeClass: 'large', + lengthM: 2.0, + weightKg: [25, 90], + strength: 0.78, + speed: 0.55, + stamina: 48, + difficulty: 0.72, + zoneWidthBase: 0.11, + preferredLures: ['lure_deep', 'lure_heavy'], + value: 95, + xpMul: 1.45, + minLevel: 28, + minRodTier: 3, + habitat: 'deep', + blurb: 'Deep rare · ink & tentacle craft' + }, + { + id: 'aetherwing_turtle', + label: 'Aetherwing Turtle', + meshUrl: `${FISH_RARE}/aetherwing_turtle.glb`, + pack: 'rare', + rarity: 'legendary', + sizeClass: 'huge', + lengthM: 3.4, + weightKg: [200, 800], + strength: 0.7, + speed: 0.28, + stamina: 75, + difficulty: 0.88, + zoneWidthBase: 0.09, + preferredLures: ['lure_game', 'lure_titan'], + value: 400, + xpMul: 2.2, + minLevel: 45, + minRodTier: 4, + superRare: true, + unlocksFormOrMount: true, + blurb: 'Super rare · Worge form recipe OR mount recipe' + }, + { + id: 'ocean_creature', + label: 'Ocean Creature', + meshUrl: `${FISH_RARE}/ocean_creature.glb`, + pack: 'rare', + rarity: 'legendary', + sizeClass: 'titan', + lengthM: 11, + weightKg: [5000, 25000], + strength: 0.35, + speed: 0.18, + stamina: 100, + difficulty: 0.75, + zoneWidthBase: 0.12, + preferredLures: ['lure_titan'], + value: 350, + xpMul: 2.0, + minLevel: 48, + minRodTier: 5, + behavior: 'passive_titan', + preyOfLeviathans: true, + superRare: true, + blurb: 'Passive titan · leviathans attack it during fight · huge' + }, + { + id: 'glow_whale', + label: 'Glow Whale', + meshUrl: `${FISH_RARE}/glow_whale.glb`, + pack: 'rare', + rarity: 'legendary', + sizeClass: 'titan', + lengthM: 10, + weightKg: [4000, 20000], + strength: 0.55, + speed: 0.22, + stamina: 110, + difficulty: 0.96, + zoneWidthBase: 0.075, + preferredLures: ['lure_titan'], + value: 480, + xpMul: 2.4, + minLevel: 50, + minRodTier: 5, + behavior: 'passive_titan', + preyOfLeviathans: true, + hardCatch: true, + superRare: true, + blurb: 'Huge · hard catch · leviathan prey · glow essence' + } +]); + +/** + * @typedef {object} FishingPoleDef + */ + +/** Poles = Grudge Angler rod types */ +export const FISHING_POLES = Object.freeze( + ROD_TYPES.map((r) => ({ + id: r.id, + label: r.label, + meshUrl: r.meshUrl, + tier: r.tier, + power: r.power, + control: r.control, + lineStrength: r.lineStrength, + castRangeM: r.castRangeM, + abilities: [...(r.abilities || [])], + reelSpeed: r.reelSpeed, + biteWindowS: r.biteWindowS, + nauticalSpeedMul: r.nauticalSpeedMul, + family: r.family, + rarity: r.rarity, + blurb: r.blurb, + poleLengthM: r.poleLengthM, + maxSizeRank: r.maxSizeRank, + maxFishLengthM: r.maxFishLengthM, + lureSlotTier: r.lureSlotTier, + animPack: 'magic', + weaponType: 'TOOL', + professions: ['fishing'] + })) +); + +/** + * Lures — sizeClass bias + habitat + profession value. + * Gameplay: wrong size bait → heavy weight penalty; preferred lure ×1.35. + * + * @typedef {object} LureDef + * @property {string} id + * @property {string} label + * @property {string} meshUrl + * @property {number} biteMul + * @property {Record} rarityBias + * @property {string[]} sizeClass which fish sizes this bait attracts + * @property {number} tier + * @property {number} meshLengthM SI bait visual + * @property {number} value craft/shop + * @property {number} [sinkRate] 0..1 deep preference + * @property {string} blurb + */ +export const FISHING_LURES = Object.freeze([ + { + id: 'worm', + label: 'Worm', + meshUrl: `${LURE}/worm.glb`, + biteMul: 1.2, + rarityBias: { common: 1.25 }, + sizeClass: ['tiny', 'small'], + tier: 0, + meshLengthM: 0.08, + value: 1, + sinkRate: 0.2, + blurb: 'Shore bait · tiny/small only' + }, + { + id: 'lure_basic', + label: 'Basic Lure', + meshUrl: `${LURE}/lure.glb`, + biteMul: 1.0, + rarityBias: {}, + sizeClass: ['tiny', 'small', 'medium'], + tier: 0, + meshLengthM: 0.1, + value: 3, + sinkRate: 0.35, + blurb: 'General shore/reef' + }, + { + id: 'lure_spinner', + label: 'Spinner', + meshUrl: `${LURE}/lure-2dkfirm95d.glb`, + biteMul: 1.12, + rarityBias: { uncommon: 1.35, rare: 1.15 }, + sizeClass: ['small', 'medium'], + tier: 1, + meshLengthM: 0.12, + value: 8, + sinkRate: 0.4, + blurb: 'Flash · small–medium predators' + }, + { + id: 'lure_deep', + label: 'Deep Lure', + meshUrl: `${LURE}/lure-3pcaourgtu.glb`, + biteMul: 0.95, + rarityBias: { rare: 1.4, epic: 1.2 }, + sizeClass: ['medium', 'large'], + tier: 2, + meshLengthM: 0.14, + value: 18, + sinkRate: 0.75, + blurb: 'Deep drop · snapper/tuna class' + }, + { + id: 'lure_heavy', + label: 'Heavy Lure', + meshUrl: `${LURE}/lure-h6fxsojx9b.glb`, + biteMul: 0.82, + rarityBias: { epic: 1.5, legendary: 1.35 }, + sizeClass: ['large', 'huge'], + tier: 3, + meshLengthM: 0.18, + value: 40, + sinkRate: 0.85, + blurb: 'Big game · sharks / sword' + }, + { + id: 'lure_game', + label: 'Game Rig', + meshUrl: `${LURE}/lure-jknxyvhxtd.glb`, + biteMul: 0.88, + rarityBias: { epic: 1.45, legendary: 1.25 }, + sizeClass: ['large', 'huge'], + tier: 4, + meshLengthM: 0.2, + value: 55, + sinkRate: 0.7, + blurb: 'Sport rig · dolphin / manta / game shark' + }, + { + id: 'lure_titan', + label: 'Titan Hook', + meshUrl: `${LURE}/lure-swjbrn0kvz.glb`, + biteMul: 0.7, + rarityBias: { legendary: 1.8 }, + sizeClass: ['huge', 'titan'], + tier: 5, + meshLengthM: 0.28, + value: 120, + sinkRate: 0.95, + blurb: 'Abyss · whale / titan class only' + } +]); + +export const FISHING_BUILDABLES = Object.freeze([ + // Claim-gated dock line (dock1/2/3 assets) + { + id: 'dock_t1', + label: 'Dock T1 (Berth)', + meshUrl: `${DOCK}/dock_t1.glb`, + kind: 'buildable', + building: true, + tier: 1, + spanM: 8, + boats: true, + boatSlots: 1, + requiresOwnClaim: false, + professions: ['fishing', 'building'], + blurb: 'Place as building anywhere · houses a boat' + }, + { + id: 'dock_t2', + label: 'Dock T2 (Harbor)', + meshUrl: `${DOCK}/dock_t2.glb`, + kind: 'buildable', + building: true, + tier: 2, + spanM: 12, + boats: true, + boatSlots: 2, + requiresOwnClaim: true, + upgradesFrom: 'dock_t1', + professions: ['fishing', 'building'], + blurb: 'Upgrade only inside own claim flag' + }, + { + id: 'dock_t3', + label: 'Dock T3 (Port)', + meshUrl: `${DOCK}/dock_t3.glb`, + kind: 'buildable', + building: true, + tier: 3, + spanM: 16, + boats: true, + boatSlots: 4, + requiresOwnClaim: true, + upgradesFrom: 'dock_t2', + professions: ['fishing', 'building'], + blurb: 'Max port · own claim flag required' + }, + // Props / kit docks (original bundle) + { id: 'dock_long', label: 'Long Dock Prop', meshUrl: `${DOCK}/dock_long.glb`, kind: 'buildable', spanM: 8, professions: ['fishing'] }, + { id: 'dock_wide', label: 'Wide Dock Prop', meshUrl: `${DOCK}/dock_wide.glb`, kind: 'buildable', spanM: 6, professions: ['fishing'] }, + { id: 'dock_stairs', label: 'Dock Stairs', meshUrl: `${DOCK}/dock_stairs.glb`, kind: 'buildable', spanM: 3, professions: ['fishing'] }, + { id: 'fishing_boat', label: 'Fishing Boat', meshUrl: `${DOCK}/boat.glb`, kind: 'buildable', spanM: 4, berthsAt: ['dock_t1', 'dock_t2', 'dock_t3'], professions: ['fishing'] }, + { id: 'fish_bucket', label: 'Fish Bucket', meshUrl: './models/fish/lures/worm.glb', kind: 'container', capacity: 12, professions: ['fishing', 'cooking'] } +]); + +export const POLE_ABILITIES = ROD_ABILITIES; +export { ROD_TYPES, ROD_ABILITIES, resolveRodMods, rodTypeById }; + +export function fishById(id) { + return FISH_SPECIES.find((f) => f.id === id) || null; +} + +export function poleById(id) { + return FISHING_POLES.find((p) => p.id === id) || FISHING_POLES[0]; +} + +export function lureById(id) { + return FISHING_LURES.find((l) => l.id === id) || FISHING_LURES[1]; +} + +/** + * Can this rod + lure + skill tree land this species? + * @param {FishSpecies} fish + * @param {{ rodTier?: number, maxSizeRank?: number, maxFishLengthM?: number, lureTier?: number, fishingLevel?: number, treeMaxSizeRank?: number }} gates + */ +export function fishAllowedByGear(fish, gates = {}) { + if (!fish) return false; + const rank = sizeRank(fish.sizeClass); + // Hard: rod line class AND skill-tree unlock (need both) + const rodRank = gates.maxSizeRank ?? 1; + const treeRank = gates.treeMaxSizeRank ?? 1; + const maxRank = Math.min(rodRank, treeRank); + if (rank > maxRank) return false; + if ((fish.lengthM || 0) > (gates.maxFishLengthM ?? 99)) return false; + if ((fish.minRodTier || 0) > (gates.rodTier ?? 0)) return false; + const lureTier = gates.lureTier ?? 0; + // Titan / huge need matching lure tier (gameplay) + if (rank >= 5 && lureTier < 5) return false; + if (rank >= 4 && lureTier < 3) return false; + return true; +} + +/** + * Weighted pick — lure size bias · rod gates · skill · rareBias. + * @param {{ + * lureId?: string, + * fishingSkill?: number, + * rareBias?: number, + * legendaryBias?: number, + * poleId?: string, + * treeMaxSizeRank?: number + * }} [ctx] + */ +export function rollFishSpecies(ctx = {}) { + const lure = lureById(ctx.lureId || 'lure_basic'); + const rod = poleById(ctx.poleId || 't0-fishing-pole'); + const skill = Number(ctx.fishingSkill) || 0; + const rareBias = Number(ctx.rareBias) || 1; + const legendaryBias = Number(ctx.legendaryBias) || 1; + const treeMax = Number(ctx.treeMaxSizeRank) || 0; + const gates = { + rodTier: rod.tier ?? 0, + maxSizeRank: rod.maxSizeRank ?? 1, + maxFishLengthM: rod.maxFishLengthM ?? 0.5, + lureTier: lure.tier ?? 0, + fishingLevel: skill, + treeMaxSizeRank: treeMax + }; + + const lureSizes = new Set(lure.sizeClass || ['small', 'medium']); + + const weights = FISH_SPECIES.map((f) => { + if (!fishAllowedByGear(f, gates)) return 0; + + let w = 1; + if (f.rarity === 'common') w = 40; + else if (f.rarity === 'uncommon') w = 22; + else if (f.rarity === 'rare') w = 10 * rareBias; + else if (f.rarity === 'epic') w = 3 * rareBias; + else w = 1 * rareBias * legendaryBias; + + w *= lure.rarityBias?.[f.rarity] || 1; + + // Lure size match — primary mechanic + if (lureSizes.has(f.sizeClass)) w *= 1.5; + else w *= 0.12; + + // Preferred lure + if (f.preferredLures?.includes(lure.id)) w *= 1.4; + + // Level gate soft + if ((f.minLevel || 1) > skill) w *= 0.25 + skill * 0.01; + + // Difficulty vs skill + if (f.difficulty > 0.3 + skill * 0.01) w *= 0.4 + skill * 0.015; + + // Sink: deep lures prefer large/huge slightly when matched + if ((lure.sinkRate || 0) > 0.6 && sizeRank(f.sizeClass) >= 3) w *= 1.15; + + // Super-rare gate — very low base weight even when gear allows + if (f.superRare) w *= 0.08; + if (f.hardCatch) w *= 0.65; + if (f.habitat === 'deep' && (lure.sinkRate || 0) < 0.5) w *= 0.25; + if (f.habitat === 'deep' && (lure.sinkRate || 0) >= 0.7) w *= 1.35; + + return Math.max(0, w); + }); + + const sum = weights.reduce((a, b) => a + b, 0); + if (sum <= 0) { + // Failsafe: smallest allowed + return FISH_SPECIES.find((f) => fishAllowedByGear(f, gates)) || FISH_SPECIES[0]; + } + let r = Math.random() * sum; + for (let i = 0; i < FISH_SPECIES.length; i++) { + r -= weights[i]; + if (r <= 0) return FISH_SPECIES[i]; + } + return FISH_SPECIES[0]; +} + +/** Effective reel zone width 0..1 */ +export function computeReelZoneWidth(pole, fish, abilityIds = [], extraZoneMul = 1) { + const rod = rodTypeById(pole?.id); + const mods = resolveRodMods(rod || pole, abilityIds); + let w = (fish?.zoneWidthBase ?? 0.18) * (mods.power || pole?.power || 1) * (extraZoneMul || 1); + // Bigger fish = slightly tighter base already on species; clamp + return Math.min(0.42, Math.max(0.055, w)); +} + +export function computeLineMax(pole, abilityIds = [], extraLineMul = 1) { + const rod = rodTypeById(pole?.id); + const mods = resolveRodMods(rod || pole, abilityIds); + return Math.min(1, (mods.lineStrength || pole?.lineStrength || 0.55) * (extraLineMul || 1)); +} + +export function computeCastRange(pole, abilityIds = []) { + const rod = rodTypeById(pole?.id); + const mods = resolveRodMods(rod || pole, abilityIds); + return mods.castRangeM || pole?.castRangeM || 14; +} + +/** Profession XP from a catch */ +export function catchXp(fish, qty = 1) { + const base = 10 + (fish.difficulty || 0.2) * 35 + (fish.value || 0) * 0.12; + return Math.round(base * (fish.xpMul || 1) * Math.max(1, qty)); +} diff --git a/src/fishing/fishingFight.js b/src/fishing/fishingFight.js new file mode 100644 index 0000000..381cc25 --- /dev/null +++ b/src/fishing/fishingFight.js @@ -0,0 +1,178 @@ +/** + * Palworld / Angler-style fishing fight bar + SCUM snag window. + * + * Bar 0..1 horizontal: + * - fishPos moves randomly (speed from fish.stats) + * - reelZone [center, width] from equipment — player reels while fish is inside + * + * Controls (wired by FishingController): + * - Wheel down: reel in (progress + zone drifts toward fish / "up" on bar) + * - Wheel up: give slack (tension −, progress slow) + * - LMB click/hold: move zone right (up the bar) + * - RMB click/hold: move zone left + * - S / RMB in bite window: snag attempt + */ + +/** + * @typedef {object} FightState + * @property {number} fishPos 0..1 + * @property {number} zoneCenter 0..1 + * @property {number} zoneWidth 0..1 + * @property {number} progress 0..1 catch fill + * @property {number} tension 0..1 line stress + * @property {number} fishVel + * @property {number} biteWindow remaining snag time (s) or 0 + * @property {boolean} hooked + * @property {'waiting'|'bite'|'fight'|'won'|'lost'} phase + */ + +/** + * @param {{ zoneWidth: number, fishSpeed: number, fishStrength: number, lineMax: number, control: number, reelSpeed?: number }} params + * @returns {FightState} + */ +export function createFightState(params) { + return { + fishPos: 0.35 + Math.random() * 0.3, + zoneCenter: 0.5, + zoneWidth: params.zoneWidth, + progress: 0.12, + tension: 0.15, + fishVel: (Math.random() > 0.5 ? 1 : -1) * params.fishSpeed * 0.35, + biteWindow: 0, + hooked: false, + phase: 'waiting', + _params: params, + _turnT: 0.4 + Math.random() * 0.8, + _leviathanT: 2.5 + Math.random() * 2, + leviathanEvent: null + }; +} + +/** + * @param {FightState} st + * @param {number} dt + * @param {{ + * reelIn?: boolean, + * slack?: boolean, + * moveRight?: number, + * moveLeft?: number, + * snag?: boolean, + * reelSpeedMul?: number + * }} input + */ +export function stepFight(st, dt, input = {}) { + if (st.phase === 'won' || st.phase === 'lost') return st; + const p = st._params; + const control = p.control ?? 1; + const reelMul = (input.reelSpeedMul || p.reelSpeed || 1); + + // —— Bite window (SCUM snag) —— + if (st.phase === 'bite') { + st.biteWindow -= dt; + if (input.snag) { + st.hooked = true; + st.phase = 'fight'; + st.progress = Math.max(st.progress, 0.18); + st.tension = 0.25; + } else if (st.biteWindow <= 0) { + st.phase = 'lost'; + st._loseReason = 'missed_snag'; + } + return st; + } + + if (st.phase !== 'fight') return st; + + // —— Fish random walk on bar —— + st._turnT -= dt; + if (st._turnT <= 0) { + st._turnT = 0.25 + Math.random() * (1.1 - p.fishSpeed * 0.4); + const burst = (Math.random() - 0.5) * 2 * p.fishSpeed; + st.fishVel = burst * (0.55 + Math.random() * 0.55); + } + st.fishPos += st.fishVel * dt; + if (st.fishPos < 0.02) { + st.fishPos = 0.02; + st.fishVel = Math.abs(st.fishVel); + } + if (st.fishPos > 0.98) { + st.fishPos = 0.98; + st.fishVel = -Math.abs(st.fishVel); + } + + // —— Zone move (LMB right / RMB left) —— + const moveSp = 0.55 * control; + if (input.moveRight) st.zoneCenter += moveSp * input.moveRight * dt; + if (input.moveLeft) st.zoneCenter -= moveSp * input.moveLeft * dt; + + // —— Reel / slack (wheel) —— + const half = st.zoneWidth * 0.5; + const inZone = + st.fishPos >= st.zoneCenter - half && st.fishPos <= st.zoneCenter + half; + + if (input.reelIn) { + // Reel pulls zone toward fish slightly + progress when on fish + const pull = (st.fishPos - st.zoneCenter) * 0.9 * dt * control * reelMul; + st.zoneCenter += pull; + st.tension += (inZone ? 0.08 : 0.22) * p.fishStrength * dt; + if (inZone) st.progress += (0.22 + 0.18 * control) * reelMul * dt; + else st.progress -= 0.06 * dt; + } else if (input.slack) { + st.tension = Math.max(0, st.tension - 0.35 * dt); + st.progress -= 0.03 * dt; + // slack lets fish run + st.fishVel *= 1 + 0.4 * dt; + } else { + // idle drift + st.tension += (inZone ? -0.04 : 0.12 * p.fishStrength) * dt; + if (inZone) st.progress += 0.06 * dt; + else st.progress -= 0.1 * p.fishStrength * dt; + } + + st.zoneCenter = Math.min(1 - half, Math.max(half, st.zoneCenter)); + + // —— Leviathans attack passive titans (ocean creature / glow whale) —— + st.leviathanEvent = null; + if (p.preyOfLeviathans && st.phase === 'fight') { + st._leviathanT = (st._leviathanT ?? 3) - dt; + if (st._leviathanT <= 0) { + st._leviathanT = 3.2 + Math.random() * 3.5; + // Strike: thrash tension + snatch progress off the passive prey + st.tension += 0.18 + Math.random() * 0.22; + st.progress = Math.max(0, st.progress - (0.08 + Math.random() * 0.1)); + st.fishVel += (Math.random() - 0.5) * 1.2; + st.leviathanEvent = 'Leviathan strikes the creature!'; + // Rare: leviathan steals the catch + if (Math.random() < 0.06) { + st.phase = 'lost'; + st._loseReason = 'leviathan_stole'; + st.leviathanEvent = 'Leviathan stole your catch!'; + } + } + } + + // Passive titans: soft fill (not thrashing). hardCatch (glow whale) resists more. + if (p.behavior === 'passive_titan' && input.reelIn && inZone) { + const soft = p.hardCatch ? 0.02 : 0.05; + st.progress += soft * reelMul * dt; + } + + st.progress = Math.min(1, Math.max(0, st.progress)); + st.tension = Math.min(1.15, Math.max(0, st.tension)); + + if (st.tension > (p.lineMax ?? 0.7)) { + st.phase = 'lost'; + st._loseReason = 'line_broke'; + } else if (st.progress >= 1) { + st.phase = 'won'; + } + return st; +} + +/** Start bite window (seconds) */ +export function beginBite(st, windowSec = 0.85) { + st.phase = 'bite'; + st.biteWindow = windowSec; + st.hooked = false; + return st; +} diff --git a/src/fishing/fishingRewards.js b/src/fishing/fishingRewards.js new file mode 100644 index 0000000..91075ba --- /dev/null +++ b/src/fishing/fishingRewards.js @@ -0,0 +1,129 @@ +/** + * Catch → recipe / form unlocks (lab localStorage; production later Railway bag). + * + * Aetherwing Turtle: + * - Worge race → unlock **form recipe** (worge aetherwing form) + * - Other races → unlock **mount recipe** (aetherwing turtle mount) + * + * Storage: grudge.casting.fishing.rewards.v1 + */ + +const STORAGE_KEY = 'grudge.casting.fishing.rewards.v1'; + +/** + * @typedef {object} FishingRewardsState + * @property {string[]} recipes unlocked recipe ids + * @property {string[]} forms + * @property {string[]} mounts + * @property {string[]} caught species ids ever caught + */ + +export function defaultRewardsState() { + return { recipes: [], forms: [], mounts: [], caught: [] }; +} + +export function loadRewardsState() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return defaultRewardsState(); + const j = JSON.parse(raw); + return { ...defaultRewardsState(), ...j }; + } catch { + return defaultRewardsState(); + } +} + +export function saveRewardsState(state) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + } catch { + /* private */ + } + return state; +} + +/** + * Species catch reward table. + * @type {Record} + */ +export const CATCH_REWARDS = Object.freeze({ + aetherwing_turtle: { + blurb: 'Super rare · Worge form OR turtle mount recipe', + formIfWorge: 'form_aetherwing_worge', + mountIfNotWorge: 'mount_aetherwing_turtle', + recipes: ['recp_aetherwing_saddle', 'recp_aetherwing_essence'] + }, + ocean_creature: { + blurb: 'Passive titan · leviathan prey · rare materials', + recipes: ['recp_ocean_creature_hide', 'recp_leviathan_scar'] + }, + glow_whale: { + blurb: 'Glow whale · hard catch · light essence', + recipes: ['recp_glow_whale_oil', 'recp_abyss_lantern'] + }, + pulbo_monstruo: { + blurb: 'Deep rare · ink & tentacle craft', + recipes: ['recp_pulbo_ink', 'recp_deep_tentacle'] + } +}); + +/** + * @param {string} speciesId + * @param {{ raceId?: string|null }} ctx + * @returns {{ unlocked: string[], messages: string[], state: FishingRewardsState }} + */ +export function applyCatchRewards(speciesId, ctx = {}) { + const state = loadRewardsState(); + const messages = []; + const unlocked = []; + if (!state.caught.includes(speciesId)) state.caught.push(speciesId); + + const def = CATCH_REWARDS[speciesId]; + if (!def) { + saveRewardsState(state); + return { unlocked, messages, state }; + } + + const race = String(ctx.raceId || '').toLowerCase(); + const isWorge = race.includes('worge'); + + if (def.formIfWorge || def.mountIfNotWorge) { + if (isWorge && def.formIfWorge) { + if (!state.forms.includes(def.formIfWorge)) { + state.forms.push(def.formIfWorge); + unlocked.push(def.formIfWorge); + messages.push(`Worge form recipe · ${def.formIfWorge}`); + } + } else if (def.mountIfNotWorge) { + if (!state.mounts.includes(def.mountIfNotWorge)) { + state.mounts.push(def.mountIfNotWorge); + unlocked.push(def.mountIfNotWorge); + messages.push(`Mount recipe · ${def.mountIfNotWorge}`); + } + } + } + + // Always grant shared recipes + for (const r of def.recipes || []) { + if (!state.recipes.includes(r)) { + state.recipes.push(r); + unlocked.push(r); + messages.push(`Recipe · ${r}`); + } + } + + saveRewardsState(state); + return { unlocked, messages, state }; +} + +export function hasRecipe(id) { + return loadRewardsState().recipes.includes(id); +} + +export function hasMount(id) { + return loadRewardsState().mounts.includes(id); +} + +export function hasForm(id) { + return loadRewardsState().forms.includes(id); +} diff --git a/src/fishing/fishingRodTypes.js b/src/fishing/fishingRodTypes.js new file mode 100644 index 0000000..fefbd1d --- /dev/null +++ b/src/fishing/fishingRodTypes.js @@ -0,0 +1,254 @@ +/** + * Fishing rod types + stats — Grudge Angler / Cute Fish Pack L1–L5 language + * extended to fleet T0–T8 gathering tiers (ObjectStore professions.Fishing). + * + * Stats feed fight bar (power/control/line) + cast range + profession bonuses. + * Mesh paths: public/models/fish/poles/* + * + * @see fishingCatalog.js · docs/FISHING_PROFESSION_SSOT.md + */ + +const POLE = './models/fish/poles'; + +/** + * @typedef {object} RodTypeDef + * @property {string} id + * @property {string} label + * @property {number} tier 0..8 + * @property {string} meshUrl + * @property {string} family shore|river|sea|deep|void + * @property {number} power reel zone width mul + * @property {number} control zone move speed mul + * @property {number} lineStrength 0..1 + * @property {number} castRangeM + * @property {number} reelSpeed progress fill mul when reeling + * @property {number} biteWindowS base snag window + * @property {number} nauticalSpeedMul freeride/boat/swim bonus when equipped or trained + * @property {string[]} abilities + * @property {string} rarity + * @property {string} blurb + * @property {number} poleLengthM SI rod mesh length + * @property {number} maxSizeRank 0 tiny … 5 titan (size gate) + * @property {number} maxFishLengthM hard line limit (m) + * @property {number} lureSlotTier max lure tier this rod can seat + */ + +/** @type {readonly RodTypeDef[]} */ +export const ROD_TYPES = Object.freeze([ + { + id: 't0-fishing-pole', + label: 'Novice Stick Pole', + tier: 0, + meshUrl: `${POLE}/fishing_rod.glb`, + family: 'shore', + power: 1.0, + control: 1.0, + lineStrength: 0.5, + castRangeM: 12, + reelSpeed: 1.0, + biteWindowS: 0.7, + nauticalSpeedMul: 1.0, + poleLengthM: 1.45, + maxSizeRank: 1, + maxFishLengthM: 0.45, + lureSlotTier: 0, + abilities: ['steady_hand'], + rarity: 'common', + blurb: 'T0 starter · shore · tiny/small only' + }, + { + id: 'fishing-pole-t1', + label: 'Shore Angler T1', + tier: 1, + meshUrl: `${POLE}/fishing_rod-0yar0lg58p.glb`, + family: 'shore', + power: 1.12, + control: 1.08, + lineStrength: 0.58, + castRangeM: 15, + reelSpeed: 1.05, + biteWindowS: 0.78, + nauticalSpeedMul: 1.02, + poleLengthM: 1.6, + maxSizeRank: 2, + maxFishLengthM: 1.0, + lureSlotTier: 1, + abilities: ['steady_hand', 'quick_snag'], + rarity: 'common', + blurb: 'Shore · up to medium · spinner lures' + }, + { + id: 'fishing-pole-t2', + label: 'River Angler T2', + tier: 2, + meshUrl: `${POLE}/fishing_rod-9aohhrphe7.glb`, + family: 'river', + power: 1.25, + control: 1.18, + lineStrength: 0.68, + castRangeM: 18, + reelSpeed: 1.12, + biteWindowS: 0.85, + nauticalSpeedMul: 1.05, + poleLengthM: 1.75, + maxSizeRank: 3, + maxFishLengthM: 2.0, + lureSlotTier: 2, + abilities: ['steady_hand', 'quick_snag', 'deep_cast'], + rarity: 'uncommon', + blurb: 'River/sea · large game (tuna class)' + }, + { + id: 'fishing-pole-t3', + label: 'Sea Angler T3', + tier: 3, + meshUrl: `${POLE}/fishing_rod-aoabqwh68m.glb`, + family: 'sea', + power: 1.4, + control: 1.28, + lineStrength: 0.78, + castRangeM: 24, + reelSpeed: 1.2, + biteWindowS: 0.9, + nauticalSpeedMul: 1.08, + poleLengthM: 1.95, + maxSizeRank: 4, + maxFishLengthM: 3.5, + lureSlotTier: 3, + abilities: ['steady_hand', 'quick_snag', 'deep_cast', 'iron_line'], + rarity: 'rare', + blurb: 'Open sea · dolphin / game shark class' + }, + { + id: 'fishing-pole-t4', + label: 'Deep Sea Rod T4', + tier: 4, + meshUrl: `${POLE}/fishing_rod-ldlwqjn9zg.glb`, + family: 'deep', + power: 1.55, + control: 1.35, + lineStrength: 0.86, + castRangeM: 30, + reelSpeed: 1.28, + biteWindowS: 0.95, + nauticalSpeedMul: 1.12, + poleLengthM: 2.15, + maxSizeRank: 4, + maxFishLengthM: 5.0, + lureSlotTier: 4, + abilities: ['steady_hand', 'quick_snag', 'deep_cast', 'iron_line', 'sea_legs'], + rarity: 'epic', + blurb: 'Deep · huge game · heavy + game rig' + }, + { + id: 'fishing-pole-t5', + label: 'Abyss Angler T5', + tier: 5, + meshUrl: `${POLE}/fishing_rod-ldlwqjn9zg.glb`, + family: 'void', + power: 1.7, + control: 1.45, + lineStrength: 0.92, + castRangeM: 36, + reelSpeed: 1.35, + biteWindowS: 1.0, + nauticalSpeedMul: 1.15, + poleLengthM: 2.35, + maxSizeRank: 5, + maxFishLengthM: 12, + lureSlotTier: 5, + abilities: ['steady_hand', 'quick_snag', 'deep_cast', 'iron_line', 'sea_legs', 'void_line'], + rarity: 'legendary', + blurb: 'Abyss · titan (whale) · titan hook' + } +]); + +export const ROD_ABILITIES = Object.freeze({ + steady_hand: { + id: 'steady_hand', + label: 'Steady Hand', + blurb: '+12% reel zone', + zoneMul: 1.12 + }, + quick_snag: { + id: 'quick_snag', + label: 'Quick Snag', + blurb: '+0.12s bite window', + biteWindowBonus: 0.12 + }, + deep_cast: { + id: 'deep_cast', + label: 'Deep Cast', + blurb: '+20% cast range', + castRangeMul: 1.2 + }, + iron_line: { + id: 'iron_line', + label: 'Iron Line', + blurb: '+15% line strength', + lineMul: 1.15 + }, + sea_legs: { + id: 'sea_legs', + label: 'Sea Legs', + blurb: '+6% nautical speed (boat/windsurf/swim)', + nauticalMul: 1.06 + }, + void_line: { + id: 'void_line', + label: 'Void Line', + blurb: '+10% reel speed · rare+ bite bias', + reelMul: 1.1, + rareBias: 1.15 + } +}); + +export function rodById(id) { + return ROD_TYPES.find((r) => r.id === id) || ROD_TYPES[0]; +} + +export function rodsByTier(maxTier = 8) { + return ROD_TYPES.filter((r) => r.tier <= maxTier); +} + +/** + * Aggregate rod + ability mods for fight / cast. + * @param {RodTypeDef} rod + * @param {string[]} [extraAbilities] from skill tree + */ +export function resolveRodMods(rod, extraAbilities = []) { + const abs = [...(rod.abilities || []), ...extraAbilities]; + let zoneMul = rod.power; + let castMul = 1; + let lineMul = 1; + let biteBonus = 0; + let reelMul = rod.reelSpeed; + let nautical = rod.nauticalSpeedMul; + let rareBias = 1; + for (const id of abs) { + const a = ROD_ABILITIES[id]; + if (!a) continue; + if (a.zoneMul) zoneMul *= a.zoneMul; + if (a.castRangeMul) castMul *= a.castRangeMul; + if (a.lineMul) lineMul *= a.lineMul; + if (a.biteWindowBonus) biteBonus += a.biteWindowBonus; + if (a.reelMul) reelMul *= a.reelMul; + if (a.nauticalMul) nautical *= a.nauticalMul; + if (a.rareBias) rareBias *= a.rareBias; + } + return { + abilities: abs, + power: zoneMul, + control: rod.control, + lineStrength: Math.min(1, rod.lineStrength * lineMul), + castRangeM: rod.castRangeM * castMul, + reelSpeed: reelMul, + biteWindowS: rod.biteWindowS + biteBonus, + nauticalSpeedMul: nautical, + rareBias, + poleLengthM: rod.poleLengthM ?? 1.6, + maxSizeRank: rod.maxSizeRank ?? 1, + maxFishLengthM: rod.maxFishLengthM ?? 0.5, + lureSlotTier: rod.lureSlotTier ?? rod.tier ?? 0 + }; +} diff --git a/src/fishing/fishingSkillTree.js b/src/fishing/fishingSkillTree.js new file mode 100644 index 0000000..f558c8d --- /dev/null +++ b/src/fishing/fishingSkillTree.js @@ -0,0 +1,257 @@ +/** + * Fishing harvest skill tree — Main Panel / ObjectStore gathering alignment. + * + * Fleet gathering: Mining · Logging · Skinning · **Fishing** · Herbalism · Scavenging + * Chef foodSystem: ONE meal each Red / Green / Blue (SWG-style triple buff). + * + * Nodes grant: quantity, bite, zone, line, cast, and **nautical speed**. + * + * @see https://objectstore.grudge-studio.com/api/v1/professions.json gathering.Fishing + */ + +/** + * @typedef {object} FishTreeNode + * @property {string} id + * @property {string} label + * @property {string} blurb + * @property {number} x 0..100 tree layout + * @property {number} y 0..100 + * @property {number} cost skill points + * @property {number} requiresLevel + * @property {string[]} [requires] + * @property {Record} bonuses + */ + +/** @type {readonly FishTreeNode[]} */ +export const FISHING_SKILL_TREE = Object.freeze([ + { + id: 'fish_init', + label: "Fisher's Initiation", + blurb: 'Unlock shore fishing · common catch', + x: 50, + y: 92, + cost: 0, + requiresLevel: 1, + requires: [], + bonuses: { fishingUnlocked: 1 } + }, + { + id: 'fish_qty_1', + label: 'Double Hook', + blurb: '+1 fish quantity on catch', + x: 35, + y: 78, + cost: 1, + requiresLevel: 5, + requires: ['fish_init'], + bonuses: { catchQty: 1 } + }, + { + id: 'fish_bite_1', + label: 'Patient Angler', + blurb: '+15% bite rate', + x: 65, + y: 78, + cost: 1, + requiresLevel: 5, + requires: ['fish_init'], + bonuses: { biteMul: 1.15 } + }, + { + id: 'fish_zone_1', + label: 'Wider Reel', + blurb: '+8% fight zone width', + x: 50, + y: 64, + cost: 1, + requiresLevel: 10, + requires: ['fish_qty_1', 'fish_bite_1'], + bonuses: { zoneMul: 1.08 } + }, + { + id: 'nautical_1', + label: 'Sea Legs I', + blurb: '+5% nautical speed (windsurf / boat / swim)', + x: 22, + y: 64, + cost: 1, + requiresLevel: 10, + requires: ['fish_init'], + bonuses: { nauticalSpeedMul: 1.05 } + }, + { + id: 'fish_line_1', + label: 'Braided Line', + blurb: '+10% line strength', + x: 78, + y: 64, + cost: 1, + requiresLevel: 12, + requires: ['fish_bite_1'], + bonuses: { lineMul: 1.1 } + }, + { + id: 'fish_qty_2', + label: 'Net Haul', + blurb: '+1 quantity (stack)', + x: 35, + y: 48, + cost: 2, + requiresLevel: 20, + requires: ['fish_zone_1'], + bonuses: { catchQty: 1 } + }, + { + id: 'nautical_2', + label: 'Sea Legs II', + blurb: '+8% nautical speed', + x: 22, + y: 48, + cost: 2, + requiresLevel: 20, + requires: ['nautical_1'], + bonuses: { nauticalSpeedMul: 1.08 } + }, + { + id: 'fish_rare_1', + label: 'Lucky Tide', + blurb: '+20% rare+ weight', + x: 65, + y: 48, + cost: 2, + requiresLevel: 22, + requires: ['fish_line_1'], + bonuses: { rareBias: 1.2 } + }, + { + id: 'fish_size_med', + label: 'Medium Game', + blurb: 'Skill-gate +1 size class (medium fish)', + x: 78, + y: 48, + cost: 1, + requiresLevel: 15, + requires: ['fish_zone_1'], + bonuses: { maxSizeRank: 2 } + }, + { + id: 'fish_lure_1', + label: 'Bait Sense', + blurb: '+10% bite when lure size matches fish', + x: 90, + y: 48, + cost: 1, + requiresLevel: 18, + requires: ['fish_size_med'], + bonuses: { lureMatchMul: 1.1 } + }, + { + id: 'fish_meal_link', + label: "Fisher's Kitchen", + blurb: 'Unlock fish meal crafts (Blue food slot)', + x: 50, + y: 34, + cost: 2, + requiresLevel: 25, + requires: ['fish_qty_2', 'fish_rare_1'], + bonuses: { mealCraft: 1 } + }, + { + id: 'nautical_3', + label: 'Tide Runner', + blurb: '+10% nautical · freeride focus', + x: 22, + y: 34, + cost: 2, + requiresLevel: 30, + requires: ['nautical_2'], + bonuses: { nauticalSpeedMul: 1.1 } + }, + { + id: 'fish_size_large', + label: 'Big Game', + blurb: 'Unlock large class (tuna / sword) with rod', + x: 78, + y: 34, + cost: 2, + requiresLevel: 28, + requires: ['fish_size_med', 'fish_line_1'], + bonuses: { maxSizeRank: 3 } + }, + { + id: 'fish_size_huge', + label: 'Sport Fisher', + blurb: 'Huge class · dolphin / manta / shark', + x: 90, + y: 28, + cost: 2, + requiresLevel: 35, + requires: ['fish_size_large', 'fish_lure_1'], + bonuses: { maxSizeRank: 4 } + }, + { + id: 'fish_size_titan', + label: 'Leviathan Hook', + blurb: 'Titan class · whale (T5 rod + titan lure)', + x: 78, + y: 18, + cost: 3, + requiresLevel: 48, + requires: ['fish_size_huge'], + bonuses: { maxSizeRank: 5 } + }, + { + id: 'fish_master', + label: 'Grandmaster Angler', + blurb: '+2 qty · +12% zone · legendary bias', + x: 50, + y: 18, + cost: 3, + requiresLevel: 50, + requires: ['fish_meal_link', 'nautical_3'], + bonuses: { catchQty: 2, zoneMul: 1.12, rareBias: 1.35, legendaryBias: 1.25 } + } +]); + +/** + * Sum bonuses from unlocked node ids. + * @param {string[]} unlocked + */ +export function sumTreeBonuses(unlocked = []) { + const set = new Set(unlocked); + /** @type {Record} */ + const out = { + catchQty: 0, + biteMul: 1, + zoneMul: 1, + lineMul: 1, + rareBias: 1, + legendaryBias: 1, + nauticalSpeedMul: 1, + lureMatchMul: 1, + mealCraft: 0, + fishingUnlocked: 0, + maxSizeRank: 1 + }; + for (const n of FISHING_SKILL_TREE) { + if (!set.has(n.id)) continue; + for (const [k, v] of Object.entries(n.bonuses || {})) { + if (k === 'maxSizeRank') out.maxSizeRank = Math.max(out.maxSizeRank || 0, v); + else if (k.endsWith('Mul') || k.endsWith('Bias')) out[k] = (out[k] || 1) * v; + else out[k] = (out[k] || 0) + v; + } + } + return out; +} + +export function treeNodeById(id) { + return FISHING_SKILL_TREE.find((n) => n.id === id) || null; +} + +/** Can unlock node given level + already unlocked */ +export function canUnlockNode(node, level, unlocked) { + if (!node) return false; + if (unlocked.includes(node.id)) return false; + if (level < node.requiresLevel) return false; + return (node.requires || []).every((r) => unlocked.includes(r)); +} diff --git a/src/fishing/mealBuffs.js b/src/fishing/mealBuffs.js new file mode 100644 index 0000000..1f8e9e8 --- /dev/null +++ b/src/fishing/mealBuffs.js @@ -0,0 +1,183 @@ +/** + * SWG-style meal buffs — **one active meal per color** (Red · Green · Blue). + * + * Fleet SSOT: ObjectStore professions.chef.foodSystem + * Red = Land meat → HP / attack / defense… + * Blue = Ocean/soup → spell / mana / **nautical** (fisher meals) + * Green = Plants → stamina / movement / crit… + * + * Main Panel professions (WCS) use the same RGB food slots. + * Lab applies temporary modifiers for freeride + fishing + combat soft stats. + * + * @see https://objectstore.grudge-studio.com/api/v1/professions.json + */ + +/** + * @typedef {'red'|'green'|'blue'} MealColor + * @typedef {object} MealDef + * @property {string} id + * @property {string} label + * @property {MealColor} color + * @property {string} blurb + * @property {number} durationSec + * @property {string[]} needs ingredient tags + * @property {Record} buffs flat or mul keys + * @property {number} [tier] + */ + +/** @type {readonly MealDef[]} */ +export const MEAL_RECIPES = Object.freeze([ + // —— Blue (ocean / fish) —— + { + id: 'meal_fish_stew', + label: 'Fish Stew', + color: 'blue', + blurb: 'SWG-style blue food · mana regen + light spell power', + durationSec: 900, + tier: 1, + needs: ['raw_fish', 'herb'], + buffs: { manaRegenMul: 1.15, spellDamageMul: 1.05, nauticalSpeedMul: 1.03 } + }, + { + id: 'meal_salmon_broth', + label: 'Salmon Broth', + color: 'blue', + blurb: 'Blue · spell speed + nautical', + durationSec: 1200, + tier: 2, + needs: ['raw_fish', 'vegetables'], + buffs: { spellSpeedMul: 1.08, manaPoolMul: 1.06, nauticalSpeedMul: 1.05 } + }, + { + id: 'meal_tuna_curry', + label: 'Tuna Curry', + color: 'blue', + blurb: 'Blue · spell crit + resist + nautical', + durationSec: 1500, + tier: 3, + needs: ['raw_fish', 'spice'], + buffs: { spellCritMul: 1.1, resistMul: 1.05, nauticalSpeedMul: 1.08 } + }, + { + id: 'meal_moonfish_soup', + label: 'Moonfish Soup', + color: 'blue', + blurb: 'Blue epic · strong mana + nautical tide', + durationSec: 1800, + tier: 4, + needs: ['raw_fish', 'pearl'], + buffs: { manaRegenMul: 1.25, spellDamageMul: 1.12, nauticalSpeedMul: 1.12 } + }, + // —— Red (land meat — chef path; fish can substitute lightly) —— + { + id: 'meal_grilled_filet', + label: 'Grilled Filet', + color: 'red', + blurb: 'Red food · HP regen + attack', + durationSec: 900, + tier: 1, + needs: ['raw_fish', 'fuel'], + buffs: { healthRegenMul: 1.12, attackDamageMul: 1.06, maxHealthMul: 1.04 } + }, + { + id: 'meal_piranha_seared', + label: 'Seared Piranha', + color: 'red', + blurb: 'Red · attack + counter', + durationSec: 1200, + tier: 2, + needs: ['raw_fish', 'fuel'], + buffs: { attackDamageMul: 1.1, counterMul: 1.08, defenseMul: 1.04 } + }, + // —— Green (plants / stamina — light fisher travel food) —— + { + id: 'meal_shore_salad', + label: 'Shore Salad', + color: 'green', + blurb: 'Green · stamina + move (not fish-exclusive)', + durationSec: 900, + tier: 1, + needs: ['herb', 'vegetables'], + buffs: { staminaMul: 1.12, moveSpeedMul: 1.06, attackSpeedMul: 1.03 } + }, + { + id: 'meal_seaweed_wrap', + label: 'Seaweed Wrap', + color: 'green', + blurb: 'Green · move + armor', + durationSec: 1200, + tier: 2, + needs: ['herb', 'shellfish'], + buffs: { moveSpeedMul: 1.1, armorMul: 1.06, staminaMul: 1.08 } + } +]); + +/** + * Active meal slots — one per color (SWG / fleet chef rule). + * @typedef {{ red: MealDef|null, green: MealDef|null, blue: MealDef|null, expires: Record }} MealSlots + */ + +export function emptyMealSlots() { + return { + red: null, + green: null, + blue: null, + /** @type {Record} color → expire elapsed time */ + expires: { red: 0, green: 0, blue: 0 } + }; +} + +/** + * Eat meal into its color slot (replaces previous of same color). + * @param {MealSlots} slots + * @param {MealDef} meal + * @param {number} nowElapsed + */ +export function applyMeal(slots, meal, nowElapsed) { + const c = meal.color; + slots[c] = meal; + slots.expires[c] = nowElapsed + (meal.durationSec || 900); + return slots; +} + +/** + * Clear expired meals. + * @param {MealSlots} slots + * @param {number} nowElapsed + */ +export function tickMeals(slots, nowElapsed) { + for (const c of ['red', 'green', 'blue']) { + if (slots[c] && (slots.expires[c] || 0) <= nowElapsed) { + slots[c] = null; + slots.expires[c] = 0; + } + } + return slots; +} + +/** + * Combined buffs from all three slots (multiplicative for *Mul keys). + * @param {MealSlots} slots + */ +export function sumMealBuffs(slots) { + /** @type {Record} */ + const out = {}; + for (const c of ['red', 'green', 'blue']) { + const m = slots[c]; + if (!m?.buffs) continue; + for (const [k, v] of Object.entries(m.buffs)) { + if (k.endsWith('Mul')) out[k] = (out[k] || 1) * v; + else out[k] = (out[k] || 0) + v; + } + } + return out; +} + +export function mealById(id) { + return MEAL_RECIPES.find((m) => m.id === id) || null; +} + +/** Blue meals craftable from fish (Fisher's Kitchen tree node) */ +export function fishMealRecipes() { + return MEAL_RECIPES.filter((m) => m.color === 'blue' || m.needs?.includes('raw_fish')); +} diff --git a/src/fishing/professionState.js b/src/fishing/professionState.js new file mode 100644 index 0000000..6e8808d --- /dev/null +++ b/src/fishing/professionState.js @@ -0,0 +1,171 @@ +/** + * Lab profession progress for Fishing (+ meal slots). + * Local SSOT for Casting; Main Panel / Railway own production character later. + * + * Storage: localStorage grudge.casting.fishing.profession.v1 + */ + +import { FISHING_SKILL_TREE, sumTreeBonuses, canUnlockNode } from './fishingSkillTree.js'; +import { + emptyMealSlots, + applyMeal, + tickMeals, + sumMealBuffs, + mealById +} from './mealBuffs.js'; +import { resolveRodMods, rodById } from './fishingRodTypes.js'; + +const STORAGE_KEY = 'grudge.casting.fishing.profession.v1'; + +/** + * @typedef {object} FishingProfessionState + * @property {number} level 1..100 + * @property {number} xp + * @property {number} skillPoints + * @property {string[]} unlocked + * @property {string} poleId + * @property {string} lureId + * @property {import('./mealBuffs.js').MealSlots} meals + */ + +export function defaultProfessionState() { + return { + level: 1, + xp: 0, + skillPoints: 1, + unlocked: ['fish_init'], + poleId: 't0-fishing-pole', + lureId: 'lure_basic', + meals: emptyMealSlots() + }; +} + +export function loadProfessionState() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return defaultProfessionState(); + const j = JSON.parse(raw); + return { + ...defaultProfessionState(), + ...j, + unlocked: Array.isArray(j.unlocked) ? j.unlocked : ['fish_init'], + meals: j.meals || emptyMealSlots() + }; + } catch { + return defaultProfessionState(); + } +} + +export function saveProfessionState(state) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + } catch { + /* private mode */ + } + return state; +} + +/** Simple XP curve aligned with fleet xpTable milestones */ +export function xpToNext(level) { + if (level >= 100) return Infinity; + return Math.floor(80 * level + 20 * level * level * 0.15); +} + +/** + * @param {FishingProfessionState} state + * @param {number} amount + */ +export function grantFishingXp(state, amount) { + state.xp += Math.max(0, amount); + let guard = 0; + while (state.level < 100 && state.xp >= xpToNext(state.level) && guard++ < 20) { + state.xp -= xpToNext(state.level); + state.level += 1; + state.skillPoints += 1; + } + return saveProfessionState(state); +} + +/** + * @param {FishingProfessionState} state + * @param {string} nodeId + */ +export function unlockTreeNode(state, nodeId) { + const node = FISHING_SKILL_TREE.find((n) => n.id === nodeId); + if (!node || !canUnlockNode(node, state.level, state.unlocked)) return false; + if (state.skillPoints < node.cost) return false; + state.skillPoints -= node.cost; + state.unlocked.push(nodeId); + saveProfessionState(state); + return true; +} + +/** + * Combined combat/fishing modifiers from tree + meals + rod. + * @param {FishingProfessionState} state + * @param {number} [elapsed] + */ +export function resolveProfessionMods(state, elapsed = 0) { + tickMeals(state.meals, elapsed); + const tree = sumTreeBonuses(state.unlocked); + const meals = sumMealBuffs(state.meals); + const rod = rodById(state.poleId); + const rodMods = resolveRodMods(rod, []); + + const mul = (a, b) => (a || 1) * (b || 1); + return { + level: state.level, + poleId: state.poleId, + lureId: state.lureId, + rod, + rodMods, + tree, + meals, + catchQty: 1 + (tree.catchQty || 0), + biteMul: mul(tree.biteMul, 1), + zoneMul: mul(tree.zoneMul, rodMods.power), + lineMul: mul(tree.lineMul, 1), + rareBias: mul(tree.rareBias, rodMods.rareBias), + legendaryBias: tree.legendaryBias || 1, + lureMatchMul: tree.lureMatchMul || 1, + // Effective size = min(rod physical, tree knowledge) + maxSizeRank: Math.min(tree.maxSizeRank || 1, rodMods.maxSizeRank ?? 1), + rodMaxSizeRank: rodMods.maxSizeRank ?? 1, + treeMaxSizeRank: tree.maxSizeRank || 1, + maxFishLengthM: rodMods.maxFishLengthM || 0.5, + lureSlotTier: rodMods.lureSlotTier ?? 0, + nauticalSpeedMul: + mul(tree.nauticalSpeedMul, 1) * + mul(meals.nauticalSpeedMul, 1) * + (rodMods.nauticalSpeedMul || 1), + moveSpeedMul: meals.moveSpeedMul || 1, + mealBuffs: meals, + mealCraft: !!tree.mealCraft + }; +} + +/** + * @param {FishingProfessionState} state + * @param {string} mealId + * @param {number} elapsed + */ +export function eatMeal(state, mealId, elapsed) { + const meal = mealById(mealId); + if (!meal) return false; + applyMeal(state.meals, meal, elapsed); + saveProfessionState(state); + return true; +} + +export { FISHING_SKILL_TREE, sumTreeBonuses, canUnlockNode, treeNodeById } from './fishingSkillTree.js'; +export { + MEAL_RECIPES, + MEAL_RECIPES as MEALS, + mealById, + fishMealRecipes, + emptyMealSlots, + applyMeal, + tickMeals, + sumMealBuffs +} from './mealBuffs.js'; +export { ROD_TYPES, ROD_ABILITIES, resolveRodMods, rodById, rodsByTier } from './fishingRodTypes.js'; diff --git a/src/input/InputManager.js b/src/input/InputManager.js index 32d3b4e..e08073a 100644 --- a/src/input/InputManager.js +++ b/src/input/InputManager.js @@ -2,24 +2,50 @@ import { Vector2 } from 'three'; import { EventEmitter } from '../utils/EventEmitter.js'; /** - * Normalises pointer + keyboard input into a small event vocabulary. + * Pointer + keyboard → events. * - * Events: `draw:start`, `draw:move`, `draw:end`, `element`, `action`. - * Pointer events that begin on top of DOM UI (the editor, the HUD) are ignored - * so dragging a slider never starts drawing a path. + * LMB modes (combat, from CombatFocus): + * focus ON → lmb:attack + * focus OFF → lmb:select (unlocked mouse) + * sandbox / walk path → draw:start|move|end + * + * Combat hotkeys: + * 1–4 skills · F interact/attack · X dodge · C parry · E block + * Shift hold sprint · Shift+Ctrl slide · Ctrl(+dir) roll + * RMB focus toggle · Space jump + * F1–F4 Admin · ] World · ` auto run / freeride sail */ export class InputManager extends EventEmitter { constructor(domElement) { super(); this.dom = domElement; - this.pointer = new Vector2(); // NDC + this.pointer = new Vector2(); this.isDrawing = false; this.keys = new Set(); this.enabled = true; + /** When true, fleet combat keys win over sandbox (clear→Shift+C, etc.) */ + this.combatKeys = true; + /** + * App sets this: () => 'draw' | 'attack' | 'select' + * @type {(() => 'draw'|'attack'|'select')|null} + */ + this.getLmbMode = null; this._bind(); } + setCombatKeys(on) { + this.combatKeys = !!on; + } + + /** + * Activity mode from App (combat | harvest) — gates R heavy vs tool radial. + * @param {'combat'|'harvest'|string} mode + */ + setActivityMode(mode) { + this.activityMode = mode === 'harvest' ? 'harvest' : 'combat'; + } + _bind() { this.dom.addEventListener('pointerdown', this._onPointerDown); window.addEventListener('pointermove', this._onPointerMove); @@ -31,6 +57,8 @@ export class InputManager extends EventEmitter { } _updatePointer(event) { + this.clientX = event.clientX; + this.clientY = event.clientY; this.pointer.set( (event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1 @@ -39,10 +67,22 @@ export class InputManager extends EventEmitter { _onPointerDown = (event) => { if (!this.enabled) return; - if (event.button !== 0) return; // left button draws; right orbits - if (event.target !== this.dom) return; // started on UI + if (event.button !== 0) return; + if (event.target !== this.dom) return; this._updatePointer(event); + const mode = this.getLmbMode?.() || 'draw'; + + if (mode === 'attack') { + this.emit('lmb:attack', this.pointer.clone()); + return; + } + if (mode === 'select') { + this.emit('lmb:select', this.pointer.clone()); + return; + } + + // Path draw (casting sandbox / walk course) this.isDrawing = true; this.dom.setPointerCapture?.(event.pointerId); this.emit('draw:start', this.pointer); @@ -64,37 +104,159 @@ export class InputManager extends EventEmitter { _onKeyDown = (event) => { if (event.repeat) return; const target = event.target; - if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) return; + if (target && (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable)) + return; this.keys.add(event.code); + if (event.code === 'Space') { + event.preventDefault(); + } + + // Alt+Shift+Q/E/R/F/V/G = Linear skillshots (MOBA line/zone aim) + if (event.altKey && event.shiftKey) { + const linearMap = { + KeyQ: 'ice', + KeyE: 'thunder', + KeyR: 'meteor', + KeyF: 'beam', + KeyV: 'snare', + KeyG: 'glacier' + }; + if (linearMap[event.code]) { + event.preventDefault(); + this.emit('linearSkill', linearMap[event.code]); + return; + } + } + + // Alt+sandbox VFX (never steal bare combat keys) + if (event.altKey) { + const map = { + KeyV: 'ice_lightning_burst', + KeyB: 'moon_beam', + KeyF: 'frost_wave', + KeyG: 'fire_aura', + KeyT: 'earth_surge', + KeyC: 'fireball', + KeyL: 'chain_lightning' + }; + if (map[event.code]) { + event.preventDefault(); + this.emit('sandboxVfx', map[event.code]); + return; + } + } + + // Shift+C = clear VFX (combat C is parry) + if (event.shiftKey && event.code === 'KeyC') { + event.preventDefault(); + this.emit('action', 'clear'); + return; + } + + // Digit skills / element select (1–6 staffs: fire storm ice nature holy arcane) + // Combat: emit skillHold:start for hold-to-charge (Charged Shot); keyup releases + if (event.code === 'Digit1') { + this.emit('element', 0); + this.emit('skillHold:start', 0); + return; + } + if (event.code === 'Digit2') { + this.emit('element', 1); + this.emit('skillHold:start', 1); + return; + } + if (event.code === 'Digit3') { + this.emit('element', 2); + this.emit('skillHold:start', 2); + return; + } + if (event.code === 'Digit4') { + this.emit('element', 3); + this.emit('skillHold:start', 3); + return; + } + if (event.code === 'Digit5') { + this.emit('element', 4); + this.emit('skillHold:start', 4); + return; + } + if (event.code === 'Digit6') { + this.emit('element', 5); + this.emit('skillHold:start', 5); + return; + } + + // F = best next action always (pickup / harvest / standard attack) + // Alt+F is sandbox frost (handled above). Residual is attack fallback, not a free skill key. + // Combat F also starts skillHold for Charged Shot when weapon skill is chargeable. + if (event.code === 'KeyF') { + event.preventDefault(); + this.emit('combatAction', 'interact'); + this.emit('skillHold:start', 'f'); + return; + } + + // Danger Room combat actions when combatKeys + // Harvest mode: R is tool radial (not heavy); Q is mode radial (not equip) + if (this.combatKeys) { + const combatMap = { + KeyX: 'dodge', + KeyC: 'parry', + KeyE: 'block', + KeyR: 'heavy', + KeyV: 'kick', + KeyJ: 'heal', + KeyH: 'bomb' + }; + if (event.code === 'KeyR' && this.activityMode === 'harvest') { + event.preventDefault(); + this.emit('action', 'rHoldStart'); + return; + } + if (combatMap[event.code]) { + event.preventDefault(); + this.emit('combatAction', combatMap[event.code]); + return; + } + } + switch (event.code) { - case 'Digit1': - this.emit('element', 0); - break; - case 'Digit2': - this.emit('element', 1); - break; - case 'Digit3': - this.emit('element', 2); - break; - case 'Digit4': - this.emit('element', 3); - break; case 'KeyQ': - this.emit('action', 'prevElement'); + // Hold Q = mode radial (Open parity); Shift+Q = equip session toggle + if (event.shiftKey) { + this.emit('action', 'toggleDrcSession'); + } else { + this.emit('action', 'qHoldStart'); + } + break; + case 'KeyR': + // Non-combat: tool radial always available for harvest tools + if (this.activityMode === 'harvest') { + event.preventDefault(); + this.emit('action', 'rHoldStart'); + } break; case 'KeyE': - this.emit('action', 'nextElement'); + // Equip mode only: cycle element (combat uses E as block) + if (!this.combatKeys) this.emit('action', 'nextElement'); break; + case 'Slash': case 'KeyH': - this.emit('action', 'toggleHelp'); + // ? / H = keyboard help (F1–F4 Admin · ] World — not help) + if (!this.combatKeys) this.emit('action', 'toggleHelp'); + break; + case 'Backquote': + // ` = auto run / freeride sail-row + event.preventDefault(); + this.emit('action', 'toggleAutoTraverse'); break; case 'KeyG': this.emit('action', 'toggleEditor'); break; case 'KeyC': - this.emit('action', 'clear'); + if (!this.combatKeys) this.emit('action', 'clear'); break; case 'KeyP': this.emit('action', 'togglePause'); @@ -105,6 +267,45 @@ export class InputManager extends EventEmitter { case 'KeyM': this.emit('action', 'toggleMode'); break; + case 'KeyI': + this.emit('action', 'toggleInventory'); + break; + case 'KeyO': + // Showcase review (race / weapon / anim / bind skills) + this.emit('action', 'toggleShowcase'); + break; + case 'KeyB': + // Drop bag (throw / pickups) + this.emit('action', 'toggleDropBag'); + break; + case 'KeyL': + // Spawn sample world loot prefabs + this.emit('action', 'spawnLoot'); + break; + case 'Tab': + // Soft-lock cycle (Shift+Tab = previous) — combat / focus targeting + event.preventDefault(); + this.emit('action', 'cycleTarget', { reverse: !!event.shiftKey }); + break; + case 'F1': + case 'F2': + case 'F3': + case 'F4': + // Admin Hub: F1 Player · F2 Assets · F3 Creatures · F4 Prefabs + event.preventDefault(); + this.emit('action', 'adminTab', event.code); + break; + case 'BracketRight': + // ] = World admin (replaces F5) + event.preventDefault(); + this.emit('action', 'adminTab', 'BracketRight'); + break; + case 'F5': + // Unbound — World moved to ] + break; + case 'Escape': + this.emit('action', 'closeAdmin'); + break; default: break; } @@ -112,6 +313,25 @@ export class InputManager extends EventEmitter { _onKeyUp = (event) => { this.keys.delete(event.code); + if (event.code === 'KeyQ' && !event.shiftKey) { + this.emit('action', 'qHoldEnd'); + } + if (event.code === 'KeyR') { + this.emit('action', 'rHoldEnd'); + } + // Digit / F release — Charged Shot release or tap fire + const digitMap = { + Digit1: 0, + Digit2: 1, + Digit3: 2, + Digit4: 3, + Digit5: 4, + Digit6: 5, + KeyF: 'f' + }; + if (digitMap[event.code] !== undefined) { + this.emit('skillHold:end', digitMap[event.code]); + } }; dispose() { diff --git a/src/input/MouseAim.js b/src/input/MouseAim.js new file mode 100644 index 0000000..de89134 --- /dev/null +++ b/src/input/MouseAim.js @@ -0,0 +1,286 @@ +import { MathUtils, Raycaster, Vector2, Vector3 } from 'three'; +import { settings } from '../config/settings.js'; +import { projectToTerrain } from '../world/terrainGround.js'; + +const _ndc = new Vector2(); +const _hit = new Vector3(); +const _tmp = new Vector3(); +const _tmp2 = new Vector3(); +const _spawn = new Vector3(); +const _dir = new Vector3(); + +/** + * Combat mouse aim — snow-brawl style ray → world hit → launch vector. + * + * Free / focus: camera ray → **terrain mesh or height sample** (one SSOT), + * then soft-lock bias + 3D launch dir. No separate heightmap here. + * + * @see world/terrainGround.js · docs/TERRAIN_PHYSICS_SSOT.md + */ +export class MouseAim { + /** + * @param {import('three').Camera} camera + */ + constructor(camera) { + this.camera = camera; + this.raycaster = new Raycaster(); + this.raycaster.far = 400; + /** World aim point (often ground / soft-lock blend) */ + this.point = new Vector3(0, 0, 4); + /** 3D hit used for projectile targeting (may be above ground) */ + this.hitPoint = new Vector3(0, 1.2, 6); + /** Camera ray origin / dir this frame */ + this.rayOrigin = new Vector3(); + this.rayDir = new Vector3(0, 0, -1); + /** Unit XZ facing from player → aim (movement / body assist) */ + this.forward = new Vector3(0, 0, 1); + /** Full 3D unit direction spawn → hit (projectiles) */ + this.forward3d = new Vector3(0, 0, 1); + this.right = new Vector3(1, 0, 0); + /** Horizontal aim yaw (atan2 x,z) */ + this.yaw = 0; + this.valid = false; + /** Screen NDC of last pointer / center */ + this.ndc = new Vector2(); + /** Optional colliders for ray (walls, props) — focus aim only */ + this.aimColliders = []; + /** @type {import('../world/terrainGround.js').TerrainGround|null} */ + this.terrain = null; + } + + /** + * @param {import('three').Object3D[]} meshes + */ + setAimColliders(meshes) { + this.aimColliders = meshes || []; + } + + /** + * One terrain handle from App (mesh + sample). Same as PathDrawer. + * @param {import('../world/terrainGround.js').TerrainGround|null} terrain + */ + setTerrain(terrain) { + this.terrain = terrain || null; + } + + /** + * Free cursor: NDC → terrain ground. + * @param {Vector2|{x:number,y:number}} pointerNdc InputManager.pointer (-1..1) + * @param {Vector3} playerPos character feet + * @returns {boolean} + */ + updateFromNdc(pointerNdc, playerPos) { + const nx = pointerNdc?.x ?? 0; + const ny = pointerNdc?.y ?? 0; + this.ndc.set(nx, ny); + _ndc.set(nx, ny); + this.raycaster.setFromCamera(_ndc, this.camera); + this.rayOrigin.copy(this.raycaster.ray.origin); + this.rayDir.copy(this.raycaster.ray.direction).normalize(); + + if (!projectToTerrain(this.raycaster, _hit, this.terrain)) { + this.hitPoint.copy(this.rayOrigin).addScaledVector(this.rayDir, this._aimFar()); + const y = + typeof this.terrain?.sample === 'function' + ? this.terrain.sample(this.hitPoint.x, this.hitPoint.z) + : 0; + this.point.set(this.hitPoint.x, Number.isFinite(y) ? y : 0, this.hitPoint.z); + this.valid = true; + this._fromPlayer(playerPos); + this._refreshLaunch(playerPos); + return true; + } + this.point.copy(_hit); + this.hitPoint.copy(_hit); + this.hitPoint.y = Math.max( + _hit.y + 0.15, + (settings.aim?.projectileAimHeight ?? 1.15) * 0.2 + _hit.y + ); + this.valid = true; + this._fromPlayer(playerPos); + this._refreshLaunch(playerPos); + return true; + } + + /** + * Focus mode: crosshair at screen center (pointer-lock look). + * Camera ray → ground/colliders/far · soft-lock bias · 3D launch vector. + * @param {Vector3} playerPos feet + * @param {{ + * softTarget?: Vector3|null, + * softBlend?: number, + * maxSoftAngleDeg?: number, + * reticleNdc?: {x:number,y:number} + * }} [opts] + */ + updateFocusAim(playerPos, opts = {}) { + const reticle = opts.reticleNdc || { x: 0, y: 0 }; + this.ndc.set(reticle.x ?? 0, reticle.y ?? 0); + this.raycaster.far = this._aimFar(); + this.raycaster.setFromCamera(this.ndc, this.camera); + this.rayOrigin.copy(this.raycaster.ray.origin); + this.rayDir.copy(this.raycaster.ray.direction).normalize(); + + // 1) Prefer mesh colliders (walls / props) + let hit3 = null; + if (this.aimColliders.length) { + const hits = this.raycaster.intersectObjects(this.aimColliders, true); + if (hits[0]) hit3 = hits[0].point.clone(); + } + // 2) Terrain mesh / height sample (same SSOT as free aim + path draw) + if (!hit3 && projectToTerrain(this.raycaster, _hit, this.terrain)) { + hit3 = _hit.clone(); + const aimH = settings.aim?.projectileAimHeight ?? 1.15; + hit3.y = _hit.y + aimH * 0.15; + } + // 3) Far point along look ray + if (!hit3) { + hit3 = this.rayOrigin.clone().addScaledVector(this.rayDir, this._aimFar()); + } + + this.hitPoint.copy(hit3); + + // Soft-lock magnetic pull (within cone — keeps accuracy when near crosshair) + const soft = opts.softTarget; + if (soft) { + const blend = opts.softBlend ?? settings.aim?.softLockBlend ?? 0.55; + const maxAng = MathUtils.degToRad( + opts.maxSoftAngleDeg ?? settings.aim?.softLockMaxAngleDeg ?? 18 + ); + _dir.copy(soft).sub(this.rayOrigin); + if (_dir.lengthSq() > 1e-6) { + _dir.normalize(); + const ang = this.rayDir.angleTo(_dir); + if (ang <= maxAng) { + const closeness = 1 - ang / Math.max(1e-4, maxAng); + const w = blend * (0.35 + closeness * 0.75); + this.hitPoint.lerp(soft, MathUtils.clamp(w, 0, 0.9)); + } + } + } + + // Ground marker on terrain surface under hit + const gy = + typeof this.terrain?.sample === 'function' + ? this.terrain.sample(this.hitPoint.x, this.hitPoint.z) + : 0; + this.point.set( + this.hitPoint.x, + (Number.isFinite(gy) ? gy : 0) + 0.05, + this.hitPoint.z + ); + this.valid = true; + this._fromPlayer(playerPos); + this._refreshLaunch(playerPos); + return true; + } + + /** + * Focus mode: aim from screen center (crosshair), not free cursor. + * @param {Vector3} playerPos + * @deprecated prefer updateFocusAim + */ + updateFromCenter(playerPos) { + return this.updateFocusAim(playerPos); + } + + /** + * @param {number} clientX + * @param {number} clientY + * @param {Vector3} playerPos + */ + updateFromClient(clientX, clientY, playerPos) { + _ndc.set( + (clientX / window.innerWidth) * 2 - 1, + -(clientY / window.innerHeight) * 2 + 1 + ); + return this.updateFromNdc(_ndc, playerPos); + } + + _aimFar() { + return settings.aim?.aimRayFar ?? 80; + } + + _fromPlayer(playerPos) { + const dx = this.point.x - playerPos.x; + const dz = this.point.z - playerPos.z; + const len = Math.hypot(dx, dz); + if (len < 0.05) { + // Fall back to camera XZ + _tmp.set(this.rayDir.x, 0, this.rayDir.z); + if (_tmp.lengthSq() > 1e-6) { + _tmp.normalize(); + this.forward.copy(_tmp); + this.yaw = Math.atan2(this.forward.x, this.forward.z); + this.right.set(this.forward.z, 0, -this.forward.x); + } + return; + } + this.forward.set(dx / len, 0, dz / len); + this.yaw = Math.atan2(this.forward.x, this.forward.z); + this.right.set(this.forward.z, 0, -this.forward.x); + } + + /** + * 3D unit direction from cast-height spawn to hit (snow-brawl spawn→target). + * @param {Vector3} playerPos feet + */ + _refreshLaunch(playerPos) { + const chestY = settings.aim?.spawnHeight ?? 1.35; + _spawn.set(playerPos.x, playerPos.y + chestY, playerPos.z); + // Nudge spawn slightly along body forward so projectile clears the mesh + _spawn.addScaledVector(this.forward, settings.aim?.spawnForwardM ?? 0.55); + _dir.subVectors(this.hitPoint, _spawn); + if (_dir.lengthSq() < 1e-8) { + this.forward3d.copy(this.rayDir); + } else { + this.forward3d.copy(_dir).normalize(); + } + } + + /** + * Snow-brawl style projectile launch pose. + * @param {Vector3} playerPos feet + * @param {{ hand?: 'left'|'right', handOffsetM?: number, height?: number }} [opts] + * @returns {{ origin: Vector3, direction: Vector3, target: Vector3, yaw: number }} + */ + computeLaunch(playerPos, opts = {}) { + const height = opts.height ?? settings.aim?.spawnHeight ?? 1.35; + const hand = opts.hand || 'right'; + const handOff = opts.handOffsetM ?? settings.aim?.handOffsetM ?? 0.28; + const side = hand === 'left' ? -1 : 1; + + const origin = new Vector3( + playerPos.x, + playerPos.y + height, + playerPos.z + ); + origin.addScaledVector(this.forward, settings.aim?.spawnForwardM ?? 0.55); + origin.addScaledVector(this.right, side * handOff); + + const target = this.hitPoint.clone(); + const direction = target.clone().sub(origin); + if (direction.lengthSq() < 1e-8) direction.copy(this.rayDir); + else direction.normalize(); + + return { origin, direction, target, yaw: this.yaw }; + } + + /** + * Distance on XZ from player to aim (m). + * @param {Vector3} playerPos + */ + distanceTo(playerPos) { + return Math.hypot(this.point.x - playerPos.x, this.point.z - playerPos.z); + } + + /** + * 3D distance spawn→hit. + * @param {Vector3} playerPos + */ + range3d(playerPos) { + const h = settings.aim?.spawnHeight ?? 1.35; + _spawn.set(playerPos.x, playerPos.y + h, playerPos.z); + return _spawn.distanceTo(this.hitPoint); + } +} diff --git a/src/input/PathDrawer.js b/src/input/PathDrawer.js index d5561be..c1b819e 100644 --- a/src/input/PathDrawer.js +++ b/src/input/PathDrawer.js @@ -1,19 +1,17 @@ -import { Raycaster, Plane, Vector3, CatmullRomCurve3, MathUtils } from 'three'; +import { Raycaster, Vector3, CatmullRomCurve3, MathUtils } from 'three'; import { settings } from '../config/settings.js'; import { EventEmitter } from '../utils/EventEmitter.js'; import { PathTrail } from '../effects/PathTrail.js'; - -const GROUND_PLANE = new Plane(new Vector3(0, 1, 0), 0); +import { projectToTerrain } from '../world/terrainGround.js'; /** * Turns a mouse drag into a smooth, castable spline. * * Pipeline: - * pointer NDC → raycast onto the ground plane → jitter-filtered sample list + * pointer NDC → terrain ground (mesh / sample) → jitter-filtered samples * → exponential smoothing → CatmullRomCurve3 → uniform arc-length resample. * - * The resampled polyline is what the preview ribbon and the abilities consume, - * so the visible trail and the ability trajectory are guaranteed to agree. + * Ground projection is the same helper as MouseAim — no parallel height math. * * Emits: `cast` (curve, points, length), `start`, `cancel`. */ @@ -32,6 +30,13 @@ export class PathDrawer extends EventEmitter { this.trail = new PathTrail(240); this.active = false; + /** performance.now() at begin — hold duration for staff wall/spikes */ + this._holdStart = 0; + /** Last projected point (AOE place on short cancel) */ + this.lastHit = new Vector3(); + this._combatMinLength = null; + /** @type {import('../world/terrainGround.js').TerrainGround|null} */ + this.terrain = null; this._hit = new Vector3(); this._smoothed = new Vector3(); @@ -41,14 +46,27 @@ export class PathDrawer extends EventEmitter { for (let i = 0; i < 320; i++) this.resampled.push(new Vector3()); } + /** + * Same terrain handle as MouseAim (App sets once). + * @param {import('../world/terrainGround.js').TerrainGround|null} terrain + */ + setTerrain(terrain) { + this.terrain = terrain || null; + } + + /** Combat staff casts allow shorter paths (AOE place). */ + setCombatMinLength(m) { + this._combatMinLength = Number.isFinite(m) ? m : null; + } + get object3D() { return this.trail.mesh; } - /** Project a pointer position onto the ground plane. @returns {boolean} hit */ + /** Project pointer onto terrain. @returns {boolean} hit */ _project(pointer, out) { this.raycaster.setFromCamera(pointer, this.camera); - return this.raycaster.ray.intersectPlane(GROUND_PLANE, out) !== null; + return projectToTerrain(this.raycaster, out, this.terrain); } begin(pointer) { @@ -56,7 +74,9 @@ export class PathDrawer extends EventEmitter { this.samples.length = 0; this._smoothed.copy(this._hit); this.samples.push(this._hit.clone()); + this.lastHit.copy(this._hit); this.active = true; + this._holdStart = performance.now(); this.trail.hide(); this.emit('start', this._hit); } @@ -75,6 +95,7 @@ export class PathDrawer extends EventEmitter { if (this.samples.length >= input.maxPoints) return; this.samples.push(this._smoothed.clone()); + this.lastHit.copy(this._smoothed); this._rebuild(); } @@ -83,16 +104,32 @@ export class PathDrawer extends EventEmitter { this.active = false; const length = this.pathLength(); - if (this.samples.length < 3 || length < settings.input.minPathLength) { + const holdSec = this._holdStart ? (performance.now() - this._holdStart) / 1000 : 0; + const minLen = + this._combatMinLength != null + ? this._combatMinLength + : settings.input.minPathLength; + + // Short stroke / tap: still emit cast when combat min allows AOE place + if (this.samples.length < 2 || length < minLen) { this.trail.hide(); + if (this.samples.length >= 1 && minLen <= 1.2) { + // Synthetic micro-curve at last hit for AOE placement + const p = this.lastHit.clone(); + const a = p.clone().add(new Vector3(0.05, 0.2, 0)); + const b = p.clone().add(new Vector3(0, 0.5, 0.05)); + const curve = new CatmullRomCurve3([a, b, p], false, 'catmullrom', 0.5); + this.emit('cast', curve, [p], 1, Math.max(length, 0.5), holdSec); + } else { + this.emit('cancel'); + } this.samples.length = 0; - this.emit('cancel'); return; } const curve = this._buildCurve(); this.trail.release(); // burn the preview away - this.emit('cast', curve, this.resampled, this.resampledCount, length); + this.emit('cast', curve, this.resampled, this.resampledCount, length, holdSec); this.samples.length = 0; } diff --git a/src/loaders/AssetLoader.js b/src/loaders/AssetLoader.js index 879f047..c28c815 100644 --- a/src/loaders/AssetLoader.js +++ b/src/loaders/AssetLoader.js @@ -1,34 +1,47 @@ -import { LoadingManager, TextureLoader } from 'three'; +import { LoadingManager, TextureLoader, SRGBColorSpace } from 'three'; import { FBXLoader } from 'three/addons/loaders/FBXLoader.js'; import { HDRLoader } from 'three/addons/loaders/HDRLoader.js'; +import { + bindKtx2, + getGltfLoadingManager, + getSharedDracoLoader, + gltfPipelineStatus, + isKtx2Bound, + makeGltfLoader +} from './gltfPipeline.js'; +import { DRACO_DECODER_PATH } from '../config/assets.js'; /** - * A 1×1 opaque white PNG. - * - * Authoring tools bake absolute local texture paths into FBX files (this model - * points at `C:/Users/.../textures/...`). Those requests can never resolve from - * a web server, so they are redirected here: the material keeps a neutral map - * instead of a permanently pending texture, and the console stays clean. + * A 1×1 opaque white PNG for broken absolute texture paths in legacy FBX. */ export const PLACEHOLDER_TEXTURE_URL = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=='; -/** Matches a drive-letter or UNC path that leaked into an asset reference. */ const ABSOLUTE_LOCAL_PATH = /(^|\/)[A-Za-z]:[\\/]|^\\\\/; /** - * Central asset loading with a single progress stream. + * Central asset loading — fleet-aligned glTF pipeline: + * - **One** shared DRACOLoader (WASM worker pool) + * - MeshoptDecoder always + * - KTX2/Basis after {@link AssetLoader.bindRenderer} + * - shared LoadingManager for boot progress + * + * Draco and KTX2 do **not** conflict: different glTF extensions. * - * Every loader shares one LoadingManager so the boot screen can report real - * aggregate progress instead of guessing. + * @see gltfPipeline.js · docs/LOADER_DRACO_KTX2_AUDIT.md */ export class AssetLoader { constructor() { - this.manager = new LoadingManager(); + // Prefer shared manager so Draco/KTX2 share progress surface + this.manager = getGltfLoadingManager(); this.manager.setURLModifier((url) => ABSOLUTE_LOCAL_PATH.test(url) ? PLACEHOLDER_TEXTURE_URL : url ); + // Shared Draco only — do not new DRACOLoader here again + this.draco = getSharedDracoLoader(this.manager); + this.gltf = makeGltfLoader({ manager: this.manager, shared: false }); + this.fbx = new FBXLoader(this.manager); this.hdr = new HDRLoader(this.manager); this.texture = new TextureLoader(this.manager); @@ -37,8 +50,9 @@ export class AssetLoader { this._loaded = 0; this._total = 0; this._settleWaiters = []; + this._ktx2 = false; - this.manager.onStart = (url, loaded, total) => { + this.manager.onStart = (_url, loaded, total) => { this._loaded = loaded; this._total = total; }; @@ -54,17 +68,32 @@ export class AssetLoader { this.manager.onError = (url) => console.error(`[AssetLoader] failed: ${url}`); } + /** + * Call once after WebGLRenderer exists — enables KTX2 textures on GLBs. + * @param {import('three').WebGLRenderer} renderer + */ + bindRenderer(renderer) { + this._ktx2 = bindKtx2(renderer); + // Re-attach shared KTX2 to this loader instance + if (this._ktx2) { + this.gltf = makeGltfLoader({ manager: this.manager, shared: false, renderer }); + } + return this._ktx2; + } + + /** Pipeline diagnostics (boot / toast). */ + pipelineStatus() { + return { + ...gltfPipelineStatus(), + ktx2Bound: isKtx2Bound() || this._ktx2, + dracoPathConfig: DRACO_DECODER_PATH + }; + } + onProgress(callback) { this._onProgress = callback; } - /** - * Resolves once every queued request has settled. - * - * Loaders resolve as soon as the *model* is parsed; its textures are still in - * flight at that point, so anything that inspects `texture.image` has to wait - * for this first or it will read a half-initialised texture. - */ settled() { if (this._total === 0 || this._loaded >= this._total) return Promise.resolve(); return new Promise((resolve) => this._settleWaiters.push(resolve)); @@ -84,17 +113,54 @@ export class AssetLoader { }); } - /** @returns {Promise} */ - loadTexture(url) { + /** + * Load glTF / GLB (Draco / Meshopt / KTX2 when present on asset). + * @returns {Promise} + */ + loadGLTF(url) { return new Promise((resolve, reject) => { - this.texture.load(encodeURI(url), resolve, undefined, reject); + this.gltf.load( + url, + resolve, + (event) => { + if (event.lengthComputable) this._onProgress?.(event.loaded / event.total, url); + }, + reject + ); }); } - /** @returns {Promise} */ + /** + * Color texture — sRGB for albedo/atlas (not data/normal maps). + * @param {string} url + * @param {{ colorSpace?: boolean }} [opts] + * @returns {Promise} + */ + loadTexture(url, opts = {}) { + return new Promise((resolve, reject) => { + this.texture.load( + url, + (tex) => { + if (opts.colorSpace !== false) { + tex.colorSpace = SRGBColorSpace; + } + resolve(tex); + }, + undefined, + reject + ); + }); + } + + /** @returns {Promise} */ loadHDR(url) { return new Promise((resolve, reject) => { this.hdr.load(encodeURI(url), resolve, undefined, reject); }); } + + dispose() { + // Do not dispose process-wide Draco — other systems may still use it. + // Tests: import { disposeGltfPipeline } from './gltfPipeline.js' + } } diff --git a/src/loaders/gltfPipeline.js b/src/loaders/gltfPipeline.js new file mode 100644 index 0000000..d5c52b8 --- /dev/null +++ b/src/loaders/gltfPipeline.js @@ -0,0 +1,183 @@ +/** + * Shared glTF decode pipeline — one Draco worker pool · Meshopt · optional KTX2. + * + * **No conflict between Draco and KTX2:** they are different glTF extensions. + * A plain GLB needs neither. Compressed geometry uses Draco and/or Meshopt; + * GPU-compressed *textures* use KTX2/Basis (only after bindKtx2(renderer)). + * + * Conflicts we *do* avoid: + * - Multiple DRACOLoader instances → multiple WASM workers (memory thrash) + * - Wrong decoder path major vs three + * - KTX2 without detectSupport(renderer) → silent missing textures + * - KTX2 transcoder pinned to wrong three version + * + * @see docs/LOADER_DRACO_KTX2_AUDIT.md + * @see gameopen artifacts/animator/src/three/loaders/gltf.ts (fleet twin) + */ + +import { LoadingManager, REVISION } from 'three'; +import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'; +import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js'; +import { KTX2Loader } from 'three/addons/loaders/KTX2Loader.js'; +import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js'; + +/** + * Google versioned Draco (recommended). Matches three r185 examples. + * Unversioned `…/draco/v1/decoders/` is legacy and can drift. + */ +export const DRACO_DECODER_PATH = + 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/'; + +/** + * Basis transcoder path pinned to the **installed** three major. + * three r185 → package 0.185.x on jsDelivr (same as node_modules/three). + */ +export const KTX2_TRANSCODER_PATH = `https://cdn.jsdelivr.net/npm/three@0.185.1/examples/jsm/libs/basis/`; + +/** @type {LoadingManager|null} */ +let sharedManager = null; +/** @type {DRACOLoader|null} */ +let sharedDraco = null; +/** @type {KTX2Loader|null} */ +let sharedKtx2 = null; +/** @type {GLTFLoader|null} */ +let sharedGltf = null; +let ktx2Bound = false; + +export function getGltfLoadingManager() { + if (!sharedManager) sharedManager = new LoadingManager(); + return sharedManager; +} + +/** + * Process-wide Draco (expensive WASM — never new DRACOLoader per projectile). + * @param {LoadingManager} [manager] + */ +export function getSharedDracoLoader(manager) { + if (!sharedDraco) { + sharedDraco = new DRACOLoader(manager || getGltfLoadingManager()); + sharedDraco.setDecoderPath(DRACO_DECODER_PATH); + // Preload only in browser (ProgressEvent is window-only) + if (typeof window !== 'undefined' && typeof ProgressEvent !== 'undefined') { + try { + sharedDraco.preload(); + } catch { + /* optional */ + } + } + } + return sharedDraco; +} + +/** + * Bind KTX2/Basis to the shared GLTF loader. Requires a live WebGLRenderer + * for GPU support detection. Safe to call multiple times. + * @param {import('three').WebGLRenderer} renderer + */ +export function bindKtx2(renderer) { + if (!renderer) return false; + const manager = getGltfLoadingManager(); + if (ktx2Bound && sharedKtx2) { + try { + sharedKtx2.detectSupport(renderer); + } catch { + /* ignore */ + } + return true; + } + try { + sharedKtx2 = new KTX2Loader(manager) + .setTranscoderPath(KTX2_TRANSCODER_PATH) + .detectSupport(renderer); + ktx2Bound = true; + if (sharedGltf) sharedGltf.setKTX2Loader(sharedKtx2); + console.info( + `[gltfPipeline] KTX2 bound · three r${REVISION} · basis ${KTX2_TRANSCODER_PATH}` + ); + return true; + } catch (e) { + console.warn('[gltfPipeline] KTX2 bind failed (non-fatal; WebP/PNG still load)', e); + return false; + } +} + +export function isKtx2Bound() { + return ktx2Bound; +} + +/** + * Build or return shared GLTFLoader with Draco + Meshopt (+ KTX2 if bound). + * @param {{ + * manager?: LoadingManager, + * renderer?: import('three').WebGLRenderer, + * shared?: boolean + * }} [opts] + */ +export function makeGltfLoader(opts = {}) { + if (opts.renderer) bindKtx2(opts.renderer); + + if (opts.shared !== false && !opts.manager) { + if (!sharedGltf) { + sharedGltf = new GLTFLoader(getGltfLoadingManager()); + sharedGltf.setDRACOLoader(getSharedDracoLoader()); + sharedGltf.setMeshoptDecoder(MeshoptDecoder); + if (sharedKtx2) sharedGltf.setKTX2Loader(sharedKtx2); + } + return sharedGltf; + } + + const manager = opts.manager || getGltfLoadingManager(); + const loader = new GLTFLoader(manager); + loader.setDRACOLoader(getSharedDracoLoader(manager)); + loader.setMeshoptDecoder(MeshoptDecoder); + if (sharedKtx2) loader.setKTX2Loader(sharedKtx2); + return loader; +} + +/** @returns {GLTFLoader} */ +export function sharedGltfLoader() { + return makeGltfLoader({ shared: true }); +} + +/** + * Diagnostics for HUD / boot toast. + * @returns {{ + * threeRevision: string, + * dracoPath: string, + * ktx2Path: string, + * ktx2Bound: boolean, + * meshopt: boolean, + * sharedDraco: boolean + * }} + */ +export function gltfPipelineStatus() { + return { + threeRevision: String(REVISION), + dracoPath: DRACO_DECODER_PATH, + ktx2Path: KTX2_TRANSCODER_PATH, + ktx2Bound, + meshopt: !!MeshoptDecoder, + sharedDraco: !!sharedDraco + }; +} + +/** + * Dispose process-wide decoders (tests / HMR only — rare). + */ +export function disposeGltfPipeline() { + try { + sharedDraco?.dispose?.(); + } catch { + /* */ + } + try { + sharedKtx2?.dispose?.(); + } catch { + /* */ + } + sharedDraco = null; + sharedKtx2 = null; + sharedGltf = null; + ktx2Bound = false; + sharedManager = null; +} diff --git a/src/loot/dropTables.js b/src/loot/dropTables.js new file mode 100644 index 0000000..db870d9 --- /dev/null +++ b/src/loot/dropTables.js @@ -0,0 +1,644 @@ +/** + * Drop / tier rate SSOT runtime (fleet). + * + * Source JSON: info/objectstore api/v1/drop-tables.json + * + * TIER POLICY: + * - Prefabs exist for T0–T8 (full icon/model/world presentation). + * - NATURAL loot (mobs, common chests, harvest) max tier = 5. + * - T6/T7/T8 are NOT natural drops — only special sources: + * player_death (corpse of holder), special_chest, dungeon_loot, raid_mythic + * - T0 always allowed for materials, potions, foods, thrown. + * - playerLevel + difficulty shape natural rates. + */ + +import { catalogJsonUrls } from '../config/fleetEnv.js'; + +export const DROP_TABLES_URL = 'https://info.grudge-studio.com/api/v1/drop-tables.json'; +/** Live Pages — objectstore.grudge-studio.com/api/v1 is 404 */ +export const DROP_TABLES_MIRROR = + 'https://grudge-objectstore.pages.dev/api/v1/drop-tables.json'; + +/** Absolute catalog max (prefabs / craft / special loot) */ +export const CATALOG_MAX_TIER = 8; + +/** Natural world loot ceiling (mobs, common chests, harvest, normal bosses) */ +export const NATURAL_MAX_DROP_TIER = 5; + +/** Mythic+ — full prefab pattern, not natural RNG */ +export const MYTHIC_TIERS = Object.freeze([6, 7, 8]); + +/** @deprecated use NATURAL_MAX_DROP_TIER — kept for callers that still import MAX_DROP_TIER */ +export const MAX_DROP_TIER = NATURAL_MAX_DROP_TIER; + +/** @deprecated natural ban only — special sources may roll these */ +export const BANNED_DROP_TIERS = MYTHIC_TIERS; + +/** Sources that may include T6–T8 (still rate-limited) */ +export const MYTHIC_ALLOWED_SOURCES = Object.freeze([ + 'player_death', + 'special_chest', + 'dungeon_loot', + 'dungeon_boss', + 'raid_mythic', + 'event_reward' +]); + +/** @typedef {'trivial'|'easy'|'normal'|'hard'|'elite'|'boss'|'raid'} DifficultyId */ + +export const DIFFICULTY = Object.freeze({ + trivial: { id: 'trivial', levelBias: -8, qtyMul: 0.7, rareMul: 0.35, t0WeightMul: 1.6 }, + easy: { id: 'easy', levelBias: -4, qtyMul: 0.85, rareMul: 0.6, t0WeightMul: 1.3 }, + normal: { id: 'normal', levelBias: 0, qtyMul: 1.0, rareMul: 1.0, t0WeightMul: 1.0 }, + hard: { id: 'hard', levelBias: 6, qtyMul: 1.15, rareMul: 1.45, t0WeightMul: 0.85 }, + elite: { id: 'elite', levelBias: 12, qtyMul: 1.35, rareMul: 1.9, t0WeightMul: 0.7 }, + boss: { id: 'boss', levelBias: 18, qtyMul: 1.6, rareMul: 2.4, t0WeightMul: 0.55 }, + raid: { id: 'raid', levelBias: 24, qtyMul: 2.0, rareMul: 3.0, t0WeightMul: 0.4 } +}); + +const SOFT_BY_MAX = { + 1: { 0: 55, 1: 45 }, + 2: { 0: 35, 1: 40, 2: 25 }, + 3: { 0: 25, 1: 32, 2: 28, 3: 15 }, + 4: { 0: 18, 1: 26, 2: 28, 3: 18, 4: 10 }, + 5: { 0: 14, 1: 22, 2: 26, 3: 20, 4: 12, 5: 6 }, + // Special-source soft curves (include mythic tails) + 6: { 0: 10, 1: 16, 2: 20, 3: 18, 4: 14, 5: 12, 6: 10 }, + 7: { 0: 8, 1: 12, 2: 16, 3: 16, 4: 14, 5: 14, 6: 12, 7: 8 }, + 8: { 0: 6, 1: 10, 2: 14, 3: 14, 4: 14, 5: 14, 6: 12, 7: 10, 8: 6 } +}; + +const DISTANCE_DECAY = 0.55; + +export const T0_CATEGORIES = Object.freeze([ + 'materials', + 'potions', + 'foods', + 'thrown', + 'consumables', + 'junk' +]); + +const T0_FLOOR = { + materials: 0.2, + potions: 0.25, + foods: 0.3, + thrown: 0.22, + consumables: 0.25, + junk: 0.5, + weapons: 0.08, + armor: 0.08 +}; + +export const DROP_SOURCES = Object.freeze({ + mob_trash: { + rolls: [0, 2], + emptyChance: 0.35, + defaultDifficulty: 'easy', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + junk: 35, + materials: 30, + foods: 15, + potions: 10, + thrown: 8, + weapons: 1, + armor: 1 + } + }, + mob_normal: { + rolls: [1, 3], + emptyChance: 0.15, + defaultDifficulty: 'normal', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + materials: 28, + foods: 16, + potions: 14, + thrown: 12, + junk: 12, + weapons: 10, + armor: 8 + } + }, + mob_elite: { + rolls: [2, 4], + emptyChance: 0.05, + defaultDifficulty: 'elite', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + materials: 20, + potions: 16, + thrown: 12, + foods: 10, + weapons: 22, + armor: 18, + junk: 2 + } + }, + chest_common: { + rolls: [2, 4], + emptyChance: 0, + defaultDifficulty: 'normal', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + materials: 25, + foods: 20, + potions: 20, + thrown: 15, + weapons: 10, + armor: 8, + junk: 2 + } + }, + chest_uncommon: { + rolls: [3, 5], + emptyChance: 0, + defaultDifficulty: 'hard', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + materials: 18, + potions: 18, + foods: 12, + thrown: 12, + weapons: 20, + armor: 18, + junk: 2 + } + }, + boss: { + rolls: [3, 6], + emptyChance: 0, + defaultDifficulty: 'boss', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + weapons: 28, + armor: 24, + materials: 16, + potions: 14, + thrown: 10, + foods: 6, + junk: 2 + }, + guaranteed: [ + { category: 'materials', tier: 0, qty: [2, 5] }, + { category: 'potions', tier: 0, qty: [1, 3] } + ] + }, + raid: { + rolls: [5, 8], + emptyChance: 0, + defaultDifficulty: 'raid', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { + weapons: 30, + armor: 28, + materials: 14, + potions: 12, + thrown: 8, + foods: 6, + junk: 2 + }, + guaranteed: [ + { category: 'materials', tier: 0, qty: [4, 10] }, + { category: 'foods', tier: 0, qty: [2, 6] }, + { category: 'potions', tier: 0, qty: [2, 5] } + ] + }, + harvest_node: { + rolls: [1, 3], + emptyChance: 0, + defaultDifficulty: 'normal', + maxTierCap: NATURAL_MAX_DROP_TIER, + allowMythic: false, + categories: { materials: 85, foods: 10, junk: 5 }, + t0Boost: 1.5 + }, + + /* ── Special: may include T6–T8 (prefab systems fully supported) ── */ + + /** Corpse of player — drops what they held (any tier including T6–8) */ + player_death: { + rolls: [0, 0], + emptyChance: 0, + defaultDifficulty: 'normal', + maxTierCap: CATALOG_MAX_TIER, + allowMythic: true, + allowHeldGear: true, + categories: { + weapons: 40, + armor: 40, + materials: 10, + potions: 5, + foods: 5 + }, + notes: 'Primary path: spill equipped/bag items as-is. Optional fill rolls use mythic-capable weights.' + }, + special_chest: { + rolls: [3, 6], + emptyChance: 0, + defaultDifficulty: 'elite', + maxTierCap: CATALOG_MAX_TIER, + allowMythic: true, + mythicChance: 0.08, + categories: { + weapons: 30, + armor: 28, + materials: 12, + potions: 14, + thrown: 8, + foods: 6, + junk: 2 + }, + guaranteed: [{ category: 'materials', tier: 0, qty: [2, 4] }] + }, + dungeon_loot: { + rolls: [2, 5], + emptyChance: 0.05, + defaultDifficulty: 'hard', + maxTierCap: CATALOG_MAX_TIER, + allowMythic: true, + mythicChance: 0.05, + categories: { + weapons: 26, + armor: 24, + materials: 16, + potions: 14, + thrown: 10, + foods: 8, + junk: 2 + } + }, + dungeon_boss: { + rolls: [4, 7], + emptyChance: 0, + defaultDifficulty: 'boss', + maxTierCap: CATALOG_MAX_TIER, + allowMythic: true, + mythicChance: 0.12, + categories: { + weapons: 32, + armor: 28, + materials: 12, + potions: 12, + thrown: 8, + foods: 6, + junk: 2 + }, + guaranteed: [ + { category: 'materials', tier: 0, qty: [3, 6] }, + { category: 'potions', tier: 0, qty: [1, 3] } + ] + }, + raid_mythic: { + rolls: [5, 9], + emptyChance: 0, + defaultDifficulty: 'raid', + maxTierCap: CATALOG_MAX_TIER, + allowMythic: true, + mythicChance: 0.18, + categories: { + weapons: 34, + armor: 30, + materials: 12, + potions: 10, + thrown: 8, + foods: 4, + junk: 2 + }, + guaranteed: [ + { category: 'materials', tier: 0, qty: [4, 10] }, + { category: 'potions', tier: 0, qty: [2, 5] } + ] + }, + event_reward: { + rolls: [1, 3], + emptyChance: 0, + defaultDifficulty: 'elite', + maxTierCap: CATALOG_MAX_TIER, + allowMythic: true, + mythicChance: 0.1, + categories: { + weapons: 30, + armor: 30, + materials: 15, + potions: 15, + foods: 10 + } + } +}); + +export function clamp(n, lo, hi) { + return Math.max(lo, Math.min(hi, n)); +} + +export function isMythicSource(sourceId) { + return MYTHIC_ALLOWED_SOURCES.includes(sourceId); +} + +export function isNaturalSource(sourceId) { + return !isMythicSource(sourceId); +} + +/** + * Max natural drop tier from player level (never > 5). + * @param {number} playerLevel + */ +export function maxTierFromPlayerLevel(playerLevel) { + const lv = clamp(Number(playerLevel) || 1, 1, 999); + if (lv < 10) return 1; + if (lv < 20) return 2; + if (lv < 30) return 3; + if (lv < 40) return 4; + return NATURAL_MAX_DROP_TIER; +} + +export function effectiveLevel(playerLevel, difficulty = 'normal') { + const d = typeof difficulty === 'string' ? DIFFICULTY[difficulty] : difficulty; + const bias = d?.levelBias ?? 0; + return clamp((Number(playerLevel) || 1) + bias, 1, 70); +} + +/** + * Max tier for a given source (natural ≤5, special ≤8). + * @param {number} playerLevel + * @param {DifficultyId|object} difficulty + * @param {string} [sourceId] + */ +export function maxDropTier(playerLevel, difficulty = 'normal', sourceId = 'mob_normal') { + const src = DROP_SOURCES[sourceId]; + const el = effectiveLevel(playerLevel, difficulty); + const fromLevel = maxTierFromPlayerLevel(el); + if (src?.allowMythic) { + // Special: allow up to 8, but soft-scale by level (T6 at high level, T8 rare) + const specialCap = src.maxTierCap ?? CATALOG_MAX_TIER; + const mythicUnlock = el >= 45 ? 6 : NATURAL_MAX_DROP_TIER; + const t7 = el >= 55 ? 7 : mythicUnlock; + const t8 = el >= 65 ? 8 : t7; + return Math.min(specialCap, Math.max(fromLevel, Math.min(t8, specialCap))); + } + return Math.min(NATURAL_MAX_DROP_TIER, fromLevel, src?.maxTierCap ?? NATURAL_MAX_DROP_TIER); +} + +export function targetDropTier(playerLevel, difficulty = 'normal', sourceId = 'mob_normal') { + const el = effectiveLevel(playerLevel, difficulty); + const maxT = maxDropTier(playerLevel, difficulty, sourceId); + return clamp(Math.floor((el - 1) / 10), 0, maxT); +} + +/** + * @param {number} playerLevel + * @param {DifficultyId|object} difficulty + * @param {string} category + * @param {{ t0Boost?: number, sourceId?: string }} [opts] + */ +export function tierWeights(playerLevel, difficulty = 'normal', category = 'materials', opts = {}) { + const d = typeof difficulty === 'string' ? DIFFICULTY[difficulty] : difficulty || DIFFICULTY.normal; + const sourceId = opts.sourceId || 'mob_normal'; + const maxT = maxDropTier(playerLevel, d, sourceId); + const target = targetDropTier(playerLevel, d, sourceId); + const soft = SOFT_BY_MAX[maxT] || SOFT_BY_MAX[5]; + const src = DROP_SOURCES[sourceId]; + /** @type {Record} */ + const w = {}; + + for (let t = 0; t <= maxT; t++) { + // Natural sources never weight T6–8 + if (!src?.allowMythic && MYTHIC_TIERS.includes(t)) continue; + + let wt = soft[t] ?? 1; + const dist = Math.abs(t - target); + wt *= Math.pow(DISTANCE_DECAY, dist); + if (t >= 3 && t <= 5) wt *= d.rareMul ?? 1; + // Mythic tail: rare even on special sources + if (MYTHIC_TIERS.includes(t)) { + const mc = src?.mythicChance ?? 0.05; + wt *= mc * (t === 6 ? 1.0 : t === 7 ? 0.55 : 0.3); + } + if (t === 0) { + const floor = T0_FLOOR[category] ?? 0.1; + let t0m = (d.t0WeightMul ?? 1) * (1 + floor); + if (opts.t0Boost) t0m *= opts.t0Boost; + if (T0_CATEGORIES.includes(category)) t0m = Math.max(t0m, 1 + floor); + wt *= t0m; + } + w[t] = Math.max(0.001, wt); + } + return w; +} + +export function pickTier(weights, rng = Math.random) { + const entries = Object.entries(weights) + .map(([t, w]) => [Number(t), w]) + .filter(([t, w]) => t >= 0 && t <= CATALOG_MAX_TIER && w > 0); + const sum = entries.reduce((a, [, w]) => a + w, 0); + if (sum <= 0) return 0; + let r = rng() * sum; + for (const [t, w] of entries) { + r -= w; + if (r <= 0) return t; + } + return entries[entries.length - 1][0]; +} + +export function pickWeightedKey(weights, rng = Math.random) { + const entries = Object.entries(weights).filter(([, w]) => w > 0); + const sum = entries.reduce((a, [, w]) => a + w, 0); + if (sum <= 0) return null; + let r = rng() * sum; + for (const [k, w] of entries) { + r -= w; + if (r <= 0) return k; + } + return entries[entries.length - 1][0]; +} + +function randInt(min, max, rng = Math.random) { + const a = Math.floor(min); + const b = Math.floor(max); + return a + Math.floor(rng() * (b - a + 1)); +} + +/** + * Roll loot. Natural sources never emit T6–8. Special sources may. + * @param {{ + * source: keyof typeof DROP_SOURCES, + * playerLevel: number, + * difficulty?: DifficultyId, + * heldItems?: object[], + * rng?: () => number + * }} opts + */ +export function rollLoot(opts) { + const sourceId = opts.source || 'mob_normal'; + const src = DROP_SOURCES[sourceId]; + if (!src) throw new Error(`Unknown drop source: ${sourceId}`); + + const difficultyId = opts.difficulty || src.defaultDifficulty || 'normal'; + const d = DIFFICULTY[difficultyId] || DIFFICULTY.normal; + const playerLevel = clamp(Number(opts.playerLevel) || 1, 1, 999); + const rng = opts.rng || Math.random; + const maxT = maxDropTier(playerLevel, d, sourceId); + const target = targetDropTier(playerLevel, d, sourceId); + const allowMythic = !!src.allowMythic; + + /** @type {object[]} */ + const drops = []; + + // Player death: spill held gear first (any tier, full prefab identity) + if (src.allowHeldGear && Array.isArray(opts.heldItems)) { + for (const held of opts.heldItems) { + const tier = clamp(Number(held.tier) ?? 0, 0, CATALOG_MAX_TIER); + drops.push({ + category: held.category || 'weapons', + tier, + qty: held.qty || 1, + sourceId, + fromPlayerDeath: true, + itemId: held.id || held.itemId || null, + uuid: held.uuid || null, + name: held.name || null, + iconUrl: held.iconUrl || null, + modelUrl: held.modelUrl || null, + present: held + }); + } + } + + if (rng() < (src.emptyChance || 0) && !drops.length) { + return emptyResult(sourceId, difficultyId, playerLevel, d, maxT, target, allowMythic, true); + } + + const [rmin, rmax] = src.rolls; + let n = rmin === 0 && rmax === 0 ? 0 : randInt(rmin, rmax, rng); + n = Math.max(0, Math.round(n * (d.qtyMul || 1))); + + for (let i = 0; i < n; i++) { + const category = pickWeightedKey(src.categories, rng) || 'materials'; + const weights = tierWeights(playerLevel, d, category, { + t0Boost: src.t0Boost, + sourceId + }); + let tier = pickTier(weights, rng); + tier = clamp(tier, 0, maxT); + + // Enforce natural ban even if weights misconfigured + if (!allowMythic && MYTHIC_TIERS.includes(tier)) { + tier = NATURAL_MAX_DROP_TIER; + } + + drops.push({ + category, + tier, + qty: 1, + sourceId, + mythic: MYTHIC_TIERS.includes(tier), + itemId: null, + name: null + }); + } + + for (const g of src.guaranteed || []) { + const qty = Array.isArray(g.qty) ? randInt(g.qty[0], g.qty[1], rng) : g.qty || 1; + let tier = g.tier ?? 0; + if (!allowMythic && MYTHIC_TIERS.includes(tier)) tier = 0; + if (tier > maxT) tier = Math.min(tier, maxT); + drops.push({ + category: g.category, + tier, + qty, + sourceId, + guaranteed: true, + itemId: null, + name: null + }); + } + + return { + drops, + meta: { + sourceId, + difficultyId, + playerLevel, + effectiveLevel: effectiveLevel(playerLevel, d), + maxTier: maxT, + targetTier: target, + allowMythic, + naturalMaxTier: NATURAL_MAX_DROP_TIER, + catalogMaxTier: CATALOG_MAX_TIER, + mythicTiers: MYTHIC_TIERS.slice(), + empty: drops.length === 0 + } + }; +} + +function emptyResult(sourceId, difficultyId, playerLevel, d, maxT, target, allowMythic, empty) { + return { + drops: [], + meta: { + sourceId, + difficultyId, + playerLevel, + effectiveLevel: effectiveLevel(playerLevel, d), + maxTier: maxT, + targetTier: target, + allowMythic, + naturalMaxTier: NATURAL_MAX_DROP_TIER, + catalogMaxTier: CATALOG_MAX_TIER, + mythicTiers: MYTHIC_TIERS.slice(), + empty + } + }; +} + +/** + * Natural loot: reject T6–8. Special: allow 0–8. + * @param {number} tier + * @param {string} [sourceId] + */ +export function assertDropTierLegal(tier, sourceId = 'mob_normal') { + const t = Number(tier); + if (t < 0 || t > CATALOG_MAX_TIER) { + throw new Error(`Illegal tier ${t}: catalog is T0–T8`); + } + if (!isMythicSource(sourceId) && MYTHIC_TIERS.includes(t)) { + throw new Error( + `Illegal natural drop tier ${t}: T6–T8 only from player_death / special_chest / dungeon / raid_mythic` + ); + } + return true; +} + +/** + * Spill equipped + bag items on player death (any tier keeps prefab systems). + * @param {object[]} heldItems + * @param {{ playerLevel?: number }} [opts] + */ +export function rollPlayerDeathDrops(heldItems, opts = {}) { + return rollLoot({ + source: 'player_death', + playerLevel: opts.playerLevel ?? 1, + difficulty: 'normal', + heldItems: heldItems || [] + }); +} + +export async function loadDropTablesRemote() { + for (const url of catalogJsonUrls('drop-tables.json')) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) continue; + return await res.json(); + } catch { + /* next */ + } + } + return null; +} diff --git a/src/loot/prefabAssets.js b/src/loot/prefabAssets.js new file mode 100644 index 0000000..38580e1 --- /dev/null +++ b/src/loot/prefabAssets.js @@ -0,0 +1,208 @@ +/** + * Resolve master-weapon-prefab presentation assets for world drops. + * + * SSOT: info…/api/v1/master-weapon-prefabs.json + * Layers: + * icon — 2D bag + ground sprite + * model — equip / world 3D (prod/gltf or models/weapons) + * drop — optional loot VFX / dropPrefab key (may 404 → sprite+model fallback) + */ + +import { catalogJsonUrls, cdnUrl as fleetCdnUrl } from '../config/fleetEnv.js'; + +export const PREFAB_CATALOG_URL = + 'https://info.grudge-studio.com/api/v1/master-weapon-prefabs.json'; +/** Live Pages — objectstore.grudge-studio.com/api/v1 is 404 */ +export const PREFAB_CATALOG_MIRROR = + 'https://grudge-objectstore.pages.dev/api/v1/master-weapon-prefabs.json'; +export const CDN = 'https://assets.grudge-studio.com'; + +/** + * Tier border / glow — full prefab pattern T0–T8. + * Natural loot only rolls ≤T5; T6–T8 still present on corpse / special / dungeon. + */ +export const TIER_PRESENT = Object.freeze({ + 0: { border: '#9aa3ad', glow: 0x8a93a0, label: 'T0' }, + 1: { border: '#e8eef6', glow: 0xd0d8e4, label: 'T1' }, + 2: { border: '#5dcf7a', glow: 0x3db85c, label: 'T2' }, + 3: { border: '#5eb0ff', glow: 0x3a8fe0, label: 'T3' }, + 4: { border: '#c48bff', glow: 0xa060e8, label: 'T4' }, + 5: { border: '#ffb84d', glow: 0xff9a1a, label: 'T5' }, + 6: { border: '#ff6b9d', glow: 0xff3d7a, label: 'T6 Mythic' }, + 7: { border: '#ff4d4d', glow: 0xe02020, label: 'T7 Ancient' }, + 8: { border: '#ffe566', glow: 0xffd000, label: 'T8 Divine' } +}); + +export function tierPresent(tier) { + const t = Math.max(0, Math.min(8, Number(tier) || 0)); + return TIER_PRESENT[t] || TIER_PRESENT[0]; +} + +/** + * Absolute CDN URL from path or full URL. + * @param {string|null|undefined} path + */ +export function cdnUrl(path) { + return fleetCdnUrl(path); +} + +/** + * Normalize one catalog prefab into world-drop presentation. + * @param {object} prefab master-weapon-prefabs entry + */ +export function presentPrefab(prefab) { + if (!prefab) return null; + // Full catalog tiers T0–T8 (presentation always; natural loot still caps at T5) + const tier = Math.max(0, Math.min(8, Number(prefab.tier) ?? 0)); + const iconUrl = + prefab.assets?.iconUrl || + cdnUrl(prefab.assets?.iconR2Key) || + cdnUrl(prefab.icon?.cdnUrl || prefab.icon?.path) || + cdnUrl('icons/pack/weapons/Sword_01.png'); + + const wt = String(prefab.weaponType || prefab.category || '').toUpperCase(); + const id = String(prefab.id || ''); + // Authored T0 meshes (R2) before family fallbacks + let familyFallback = 'prod/gltf/weapons/sword.glb'; + if (/WAND|STAFF|TOME|MAGIC|NATURE_STAFF|FIRE_STAFF|FROST|HOLY|LIGHTNING|ARCANE/i.test(wt + id)) { + familyFallback = 'prod/gltf/weapons/staff.glb'; + } else if (/BOW|CROSSBOW|GUN|RIFLE|PISTOL/i.test(wt)) { + familyFallback = 'prod/gltf/weapons/bow.glb'; + } else if (/AXE|GREATAXE/i.test(wt)) { + familyFallback = 'prod/gltf/weapons/axe.glb'; + } else if (/HAMMER|MACE/i.test(wt)) { + familyFallback = 'prod/gltf/weapons/hammer.glb'; + } else if (/DAGGER/i.test(wt)) { + familyFallback = 'prod/gltf/weapons/dagger.glb'; + } else if (/SHIELD/i.test(wt)) { + // shield.glb not on CDN yet — axe body as temporary offhand silhouette + familyFallback = 'prod/gltf/weapons/axe.glb'; + } + if (id === 't0-wand') familyFallback = 'prod/gltf/weapons/t0-wand.glb'; + if (id === 't0-nature-staff') familyFallback = 'prod/gltf/weapons/t0-nature-staff.glb'; + + const modelUrl = + prefab.modelUrl || + prefab.prodGltfUrl || + prefab.mesh?.prodGltfUrl || + prefab.assets?.modelUrl || + cdnUrl(prefab.assets?.modelR2Key || prefab.modelPath || prefab.prodGltfKey) || + cdnUrl(familyFallback); + + const dropPrefabUrl = prefab.assets?.dropPrefabR2Key + ? cdnUrl(prefab.assets.dropPrefabR2Key) + : null; + const lootVfxUrl = prefab.assets?.worldDropVfxR2Key + ? cdnUrl(prefab.assets.worldDropVfxR2Key) + : null; + + const present = tierPresent(tier); + + return { + id: prefab.id || prefab.uuid, + uuid: prefab.uuid || null, + name: prefab.name || prefab.baseName || prefab.id || 'Item', + tier, + weaponType: prefab.weaponType || prefab.category || 'SWORD', + category: prefab.category || 'weapons', + iconUrl, + modelUrl, + dropPrefabUrl, + lootVfxUrl, + borderColor: present.border, + glowColor: present.glow, + tierLabel: present.label, + /** World state — never equipped */ + state: 'world', + equipped: false, + raw: prefab + }; +} + +/** + * Minimal bag / throw item from presentation. + * @param {ReturnType} p + * @param {number} [qty] + */ +export function bagItemFromPresent(p, qty = 1) { + if (!p) return null; + return { + id: p.id, + uuid: p.uuid, + name: p.name, + tier: p.tier, + qty, + iconUrl: p.iconUrl, + modelUrl: p.modelUrl, + weaponType: p.weaponType, + category: p.category, + borderColor: p.borderColor, + glowColor: p.glowColor + }; +} + +let _cache = null; +let _loading = null; + +/** + * Load prefab catalog (cached). Large file — keep in memory once. + */ +export async function loadPrefabCatalog() { + if (_cache) return _cache; + if (_loading) return _loading; + _loading = (async () => { + let data = null; + for (const url of catalogJsonUrls('master-weapon-prefabs.json')) { + try { + const res = await fetch(url, { mode: 'cors' }); + if (!res.ok) continue; + data = await res.json(); + break; + } catch { + /* next */ + } + } + if (!data?.prefabs?.length) { + _loading = null; + throw new Error('master-weapon-prefabs unreachable'); + } + const byId = new Map(); + const list = []; + for (const raw of data.prefabs) { + const p = presentPrefab(raw); + if (!p) continue; + list.push(p); + byId.set(p.id, p); + if (p.uuid) byId.set(p.uuid, p); + } + _cache = { + version: data.version || '1.0.0', + total: list.length, + list, + byId, + /** Raw master-weapon-prefabs entries (skills, stats, assets) for equip/export */ + _rawPrefabs: data.prefabs, + prodGltfWeaponMap: data.prodGltfWeaponMap || null, + cdnBase: data.cdnBase || CDN, + r2Layout: data.r2Layout || null + }; + _loading = null; + return _cache; + })(); + return _loading; +} + +/** + * Pick a sample prefab for lab (tier-capped for drops). + * @param {Awaited>} cat + * @param {{ weaponType?: string, maxTier?: number }} [opts] + */ +export function pickSamplePrefab(cat, opts = {}) { + const maxTier = opts.maxTier ?? 5; + const type = opts.weaponType ? String(opts.weaponType).toUpperCase() : null; + let pool = cat.list.filter((p) => p.tier <= maxTier); + if (type) pool = pool.filter((p) => p.weaponType === type); + if (!pool.length) pool = cat.list.filter((p) => p.tier <= maxTier); + if (!pool.length) return null; + return pool[Math.floor(Math.random() * pool.length)]; +} diff --git a/src/materials/SailCloth.js b/src/materials/SailCloth.js new file mode 100644 index 0000000..7fa1fff --- /dev/null +++ b/src/materials/SailCloth.js @@ -0,0 +1,207 @@ +/** + * Sail / cloth material practice — fleet texture + physics rules. + * + * **Textures (GLTF embed / CDN):** + * - baseColor → colorSpace = SRGBColorSpace, flipY = false (GLTF) + * - metalnessRoughness → no colorSpace convert (linear data) + * - normal → linear, typically OpenGL convention from glTF + * + * **Physics / SI:** + * - Mesh scale from BackSlotEquip stow length (not 100× author cm) + * - Cloth is **visual only** (vertex wind) — no second soft-body engine + * - Mass/air feel via amplitude + frequency, not Rapier cloth + * + * **Look:** double-sided sail, low metal, high roughness, wind wave along UV height. + * + * @see BackSlotEquip.js · docs/WINDSURF_RIDE_SSOT.md + */ + +import { + Color, + DoubleSide, + MeshStandardMaterial, + SRGBColorSpace, + Vector2 +} from 'three'; + +/** + * Normalize glTF materials for cloth/sail production rules. + * @param {import('three').Object3D} root + * @param {{ clothTint?: string, forceCloth?: boolean }} [opts] + */ +export function applySailClothMaterials(root, opts = {}) { + if (!root) return 0; + let n = 0; + root.traverse((o) => { + if (!o.isMesh || !o.material) return; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (let i = 0; i < mats.length; i++) { + const m = mats[i]; + if (!m) continue; + const name = `${m.name || ''} ${o.name || ''}`.toLowerCase(); + const isCloth = + opts.forceCloth || + m.side === DoubleSide || + m.transparent || + m.alphaMode === 'BLEND' || + /sail|cloth|canvas|fabric|para|coat|wing|flag/i.test(name); + + // Texture color-space hygiene (glTF embeds) + if (m.map) { + m.map.colorSpace = SRGBColorSpace; + m.map.flipY = false; + m.map.needsUpdate = true; + } + if (m.metalnessMap) { + m.metalnessMap.colorSpace = undefined; // linear + m.metalnessMap.flipY = false; + } + if (m.roughnessMap) { + m.roughnessMap.colorSpace = undefined; + m.roughnessMap.flipY = false; + } + if (m.normalMap) { + m.normalMap.colorSpace = undefined; + m.normalMap.flipY = false; + } + if (m.aoMap) { + m.aoMap.colorSpace = undefined; + m.aoMap.flipY = false; + } + + if (isCloth) { + // Cloth PBR: not metal, rough fabric, double-sided for thin sail + m.metalness = Math.min(m.metalness ?? 0, 0.08); + m.roughness = Math.max(m.roughness ?? 0.75, 0.72); + m.side = DoubleSide; + if (m.transparent || m.opacity < 0.99) { + m.transparent = true; + m.depthWrite = false; + } + if (opts.clothTint) m.color = new Color(opts.clothTint); + m.userData.sailCloth = true; + // Install wind-wave shader patch once + patchMaterialWithClothWind(m); + n++; + } else { + // Board / hard parts: keep embeds, clamp extreme metal + if (typeof m.metalness === 'number' && m.metalness > 0.85) m.metalness = 0.45; + m.userData.sailCloth = false; + } + m.needsUpdate = true; + } + }); + return n; +} + +/** + * Patch MeshStandardMaterial with a subtle vertex wind (no second cloth engine). + * @param {import('three').Material} material + */ +export function patchMaterialWithClothWind(material) { + if (!material || material.userData._clothWindPatched) return material; + material.userData._clothWindPatched = true; + material.userData.clothWind = { + time: 0, + /** metres peak displace — SI, small for back stow */ + amp: 0.028, + /** waves along sail height */ + freq: 3.2, + /** phase speed */ + speed: 2.4, + /** secondary ripple */ + ripAmp: 0.012, + ripFreq: 7.5 + }; + + const prev = material.onBeforeCompile; + material.onBeforeCompile = (shader, renderer) => { + if (typeof prev === 'function') prev(shader, renderer); + shader.uniforms.uClothTime = { value: 0 }; + shader.uniforms.uClothAmp = { value: material.userData.clothWind.amp }; + shader.uniforms.uClothFreq = { value: material.userData.clothWind.freq }; + shader.uniforms.uClothSpeed = { value: material.userData.clothWind.speed }; + shader.uniforms.uClothRipAmp = { value: material.userData.clothWind.ripAmp }; + shader.uniforms.uClothRipFreq = { value: material.userData.clothWind.ripFreq }; + material.userData.clothShader = shader; + + shader.vertexShader = shader.vertexShader + .replace( + '#include ', + `#include +uniform float uClothTime; +uniform float uClothAmp; +uniform float uClothFreq; +uniform float uClothSpeed; +uniform float uClothRipAmp; +uniform float uClothRipFreq; +` + ) + .replace( + '#include ', + `#include +// Sail cloth wind — displace along normal, stronger toward top of UV.y +{ + float h = uv.y; + float flap = sin(h * uClothFreq + uClothTime * uClothSpeed) + + 0.45 * sin(uv.x * uClothRipFreq + uClothTime * (uClothSpeed * 1.35)); + float rip = sin((uv.x + uv.y) * uClothRipFreq * 1.7 + uClothTime * 3.1); + float w = uClothAmp * (0.25 + 0.75 * h) * flap + + uClothRipAmp * h * rip; + transformed += normalize(objectNormal) * w; +} +` + ); + }; + material.customProgramCacheKey = () => 'sail_cloth_wind_v1'; + return material; +} + +/** + * Drive cloth wind uniforms each frame (call from character / ride update). + * @param {import('three').Object3D} root + * @param {number} dt + * @param {{ wind?: number, speed?: number }} [opts] wind 0..2 intensity + */ +export function updateSailCloth(root, dt, opts = {}) { + if (!root || !(dt > 0)) return; + const wind = Number.isFinite(opts.wind) ? opts.wind : 1; + const speedMul = Number.isFinite(opts.speed) ? opts.speed : 1; + root.traverse((o) => { + if (!o.isMesh || !o.material) return; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (const m of mats) { + if (!m?.userData?.sailCloth || !m.userData.clothWind) continue; + const cw = m.userData.clothWind; + cw.time += dt * speedMul * (0.85 + 0.4 * wind); + const sh = m.userData.clothShader; + if (sh?.uniforms) { + sh.uniforms.uClothTime.value = cw.time; + sh.uniforms.uClothAmp.value = cw.amp * wind; + sh.uniforms.uClothSpeed.value = cw.speed * (0.9 + 0.3 * wind); + sh.uniforms.uClothRipAmp.value = cw.ripAmp * wind; + } + } + }); +} + +/** + * Tune cloth amp for stow (smaller) vs full vehicle sail (larger SI). + * @param {import('three').Object3D} root + * @param {'stow'|'ride'} mode + */ +export function setSailClothMode(root, mode) { + if (!root) return; + const stow = mode === 'stow'; + root.traverse((o) => { + if (!o.isMesh || !o.material) return; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (const m of mats) { + if (!m?.userData?.clothWind) continue; + m.userData.clothWind.amp = stow ? 0.018 : 0.06; + m.userData.clothWind.ripAmp = stow ? 0.008 : 0.022; + m.userData.clothWind.freq = stow ? 4.0 : 2.6; + m.userData.clothWind.speed = stow ? 2.8 : 2.1; + } + }); +} diff --git a/src/physics/PhysicsWorld.js b/src/physics/PhysicsWorld.js new file mode 100644 index 0000000..4fd2711 --- /dev/null +++ b/src/physics/PhysicsWorld.js @@ -0,0 +1,393 @@ +import RAPIER from '@dimforge/rapier3d-compat'; +import { PLAYER_CAPSULE, WORLD } from '../config/worldScale.js'; + +/** + * Fleet-style Rapier world for Casting Abilities. + * SI meters, fixed 1/60, ground plane + human CCT capsule. + * @see grudge-rapier · https://rapier.rs/docs/api/javascript/JavaScript3D + */ + +export const FIXED_DT = 1 / 60; +/** Human capsule ~1.8–2.0 m hero (fleet SI) */ +export const HUMAN_CAPSULE = { radius: PLAYER_CAPSULE.radius, halfHeight: PLAYER_CAPSULE.halfHeight }; + +let _initPromise = null; + +export async function initRapier() { + if (!_initPromise) { + // rapier3d-compat 0.19 embeds base64 wasm and calls init(bytes) internally. + // Vite plugin `fix-rapier-init-deprecation` normalizes that to + // `{ module_or_path: bytes }` so the wasm-bindgen warn is gone. + _initPromise = RAPIER.init().then(() => RAPIER); + } + return _initPromise; +} + +export class PhysicsWorld { + constructor() { + /** @type {import('@dimforge/rapier3d-compat').World|null} */ + this.world = null; + this.accumulator = 0; + /** @type {Map} */ + this.bodies = new Map(); + this.characterController = null; + this.playerCollider = null; + this.playerBody = null; + this.ready = false; + this.debug = typeof location !== 'undefined' && /[?&]physicsDebug=1/.test(location.search); + /** Vertical velocity for jumps (m/s). Gravity integrates each step. */ + this.vy = 0; + this.grounded = true; + this.gravity = -9.81; + /** Multiplier on gravity (backflip hang = ~0.32 for air-aim window) */ + this.gravityScale = 1; + } + + async init(opts = {}) { + await initRapier(); + const g = opts.gravityY ?? -9.81; + this.world = new RAPIER.World({ x: 0, y: g, z: 0 }); + this.world.timestep = FIXED_DT; + + // Default flat ground — replaced by addHeightfield when island terrain loads + const groundBody = this.world.createRigidBody(RAPIER.RigidBodyDesc.fixed().setTranslation(0, -0.05, 0)); + const half = WORLD.physicsGroundHalf; + const groundCol = this.world.createCollider( + RAPIER.ColliderDesc.cuboid(half, 0.05, half).setFriction(0.9).setRestitution(0.05), + groundBody + ); + this.bodies.set('ground', { body: groundBody, collider: groundCol, kind: 'ground' }); + /** Optional water surface Y sampler (ocean layer) */ + this.waterHeightAt = null; + /** Optional land height sampler (matches heightfield) */ + this.landHeightAt = null; + + // Player kinematic capsule (CCT) + const r = opts.radius ?? HUMAN_CAPSULE.radius; + const hh = opts.halfHeight ?? HUMAN_CAPSULE.halfHeight; + const feetY = opts.feetY ?? 0; + const centerY = feetY + r + hh; + const bodyDesc = RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(0, centerY, 0); + this.playerBody = this.world.createRigidBody(bodyDesc); + this.playerCollider = this.world.createCollider( + RAPIER.ColliderDesc.capsule(hh, r).setFriction(0.7).setDensity(0), + this.playerBody + ); + this.characterController = this.world.createCharacterController(0.01); + this.characterController.setApplyImpulsesToDynamicBodies(true); + // API is positional (maxHeight, minWidth, includeDynamicBodies) — not an object. + this.characterController.enableAutostep(0.35, 0.2, true); + this.characterController.enableSnapToGround(0.3); + this.bodies.set('player', { + body: this.playerBody, + collider: this.playerCollider, + kind: 'player', + radius: r, + halfHeight: hh + }); + + this.ready = true; + return this; + } + + /** + * Apply jump impulse (m/s). Call once per jump press. + * @param {number} impulseVy + */ + jump(impulseVy) { + if (!this.ready) return; + this.vy = Math.max(this.vy, impulseVy); + this.grounded = false; + } + + /** + * Scale world gravity for hang time (1 = normal). Clamped ≥ 0.05. + * @param {number} scale + */ + setGravityScale(scale = 1) { + this.gravityScale = Math.max(0.05, Number(scale) || 1); + } + + /** Kill vertical velocity (hard stop / float entry). */ + zeroVerticalVelocity() { + this.vy = 0; + } + + /** + * Move player CCT with desired XZ velocity + integrated vertical jump. + * @param {number} vx + * @param {number} vz + * @param {number} dt + * @returns {{ x: number, y: number, z: number, grounded: boolean, vy: number }} + */ + movePlayer(vx, vz, dt) { + if (!this.ready || !this.playerBody || !this.characterController) { + return { x: 0, y: 0, z: 0, grounded: true, vy: 0 }; + } + const entry = this.bodies.get('player'); + const r = entry.radius; + const hh = entry.halfHeight; + const g = this.gravity * (this.gravityScale ?? 1); + + this.accumulator += Math.min(dt, 0.05); + let grounded = this.grounded; + while (this.accumulator >= FIXED_DT) { + // Integrate vertical velocity (SI: m/s²) + this.vy += g * FIXED_DT; + // Slight ground snap when nearly landed and falling + let dy = this.vy * FIXED_DT; + if (grounded && this.vy <= 0) { + this.vy = 0; + dy = -0.35 * FIXED_DT; // keep CCT grounded settle + } + + const desired = { + x: vx * FIXED_DT, + y: dy, + z: vz * FIXED_DT + }; + this.characterController.computeColliderMovement(this.playerCollider, desired); + const mv = this.characterController.computedMovement(); + grounded = this.characterController.computedGrounded(); + // If we hit ceiling, kill upward velocity + if (this.vy > 0 && mv.y < desired.y * 0.5) this.vy = 0; + // Landed + if (grounded && this.vy <= 0) this.vy = 0; + + const t = this.playerBody.translation(); + this.playerBody.setNextKinematicTranslation({ + x: t.x + mv.x, + y: t.y + mv.y, + z: t.z + mv.z + }); + this.world.step(); + this.accumulator -= FIXED_DT; + } + + this.grounded = grounded; + const t = this.playerBody.translation(); + let feetY = t.y - hh - r; + // Snap feet to land sampler if CCT slightly floats (heightfield edge cases) + if (grounded && typeof this.landHeightAt === 'function') { + const landY = this.landHeightAt(t.x, t.z); + if (Number.isFinite(landY) && Math.abs(feetY - landY) < 0.45) { + feetY = landY; + } + } + return { + x: t.x, + y: feetY, + z: t.z, + grounded, + vy: this.vy + }; + } + + /** + * Teleport kinematic player (spawn / reset / ride mount glue). + * Does not apply CCT gravity — keeps capsule under the board while mounted. + */ + setPlayerFeet(x, y, z) { + if (!this.playerBody) return; + const entry = this.bodies.get('player'); + const centerY = y + entry.radius + entry.halfHeight; + // Instant kinematic snap (no step) so ride mount is not knocked by gravity + if (typeof this.playerBody.setTranslation === 'function') { + this.playerBody.setTranslation({ x, y: centerY, z }, true); + } else { + this.playerBody.setNextKinematicTranslation({ x, y: centerY, z }); + this.world.step(); + } + } + + getPlayerFeet() { + if (!this.playerBody) return { x: 0, y: 0, z: 0 }; + const entry = this.bodies.get('player'); + const t = this.playerBody.translation(); + return { x: t.x, y: t.y - entry.halfHeight - entry.radius, z: t.z }; + } + + /** + * Replace flat ground with Rapier heightfield (island terrain). + * Official: https://rapier.rs/docs/user_guides/javascript/colliders#heightfield + * three.js example: physics_rapier_terrain + * + * @param {{ + * nrows: number, + * ncols: number, + * heights: Float32Array, + * scale: { x: number, y: number, z: number } + * }} desc + * @param {{ landHeightAt?: (x:number,z:number)=>number, waterHeightAt?: (x:number,z:number,t?:number)=>number }} [samplers] + */ + addHeightfield(desc, samplers = {}) { + if (!this.ready || !this.world || !desc?.heights) return false; + // Remove flat ground collider + const old = this.bodies.get('ground'); + if (old) { + try { + if (old.collider) this.world.removeCollider(old.collider, true); + if (old.body) this.world.removeRigidBody(old.body); + } catch { + /* ok */ + } + this.bodies.delete('ground'); + } + + // Rapier heightfield: heights length (nrows+1)*(ncols+1) + const nrows = desc.nrows | 0; + const ncols = desc.ncols | 0; + const scale = desc.scale || { x: 1, y: 1, z: 1 }; + const body = this.world.createRigidBody(RAPIER.RigidBodyDesc.fixed().setTranslation(0, 0, 0)); + let colliderDesc; + try { + colliderDesc = RAPIER.ColliderDesc.heightfield(nrows, ncols, desc.heights, scale) + .setFriction(0.95) + .setRestitution(0.02); + } catch (err) { + console.warn('[PhysicsWorld] heightfield create failed — keep flat ground', err); + // Restore flat + const half = WORLD.physicsGroundHalf; + const gb = this.world.createRigidBody(RAPIER.RigidBodyDesc.fixed().setTranslation(0, -0.05, 0)); + const gc = this.world.createCollider( + RAPIER.ColliderDesc.cuboid(half, 0.05, half).setFriction(0.9), + gb + ); + this.bodies.set('ground', { body: gb, collider: gc, kind: 'ground' }); + return false; + } + const col = this.world.createCollider(colliderDesc, body); + this.bodies.set('ground', { body, collider: col, kind: 'heightfield' }); + this.landHeightAt = samplers.landHeightAt || null; + this.waterHeightAt = samplers.waterHeightAt || null; + console.info( + `[PhysicsWorld] heightfield ${nrows}×${ncols} scale=(${scale.x.toFixed(1)},${scale.y},${scale.z.toFixed(1)})` + ); + return true; + } + + /** + * Water terrain layer — surface sensor + depth volume for buoyancy queries. + * Solid walk is the heightfield (land + shore bathymetry under water). + * Water "hits" terrain where landHeightAt < waterY (shore / shelf). + * @param {{ waterY?: number, halfXZ?: number, deepY?: number }} [opts] + */ + addWaterLayer(opts = {}) { + if (!this.ready || !this.world) return; + const waterY = opts.waterY ?? WORLD.waterY ?? 0; + const deepY = opts.deepY ?? WORLD.oceanFloorY ?? -50; + const half = opts.halfXZ ?? WORLD.physicsGroundHalf * 1.8; + if (this.bodies.has('water')) return; + + // Thin sensor at surface (freeride / splash queries) + const body = this.world.createRigidBody( + RAPIER.RigidBodyDesc.fixed().setTranslation(0, waterY - 0.15, 0) + ); + const col = this.world.createCollider( + RAPIER.ColliderDesc.cuboid(half, 0.2, half).setSensor(true).setFriction(0), + body + ); + this.bodies.set('water', { body, collider: col, kind: 'water_sensor', waterY }); + + // Deep water volume sensor (surface → deep floor) for submersion tests + const depth = Math.max(2, waterY - deepY); + const midY = (waterY + deepY) * 0.5; + const volBody = this.world.createRigidBody( + RAPIER.RigidBodyDesc.fixed().setTranslation(0, midY, 0) + ); + const volCol = this.world.createCollider( + RAPIER.ColliderDesc.cuboid(half * 1.05, depth * 0.5, half * 1.05) + .setSensor(true) + .setFriction(0), + volBody + ); + this.bodies.set('water_volume', { + body: volBody, + collider: volCol, + kind: 'water_volume', + waterY, + deepY + }); + console.info( + `[PhysicsWorld] water surface y=${waterY} volume deepY=${deepY} (heightfield owns solid shore)` + ); + } + + /** + * True when land/seafloor height is under water surface (wet / swimming zone). + * @param {number} x + * @param {number} z + * @param {number} [margin] + */ + isSubmergedAt(x, z, margin = 0.05) { + const land = this.sampleLandY(x, z); + const water = this.sampleWaterY(x, z); + return land < water - margin; + } + + /** + * Water column depth above terrain (0 on dry land). + * @param {number} x + * @param {number} z + */ + waterDepthAt(x, z) { + const land = this.sampleLandY(x, z); + const water = this.sampleWaterY(x, z); + return Math.max(0, water - land); + } + + /** + * Sample land height if sampler set (post-heightfield). + * @param {number} x + * @param {number} z + */ + sampleLandY(x, z) { + if (typeof this.landHeightAt === 'function') return this.landHeightAt(x, z); + return 0; + } + + /** + * Sample water surface (waves) if set. + * @param {number} x + * @param {number} z + * @param {number} [t] + */ + sampleWaterY(x, z, t = 0) { + if (typeof this.waterHeightAt === 'function') return this.waterHeightAt(x, z, t); + return WORLD.waterY ?? -0.04; + } + + /** + * Dynamic sphere for skill projectile hit proxy (sensor). + * @returns {string} id + */ + spawnProjectileSensor(x, y, z, radius = 0.25) { + if (!this.ready) return null; + const id = `proj_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`; + const body = this.world.createRigidBody( + RAPIER.RigidBodyDesc.dynamic().setTranslation(x, y, z).setLinvel(0, 0, 0) + ); + const col = this.world.createCollider( + RAPIER.ColliderDesc.ball(radius).setSensor(true).setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS), + body + ); + this.bodies.set(id, { body, collider: col, kind: 'projectile' }); + return id; + } + + removeBody(id) { + const e = this.bodies.get(id); + if (!e || !this.world) return; + this.world.removeCollider(e.collider, true); + this.world.removeRigidBody(e.body); + this.bodies.delete(id); + } + + dispose() { + if (this.world) { + this.world.free(); + this.world = null; + } + this.ready = false; + } +} diff --git a/src/player/playerIdentity.js b/src/player/playerIdentity.js new file mode 100644 index 0000000..62070bd --- /dev/null +++ b/src/player/playerIdentity.js @@ -0,0 +1,162 @@ +/** + * Honest player identity for casting host (player frontend path). + * + * Priority: + * 1. URL ?name= & ?race= & ?characterId= + * 2. localStorage handoff (Foundry / heroes / Open) + * 3. Race kit label from grudge6 SSOT — never invent "Hero" / "mage" as product name + */ +import { DEFAULT_RACE, raceDef, RACES } from '../config/grudge6SSOT.js'; + +const ROLE_LABEL = { + mage: 'Mage', + warrior: 'Warrior', + ranger: 'Ranger', + priest: 'Priest', + rogue: 'Rogue', + default: 'Warlord' +}; + +/** + * @returns {{ + * characterId: string|null, + * raceId: string, + * roleId: string, + * displayName: string, + * raceLabel: string, + * roleLabel: string, + * source: string + * }} + */ +export function resolvePlayerIdentity() { + const q = readQuery(); + const ls = readLocalStorageHandoff(); + + const raceRaw = q.race || ls.raceId || DEFAULT_RACE; + const raceId = normalizeRaceId(raceRaw); + const def = raceDef(raceId); + const roleId = String(q.role || q.class || ls.classId || ls.roleId || 'mage').toLowerCase(); + const roleLabel = ROLE_LABEL[roleId] || ROLE_LABEL.default; + + let displayName = + (q.name && String(q.name).trim()) || + (ls.name && String(ls.name).trim()) || + ''; + + const characterId = q.characterId || ls.characterId || null; + let source = 'kit'; + + if (q.name || q.race || q.characterId) source = 'url'; + else if (ls.name || ls.characterId || ls.raceId) source = 'handoff'; + + if (!displayName) { + // Honest default: race + role — not fake "Hero" or internal preset id alone + displayName = `${def.short ? capitalize(def.short) : def.label} ${roleLabel}`; + source = source === 'kit' ? 'kit-default' : source; + } + + return { + characterId, + raceId, + roleId, + displayName, + raceLabel: def.label, + roleLabel, + source + }; +} + +/** + * Frame-friendly portrait initials from race id. + * @param {string} raceId + */ +export function racePortraitGlyph(raceId) { + const id = normalizeRaceId(raceId); + return String(id || 'WK').slice(0, 3).toUpperCase(); +} + +/** + * @param {string} raceId + * @param {string} [roleId] + */ +export function displayNameForKit(raceId, roleId = 'mage') { + const def = raceDef(normalizeRaceId(raceId)); + const roleLabel = ROLE_LABEL[String(roleId).toLowerCase()] || ROLE_LABEL.default; + return `${capitalize(def.short || def.label)} ${roleLabel}`; +} + +function normalizeRaceId(raw) { + const s = String(raw || DEFAULT_RACE).trim(); + const up = s.toUpperCase(); + if (RACES[up]) return up; + // allow short names: human, elf, orc… + const byShort = Object.values(RACES).find( + (r) => r.short === s.toLowerCase() || r.label.toLowerCase() === s.toLowerCase() + ); + return byShort?.id || DEFAULT_RACE; +} + +function capitalize(s) { + const t = String(s || ''); + return t ? t.charAt(0).toUpperCase() + t.slice(1) : t; +} + +function readQuery() { + if (typeof window === 'undefined') return {}; + try { + const p = new URLSearchParams(window.location.search); + return { + name: p.get('name'), + race: p.get('race') || p.get('raceId'), + role: p.get('role') || p.get('class') || p.get('classId'), + characterId: p.get('characterId') || p.get('char') || p.get('id') + }; + } catch { + return {}; + } +} + +function readLocalStorageHandoff() { + if (typeof localStorage === 'undefined') return {}; + try { + const characterId = + localStorage.getItem('grudge_active_character') || + localStorage.getItem('gruda_active_character') || + localStorage.getItem('grudge.open.selectedCharacterId') || + null; + + let name = null; + let raceId = null; + let classId = null; + + // Foundry / heroes often stash last payload + const raw = + localStorage.getItem('grudge_character_handoff') || + localStorage.getItem('grudge_active_character_json') || + localStorage.getItem('grudge.open.selectedCharacter'); + if (raw) { + try { + const o = JSON.parse(raw); + name = o.name || o.displayName || null; + raceId = o.raceId || o.race || null; + classId = o.classId || o.class || o.role || null; + } catch { + /* ignore */ + } + } + + // era map may only have id + try { + const byEra = JSON.parse(localStorage.getItem('grudge.selectedCharacterByEra') || '{}'); + if (!characterId && byEra.warlords) { + /* id only */ + } + } catch { + /* ignore */ + } + + return { characterId, name, raceId, classId }; + } catch { + return {}; + } +} diff --git a/src/postprocessing/PostProcessing.js b/src/postprocessing/PostProcessing.js index bc8a080..6d15206 100644 --- a/src/postprocessing/PostProcessing.js +++ b/src/postprocessing/PostProcessing.js @@ -78,15 +78,16 @@ export class PostProcessing { ); this.composer.addPass(this.bloomPass); - // Tone mapping + sRGB conversion happen here; everything before is linear HDR. - this.outputPass = new OutputPass(); - this.composer.addPass(this.outputPass); - + // Grade in HDR-ish intermediate space *before* OutputPass. + // OutputPass MUST be last (tone map + sRGB → screen). Putting Grade after + // OutputPass left the canvas pure black on some WebGL2 paths (live 2026-08). this.gradePass = new ShaderPass(GradeShader); this.gradePass.uniforms.uFlashColor.value = new Color(1, 1, 1); - this.gradePass.renderToScreen = true; this.composer.addPass(this.gradePass); + this.outputPass = new OutputPass(); + this.composer.addPass(this.outputPass); + this._clearColor = new Color(); } @@ -169,11 +170,28 @@ export class PostProcessing { } render() { - this._renderDepth(); - this._renderDistortion(); - // Tone mapping is applied by OutputPass: three automatically disables the - // in-material tone mapping while rendering into the composer's targets. - this.composer.render(); + const postOn = settings.post?.enabled !== false; + if (!postOn) { + // Lab / emergency: direct forward render (no composer) + this.gl.setRenderTarget(null); + this.gl.setClearColor(this.scene.background || 0x1a222c, 1); + this.gl.clear(); + this.gl.render(this.scene, this.camera); + return; + } + + try { + this._renderDepth(); + this._renderDistortion(); + // Tone mapping + display encoding: OutputPass is last in the chain. + this.composer.render(); + } catch (err) { + console.warn('[PostProcessing] composer failed — direct render', err); + this.gl.setRenderTarget(null); + this.gl.setClearColor(this.scene.background || 0x1a222c, 1); + this.gl.clear(); + this.gl.render(this.scene, this.camera); + } this.gl.setRenderTarget(null); } diff --git a/src/sdk/castingLabSdk.js b/src/sdk/castingLabSdk.js new file mode 100644 index 0000000..9a42599 --- /dev/null +++ b/src/sdk/castingLabSdk.js @@ -0,0 +1,261 @@ +/** + * Casting Lab public SDK surface — import/export contract for fleet games. + * + * Use this module (or `package.json` export `./sdk`) when Open / Warlords / + * satellites need lab systems without deep-importing App internals. + * + * Does **not** invent a parallel combat engine. Re-exports existing SSOT. + * + * @example + * import { + * sharedGltfLoader, + * bindKtx2, + * compileProductionWeaponSkill, + * mountTerrainLayers, + * CASTING_LAB_CONTRACT + * } from './sdk/castingLabSdk.js'; + * + * @see docs/CASTING_SDK_EXPORT_SSOT.md + * @see public/api/v1/casting-lab-contract.json + */ + +// ── Loaders ────────────────────────────────────────────────────────── +export { + DRACO_DECODER_PATH, + KTX2_TRANSCODER_PATH, + bindKtx2, + isKtx2Bound, + makeGltfLoader, + sharedGltfLoader, + getSharedDracoLoader, + gltfPipelineStatus, + disposeGltfPipeline +} from '../loaders/gltfPipeline.js'; + +export { AssetLoader } from '../loaders/AssetLoader.js'; + +// ── Weapon skills · production ─────────────────────────────────────── +export { + compileProductionWeaponSkill, + compileProductionWeaponSkillAsync, + productionToDrcSkill, + assessProductionReadiness, + compileWeaponSkillBar, + warmProductionOverrides, + loadProductionOverride, + getCachedProductionOverride, + defaultPhysicsForStyle, + PRODUCTION_SKILL_OVERRIDE_TEMPLATE +} from '../combat/weaponSkillProduction.js'; + +export { + parseCatalogEffects, + SkillStatusSystem +} from '../combat/skillStatusSystem.js'; + +export { + planElementalLinearCast, + fireLinearFromPlan, + ELEMENTAL_LINEAR_LEARNED, + PRODUCT_TO_LINEAR +} from '../combat/elementalLinearCast.js'; + +export { + enrichSkillDelivery, + inferDeliveryPattern, + resolveDeliveryPose, + resolveSkillProjectileMesh, + STAFF_ORB_MESH_BY_ELEMENT, + STAFF_CHARGE_MESH, + SUMMON_MESH_BY_ELEMENT, + DELIVERY_META +} from '../combat/skillDelivery.js'; + +export { + bindFromCatalogSkill, + staffBindFor, + enrichStaffSkill +} from '../combat/staffWeaponSkillsBind.js'; + +// Weapon skill + ability SFX (public/audio/sfx) +export { + SkillSfx, + playSkillSfx, + playForWeaponSkillCast, + playForElementCast, + playForImpact, + playMagicImpactSfx, + playParrySfx, + playHealSfx, + playBurnSfx, + setPlayerBurningSfx, + unlockSkillSfx, + installSkillSfxGestureUnlock, + SKILL_SFX_URLS +} from '../audio/skillSfx.js'; + +export { + CASTING_ELEMENT_PHASE_VFX, + normalizeElement, + staffWeaponIdForElement +} from '../combat/elementWeaponSkills.js'; + +// ── VFX ────────────────────────────────────────────────────────────── +export { + STAFF_ORB_BY_ELEMENT, + STAFF_NORMAL_ATTACK, + STAFF_CHARGE, + staffOrbForElement, + staffProjectileMeshUrl, + applyElementalOrbMaterials, + isStaffNormalAttack, + staffOrbWarmUrls +} from '../vfx/staffOrbVfx.js'; + +export { + EARTH_ROCK_MESHES, + ARROW_SYSTEMS, + FREEZE_NOVA, + WATER_BUBBLE, + pickEarthRocks, + inferElementAttackKind, + createWaterBubbleMaterial +} from '../vfx/elementAttackVfx.js'; + +export { + PISTOL_BULLET, + FLINTLOCK_WEAPON, + isLivingTarget, + isPistolBulletSkill, + pistolBulletCount +} from '../vfx/pistolBulletVfx.js'; + +export { + FLINTLOCK_FIRE, + FLINTLOCK_RELOAD, + PISTOL_SOFT_LOCK, + OPEN_DANGER_PISTOL_SLOTS, + pistolTimeScale, + pistolHitFrameSec +} from '../config/pistolAnimSsot.js'; + +export { + PistolReloadPose, + findWeaponAttach, + getMuzzleWorld, + getBarrelForward +} from '../animation/pistolReloadPose.js'; + +export { + createFlintlockChamber, + skillNeedsLoad, + isFlintlockContext, + makeReloadSkillDef, + OPEN_PISTOL_SKILL_SLOTS, + FLINTLOCK_ANIM_REVIEW +} from '../combat/flintlockChamber.js'; + +export { + WeaponChargeSession, + isChargeableWeaponSkill, + chargeBarState, + chargeDamageMul, + weaponChargeConfig, + WEAPON_CHARGE_DEFAULTS, + CHARGE_LEVELS +} from '../combat/weaponChargeSystem.js'; + +export { + FALL_ROLES, + FALL_FBX_URLS, + FALL_THRESHOLDS, + FALL_AUTHOR_MAP, + pickLandRole, + shouldEnterFall +} from '../config/fallAnimSsot.js'; + +export { + BACK_MOBILITY_CATALOG, + WINDSURF_PARTS, + FLIGHT_DEFAULTS, + WING_ASSET_DROP, + getBackMobility, + getBackWaterBuffs, + canDeployBackItem, + reduceTravelerFlight +} from '../config/backSlotMobilitySsot.js'; + +// ── Terrain ────────────────────────────────────────────────────────── +export { + TERRAIN_LAYER, + TERRAIN_LEARNED, + mountTerrainLayers +} from '../world/terrainLayers.js'; + +export { + IslandHeightfield, + heightAt, + terrainOpts, + isDryLand, + bakeHeightGrid +} from '../world/IslandHeightfield.js'; + +export { projectToTerrain, surfaceY, terrainHandle } from '../world/terrainGround.js'; +export { StylizedGrassLayer } from '../world/StylizedGrassLayer.js'; +export { GrowingForest } from '../world/GrowingForest.js'; + +// ── World scale ────────────────────────────────────────────────────── +export { WORLD } from '../config/worldScale.js'; + +// ── Fleet contract stamp ───────────────────────────────────────────── +/** Machine-readable contract version (bump when export surface breaks). */ +export const CASTING_LAB_SDK_VERSION = '1.1.0'; + +/** + * Static contract for ObjectStore / Open / agents (mirror public/api/v1 JSON). + */ +export const CASTING_LAB_CONTRACT = Object.freeze({ + id: 'casting-lab', + version: CASTING_LAB_SDK_VERSION, + productHost: 'casting.grudge.studio', + vercelHost: 'casting-abilities-threejs.vercel.app', + three: '^0.185', + rapier: '@dimforge/rapier3d-compat', + loaders: { + draco: 'https://www.gstatic.com/draco/versioned/decoders/1.5.7/', + ktx2: 'https://cdn.jsdelivr.net/npm/three@0.185.1/examples/jsm/libs/basis/', + meshopt: true, + entry: 'src/loaders/gltfPipeline.js' + }, + skills: { + catalog: 'master-weaponSkills + t0-weapons', + production: 'src/combat/weaponSkillProduction.js', + statuses: 'src/combat/skillStatusSystem.js', + linear: 'src/combat/elementalLinearCast.js', + overrides: 'public/skills/production/.json' + }, + terrain: { + layers: ['L0_height', 'L1_surface', 'L2_vegetation', 'L3_detail', 'water'], + sample: 'IslandHeightfield.sample', + doc: 'docs/THREE_LAYER_TERRAIN_SSOT.md' + }, + character: { + playKit: 'toonKitPlay / loadRaceKit', + mixer: 1, + siHumanM: 1.8 + }, + export: { + module: 'src/sdk/castingLabSdk.js', + packageExport: './sdk', + contractJson: '/api/v1/casting-lab-contract.json' + }, + docs: [ + 'docs/CASTING_LAB_SSOT.md', + 'docs/CASTING_SDK_EXPORT_SSOT.md', + 'docs/WEAPON_SKILL_PRODUCTION_SSOT.md', + 'docs/LOADER_DRACO_KTX2_AUDIT.md', + 'docs/SYSTEMS_HEALTH_AUDIT.md', + 'docs/THREE_LAYER_TERRAIN_SSOT.md', + 'docs/SSOT_INDEX.md' + ] +}); diff --git a/src/skillshot/AimController.js b/src/skillshot/AimController.js new file mode 100644 index 0000000..337ae06 --- /dev/null +++ b/src/skillshot/AimController.js @@ -0,0 +1,222 @@ +import { Group, Raycaster, Plane, Vector2, Vector3, MathUtils } from 'three'; +import { settings, ELEMENTS, CastShape, castShapeOf } from './linearSettings.js'; +import { EventEmitter } from '../utils/EventEmitter.js'; +import { AimIndicator } from './effects/AimIndicator.js'; +import { ZoneIndicator } from './effects/ZoneIndicator.js'; + +const GROUND_PLANE = new Plane(new Vector3(0, 1, 0), 0); + +/** + * Targeting, in the two shapes players already know from MOBAs. + * + * A **line cast** arms an arrow that swings about the caster and fires along + * its length; a **far cast** arms a circle that follows the cursor and drops + * where it is clicked. Which one an ability uses is declared in + * `ELEMENT_META[...].cast`, and the controller swaps indicators on selection — + * everything else about arming, clamping, validating and firing is shared, + * because from the targeting side the only difference is what gets drawn. + * + * The controller owns the aim state and both indicators; it decides nothing + * about what the cast does. It emits one event, `cast`, with an origin, a unit + * direction and a distance — which is exactly the signature the ability's + * `spawn` takes. A far cast reads its target point off the far end of that + * line, so the ability contract never had to change. + * + * The pointer is re-projected every frame rather than only on move, so orbiting + * the camera with the cast armed swings the indicator under a stationary + * cursor. + * + * Emits: `cast` (origin, direction, distance), `arm`, `cancel`, `reject`. + */ +export class AimController extends EventEmitter { + constructor(camera) { + super(); + this.camera = camera; + this.raycaster = new Raycaster(); + this.raycaster.far = 500; + + this.indicator = new AimIndicator(); + this.zone = new ZoneIndicator(); + + this.group = new Group(); + this.group.name = 'AimIndicators'; + this.group.add(this.indicator.object3D, this.zone.object3D); + + /** + * Which ability the arrow is measuring for. `range` and `minRange` are + * per-element, so the reach of the arrow changes with the slot the player + * has selected. + */ + this.element = ELEMENTS[0]; + + this.armed = false; + /** 0..1 sweep-out of the indicator. Driven by real time, never scaled. */ + this.reveal = 0; + + /** Where the cast comes from — the caster's feet. */ + this.origin = new Vector3(); + /** Unit vector on the ground plane. */ + this.direction = new Vector3(0, 0, 1); + this.distance = 0; + this.yaw = 0; + /** False while the pointer is nearer than the ability's `minRange`. */ + this.valid = true; + + this._pointer = new Vector2(); + this._hasPointer = false; + this._hit = new Vector3(); + this._flat = new Vector3(); + } + + get object3D() { + return this.group; + } + + /** Live settings block of the ability being aimed. */ + get config() { + return settings[this.element] ?? settings[ELEMENTS[0]]; + } + + /** Whether the ability in the slot is aimed with the arrow or the circle. */ + get shape() { + return castShapeOf(this.element); + } + + /** Footprint of a far cast, metres. Zero for a line cast. */ + get zoneRadius() { + return Math.max(0.05, this.config.zoneRadius ?? 1); + } + + /** Point the indicator at a different ability's reach. */ + setElement(element) { + if (!settings[element]) return; + this.element = element; + } + + get isArmed() { + return this.armed; + } + + /** Heading the caster should face, radians about +Y. */ + get facing() { + return this.yaw; + } + + /* ------------------------------------------------------------------ */ + + /** Where the arrow starts. Called every frame with the character's position. */ + setOrigin(position) { + this.origin.set(position.x, 0, position.z); + } + + arm() { + if (this.armed) return; + this.armed = true; + this.emit('arm'); + } + + cancel() { + if (!this.armed) return; + this.armed = false; + this.emit('cancel'); + } + + toggle() { + if (this.armed) this.cancel(); + else this.arm(); + } + + /** Latest pointer position in NDC. Kept even while disarmed. */ + point(pointer) { + this._pointer.copy(pointer); + this._hasPointer = true; + } + + /** + * Fire, if the aim is legal. + * @returns {boolean} whether a cast was emitted + */ + confirm() { + if (!this.armed) return false; + if (!this.valid) { + this.emit('reject'); + return false; + } + this.armed = false; + this.emit('cast', this.origin, this.direction, this.distance); + return true; + } + + /* ------------------------------------------------------------------ */ + + /** Project the pointer onto the ground and resolve the aim from it. */ + _resolve() { + const c = this.config; + + if (this._hasPointer) { + this.raycaster.setFromCamera(this._pointer, this.camera); + if (this.raycaster.ray.intersectPlane(GROUND_PLANE, this._hit)) { + this._flat.copy(this._hit).sub(this.origin); + this._flat.y = 0; + // Behind the caster and dead on top of them are the two degenerate + // cases; keep the last good heading rather than snapping to north. + if (this._flat.lengthSq() > 1e-6) { + const raw = this._flat.length(); + this.direction.copy(this._flat).multiplyScalar(1 / raw); + this.yaw = Math.atan2(this.direction.x, this.direction.z); + this.valid = raw >= c.minRange; + this.distance = MathUtils.clamp(raw, Math.max(0.2, c.minRange), Math.max(0.4, c.range)); + return; + } + } + } + + this.valid = false; + this.distance = MathUtils.clamp(this.distance, Math.max(0.2, c.minRange), Math.max(0.4, c.range)); + } + + /** + * @param {number} dt real seconds — deliberately *not* the scaled simulation + * delta, so the indicator keeps animating while the sandbox is paused. + */ + update(dt) { + this._resolve(); + + const zoned = this.shape === CastShape.ZONE; + const revealTime = Math.max(0.01, zoned ? settings.zone.reveal : settings.aim.reveal); + const target = this.armed ? 1 : 0; + const step = dt / revealTime; + this.reveal = MathUtils.clamp( + this.reveal + MathUtils.clamp(target - this.reveal, -step, step), + 0, + 1 + ); + + const visible = this.reveal > 0.001; + // Only ever one of the two is on screen, and swapping the slot mid-reveal + // hides the other outright rather than leaving it fading in place. + this.indicator.setVisible(visible && !zoned); + this.zone.setVisible(visible && zoned); + + if (!visible) return; + if (zoned) { + this.zone.update( + this.origin, + this.yaw, + this.distance, + this.zoneRadius, + this.config.range, + this.reveal, + this.valid + ); + } else { + this.indicator.update(this.origin, this.yaw, this.distance, this.reveal, this.valid); + } + } + + dispose() { + this.indicator.dispose(); + this.zone.dispose(); + this.clear(); + } +} diff --git a/src/skillshot/LinearSkillBridge.js b/src/skillshot/LinearSkillBridge.js new file mode 100644 index 0000000..3fac5f1 --- /dev/null +++ b/src/skillshot/LinearSkillBridge.js @@ -0,0 +1,305 @@ +/** + * Bridge: LinearAbilityCastingThreeJS skillshots → Casting Warlords lab. + * + * Does **not** replace path-cast Fire/Water/Earth/Wind. Adds a second pool: + * ice · thunder · meteor · beam · snare · glacier with MOBA line/zone aim. + * + * Best practices learned from LinearAbilityCasting: + * - settings SSOT sampled every frame (live editor knobs mid-cast) + * - pooled abilities, no alloc on cast + * - line advance at constant m/s + phase machine + * - aim indicator metres (SDF arrow / zone circle) + * - procedural geometry + GLSL materials (no texture sprites for FX) + * + * Hosts: casting.grudge.studio · casting.grudge-studio.com (same deploy) + * + * @see docs/LINEAR_SKILLSHOT_SSOT.md + */ + +import { Vector3 } from 'three'; +import { AbilityManager as LinearAbilityManager } from './abilities/AbilityManager.js'; +import { AimController } from './AimController.js'; +import { + ELEMENTS as LINEAR_ELEMENTS, + ELEMENT_META, + settings as linearSettings, + CastShape, + castShapeOf +} from './linearSettings.js'; +import { FissureSystem } from './effects/GroundFissures.js'; + +const _origin = new Vector3(); +const _dir = new Vector3(); + +/** Product element / hotkey → linear skillshot id */ +export const PRODUCT_TO_LINEAR = Object.freeze({ + ice: 'ice', + storm: 'thunder', + fire: 'meteor', + holy: 'beam', + arcane: 'snare', + nature: 'glacier', + // direct skillshot ids + ice_shot: 'ice', + thunder: 'thunder', + meteor: 'meteor', + beam: 'beam', + snare: 'snare', + glacier: 'glacier' +}); + +/** Sandbox arm keys (Linear README: Q E R F V + glacier) */ +export const LINEAR_HOTKEYS = Object.freeze({ + KeyQ: 'ice', + KeyE: 'thunder', + KeyR: 'meteor', + KeyF: 'beam', + KeyV: 'snare', + KeyG: 'glacier' +}); + +export { LINEAR_ELEMENTS, ELEMENT_META, linearSettings, CastShape, castShapeOf }; + +/** + * Owns linear AbilityManager + AimController + fissures. + * Wire from App after particles/decals/lights exist. + */ +export class LinearSkillBridge { + /** + * @param {{ + * scene: import('three').Scene, + * camera: import('three').Camera, + * environment: object, + * particles: object, + * lights: object, + * decals: object, + * bursts: object, + * shake: object, + * flash: object, + * character?: object, + * onToast?: (s: string) => void + * }} ctx + */ + constructor(ctx) { + this.ctx = ctx; + this.onToast = ctx.onToast || (() => {}); + this.enabled = true; + this.armed = false; + this.selected = LINEAR_ELEMENTS[0]; + this.cooldowns = new Map(LINEAR_ELEMENTS.map((e) => [e, 0])); + + this.fissures = new FissureSystem(ctx.scene); + + this.manager = new LinearAbilityManager({ + scene: ctx.scene, + camera: ctx.camera, + environment: ctx.environment, + particles: ctx.particles, + lights: ctx.lights, + decals: ctx.decals, + fissures: this.fissures, + bursts: ctx.bursts, + shake: ctx.shake, + flash: ctx.flash + }); + + this.aim = new AimController(ctx.camera); + ctx.scene.add(this.aim.object3D); + + this.aim.on('cast', (origin, direction, distance) => { + this._fire(origin, direction, distance); + }); + this.aim.on('reject', () => this.onToast('Too close — aim further out')); + this.aim.on('cancel', () => { + this.armed = false; + }); + } + + /** + * Apply size/speed/angle/texture variant to a linear skill block (live knobs). + * One texture family · different cast reads. @see vfx/effectVariants.js + * @param {string} variantId e.g. arc_bolt · arc_storm · aoe_snare + * @param {string} [skillId] linear id (default selected) + */ + applyVariant(variantId, skillId) { + const id = skillId || this.selected; + const block = linearSettings[id]; + if (!block) return false; + // Dynamic import avoided — sync catalog + return import('../vfx/effectVariants.js').then(({ getEffectVariant, applyVariantToBlock }) => { + const v = getEffectVariant(variantId); + if (!v) { + this.onToast(`Unknown variant ${variantId}`); + return false; + } + applyVariantToBlock(block, v, { + baseSpeed: block.speed, + baseWidth: block.width + }); + // Fan angle for multi-cast / launch offset + this._variantAngleDeg = v.angleDeg || 0; + this.onToast( + `Variant ${v.label} · size×${v.size} · speed×${v.speed}` + + (v.aoe ? ` · aoe ${v.aoe}m` : '') + ); + return true; + }); + } + + /** + * Select + arm skillshot for MOBA aim (or fire immediately if opts.instant). + * @param {string} id product element or linear id + * @param {{ instant?: boolean, origin?: Vector3, direction?: Vector3, distance?: number, variantId?: string }} [opts] + */ + select(id, opts = {}) { + const linear = PRODUCT_TO_LINEAR[id] || (LINEAR_ELEMENTS.includes(id) ? id : null); + if (!linear) return false; + this.selected = linear; + this.manager.select(linear); + this.aim.setElement(linear); + if (opts.variantId) this.applyVariant(opts.variantId, linear); + + if (opts.instant && opts.origin && opts.direction) { + const dist = + opts.distance ?? + linearSettings[linear]?.range ?? + 12; + // Variant angle: rotate launch dir in XZ + if (this._variantAngleDeg) { + const a = (this._variantAngleDeg * Math.PI) / 180; + const x = opts.direction.x; + const z = opts.direction.z; + opts.direction.x = x * Math.cos(a) - z * Math.sin(a); + opts.direction.z = x * Math.sin(a) + z * Math.cos(a); + } + this._fire(opts.origin, opts.direction, dist); + return true; + } + return true; + } + + /** + * Arm ground indicator (line or zone). Call after select. + * @param {Vector3} feet + */ + arm(feet) { + if (!this.enabled) return; + const cd = this.cooldowns.get(this.selected) || 0; + if (cd > 0) { + this.onToast(`${this.selected} cooling · ${cd.toFixed(1)}s`); + return; + } + this.armed = true; + this.aim.setElement(this.selected); + if (feet) this.aim.setOrigin(feet); + this.aim.arm(); + const meta = ELEMENT_META[this.selected]; + this.onToast( + `Arm ${meta?.label || this.selected} · ${ + castShapeOf(this.selected) === CastShape.ZONE ? 'zone' : 'line' + } · click to fire · Esc cancel` + ); + } + + /** Cancel arming without cast. */ + cancel() { + this.armed = false; + this.aim.cancel(); + } + + /** Confirm cast if armed (LMB while skillshot aiming). */ + confirm() { + if (!this.armed && !this.aim.isArmed) return false; + return this.aim.confirm(); + } + + /** + * Fire from focus crosshair / mouse aim (combat skill path). + * @param {Vector3} feet + * @param {Vector3} aimPoint world + * @param {string} [id] + */ + castToward(feet, aimPoint, id) { + if (id) this.select(id); + _origin.copy(feet); + _origin.y = 0; + _dir.set(aimPoint.x - feet.x, 0, aimPoint.z - feet.z); + const len = _dir.length(); + if (len < 0.15) _dir.set(0, 0, 1); + else _dir.multiplyScalar(1 / len); + const maxR = linearSettings[this.selected]?.range ?? 14; + const minR = linearSettings[this.selected]?.minRange ?? 1.5; + const dist = Math.max(minR, Math.min(maxR, len || maxR * 0.7)); + return this._fire(_origin, _dir, dist); + } + + /** + * @param {Vector3} origin + * @param {Vector3} direction + * @param {number} distance + */ + _fire(origin, direction, distance) { + const cd = this.cooldowns.get(this.selected) || 0; + if (cd > 0) return null; + const ab = this.manager.cast(origin, direction, distance, this.selected); + if (!ab) return null; + const cool = linearSettings[this.selected]?.cooldown ?? 0.5; + this.cooldowns.set(this.selected, cool); + this.armed = false; + this.ctx.character?.playCastFlourish?.() || + this.ctx.character?.playWeaponCombat?.('cast') || + this.ctx.character?.requestOneShot?.('cast'); + // Face cast + if (this.ctx.character && direction) { + const yaw = Math.atan2(direction.x, direction.z); + if (typeof this.ctx.character.facing === 'number') this.ctx.character.facing = yaw; + if (this.ctx.character.root) this.ctx.character.root.rotation.y = yaw; + } + this.onToast(`${ELEMENT_META[this.selected]?.label || this.selected} cast`); + return ab; + } + + /** + * @param {number} dt + * @param {Vector3} feet + * @param {{x:number,y:number}|null} pointerNdc + */ + update(dt, feet, pointerNdc) { + for (const [k, v] of this.cooldowns) { + if (v > 0) this.cooldowns.set(k, Math.max(0, v - dt)); + } + this.manager.update(dt); + this.fissures?.update?.(dt); + + if (this.aim.isArmed || this.armed) { + this.armed = this.aim.isArmed; + if (feet) this.aim.setOrigin(feet); + if (pointerNdc) this.aim.point(pointerNdc); + this.aim.update(dt); + } else { + this.aim.update(dt); // reveal fade-out + } + } + + /** Intensity / global knobs → linearSettings.global (live mid-cast). */ + applyIntensity(level = 1) { + const g = linearSettings.global; + if (!g) return; + const t = Math.max(0.25, Math.min(2, level)); + g.shaderIntensity = t; + g.glow = t; + g.explosionIntensity = t; + g.particleSize = 0.85 + t * 0.25; + g.lightIntensity = t; + } + + clear() { + this.manager.clear?.(); + this.cancel(); + } + + dispose() { + this.clear(); + this.aim?.object3D?.removeFromParent?.(); + } +} diff --git a/src/skillshot/ProceduralGeometry.js b/src/skillshot/ProceduralGeometry.js new file mode 100644 index 0000000..aefd1a9 --- /dev/null +++ b/src/skillshot/ProceduralGeometry.js @@ -0,0 +1,516 @@ +import { + BufferGeometry, + BufferAttribute, + Float32BufferAttribute, + IcosahedronGeometry, + InstancedBufferGeometry, + InstancedBufferAttribute, + Sphere, + Vector3 +} from 'three'; +import { clamp, hash11, smoothstep } from '../utils/math.js'; + +/** + * CPU-side procedural geometry. + * + * The project ships no mesh assets beyond the character, so the ice crystals are + * generated here. A crystal is cheap enough to rebuild — a six-sided one is 108 + * triangles — that the *shape* sliders in the editor regenerate it outright + * instead of approximating themselves in a vertex shader. That is what lets the + * facet count, the taper and the surface roughness stay live controls, including + * while the simulation is paused. + * + * Everything is deterministic in `seed`: the same seed always yields the same + * crystal, so a rebuild mid-cast reshapes the field without reshuffling it. + */ + +const TAU = Math.PI * 2; + +/** + * Height / radius profile of a crystal, sampled at the ring heights below. + * `t` is the fraction of the way to the tip; the returned radius is a fraction + * of the base radius. + */ +const RING_HEIGHTS = [0, 0.22, 0.5, 0.75, 0.92]; + +function profileRadius(t, taper) { + return taper + (1 - taper) * Math.pow(1 - t, 1.15); +} + +/** + * A single ice crystal: a tapered, faceted, slightly bent prism. + * + * Unit space — base ring on y = 0 with a circumscribed radius of 0.5, apex at + * y = 1. An instance therefore scales footprint and height independently, and + * `local.y` reads straight off as "how far up this crystal am I", which is what + * the rime banding in `materials/IceMaterial.js` keys off. + * + * @param {object} options + * @param {number} [options.seed] deterministic shape seed + * @param {number} [options.sides] facet count around the prism (5–8 read best) + * @param {number} [options.taper] tip radius as a fraction of the base + * @param {number} [options.roughness] how far facets are pushed off a clean prism + * @param {number} [options.bend] sideways curve from base to tip + */ +export function createCrystalGeometry({ + seed = 1, + sides = 6, + taper = 0.13, + roughness = 0.28, + bend = 0.22 +} = {}) { + const facets = Math.max(3, Math.round(sides)); + const tipRadius = Math.min(0.9, Math.max(0.01, taper)); + + // One fixed bend direction per crystal, so a whole field leans convincingly + // instead of every spike curving the same way. + const bendAngle = hash11(seed * 1.77) * TAU; + const bendX = Math.cos(bendAngle); + const bendZ = Math.sin(bendAngle); + + /** Lateral drift of the crystal's axis at height `t`. */ + const axisOffset = (t) => bend * 0.5 * Math.pow(t, 1.6); + + // --- rings ------------------------------------------------------------- + // Angles are jittered once and shared by every ring, so the facets stay + // continuous edges up the crystal rather than twisting into a screw. + const angles = []; + for (let i = 0; i < facets; i++) { + const jitter = (hash11(seed * 3.13 + i * 7.7) - 0.5) * (TAU / facets) * 0.55 * roughness * 3; + angles.push((i / facets) * TAU + jitter); + } + + const rings = RING_HEIGHTS.map((t, ringIndex) => { + const baseR = profileRadius(t, tipRadius) * 0.5; + const drift = axisOffset(t); + // Height wobble keeps the shoulder lines from stacking into clean bands. + const y = t + (hash11(seed * 5.9 + ringIndex * 2.3) - 0.5) * 0.06 * roughness * (t > 0 ? 1 : 0); + + return angles.map((angle, i) => { + // Irregularity grows toward the tip: a crystal is roughly round where it + // leaves the ground and increasingly ragged where it was torn. + const wobble = 1 + (hash11(seed * 11.1 + ringIndex * 13.7 + i * 3.9) - 0.5) * roughness * 1.3 * (0.35 + 0.65 * t); + const r = Math.max(0.002, baseR * wobble); + return [Math.cos(angle) * r + bendX * drift, y, Math.sin(angle) * r + bendZ * drift]; + }); + }); + + // The apex is offset a little off-axis so the tip reads as chipped rather + // than as the vertex of a cone. + const apexDrift = axisOffset(1); + const apex = [ + bendX * apexDrift + (hash11(seed * 17.3) - 0.5) * 0.09 * roughness, + 1, + bendZ * apexDrift + (hash11(seed * 19.7) - 0.5) * 0.09 * roughness + ]; + const floorCentre = [0, 0, 0]; + + // --- triangles --------------------------------------------------------- + const positions = []; + const push = (p) => positions.push(p[0], p[1], p[2]); + + for (let ring = 0; ring < rings.length - 1; ring++) { + const lower = rings[ring]; + const upper = rings[ring + 1]; + for (let i = 0; i < facets; i++) { + const j = (i + 1) % facets; + push(lower[i]); push(lower[j]); push(upper[i]); + push(lower[j]); push(upper[j]); push(upper[i]); + } + } + + const top = rings[rings.length - 1]; + const base = rings[0]; + for (let i = 0; i < facets; i++) { + const j = (i + 1) % facets; + push(top[i]); push(top[j]); push(apex); // the point + push(floorCentre); push(base[j]); push(base[i]); // the underside + } + + const geometry = new BufferGeometry(); + geometry.setAttribute('position', new Float32BufferAttribute(positions, 3)); + // Non-indexed + per-face normals: this is what makes the facets crisp. + geometry.computeVertexNormals(); + return geometry; +} + +/** + * A short, wide crystal shard — the ankle-height rubble that fills in around the + * main spikes. Same unit space, so it instances through the identical path. + */ +export function createShardGeometry(seed = 5, sides = 5) { + return createCrystalGeometry({ + seed: seed * 2.7 + 41, + sides, + taper: 0.22, + roughness: 0.55, + bend: 0.35 + }); +} + +/* ---------------------------------------------------------------------- */ +/* Asteroid */ +/* ---------------------------------------------------------------------- */ + +/** One lattice corner of the value noise below. */ +function lattice(ix, iy, iz, seed) { + return hash11(ix * 127.1 + iy * 311.7 + iz * 74.7 + seed * 19.19); +} + +/** + * Deterministic 3D value noise, 0..1. + * + * The GLSL library has simplex noise, but the asteroid is displaced on the CPU + * (a vertex shader cannot move a shadow caster's silhouette or its normals), so + * it needs a JS counterpart. Value noise on a smoothstepped lattice is plenty: + * the shape is read at the silhouette, not up close, and the rock's *detail* + * comes from the flat facets and the crack shader on top. + */ +function valueNoise3(x, y, z, seed) { + const ix = Math.floor(x); + const iy = Math.floor(y); + const iz = Math.floor(z); + const fx = x - ix; + const fy = y - iy; + const fz = z - iz; + + const ux = fx * fx * (3 - 2 * fx); + const uy = fy * fy * (3 - 2 * fy); + const uz = fz * fz * (3 - 2 * fz); + + const c000 = lattice(ix, iy, iz, seed); + const c100 = lattice(ix + 1, iy, iz, seed); + const c010 = lattice(ix, iy + 1, iz, seed); + const c110 = lattice(ix + 1, iy + 1, iz, seed); + const c001 = lattice(ix, iy, iz + 1, seed); + const c101 = lattice(ix + 1, iy, iz + 1, seed); + const c011 = lattice(ix, iy + 1, iz + 1, seed); + const c111 = lattice(ix + 1, iy + 1, iz + 1, seed); + + const x00 = c000 + (c100 - c000) * ux; + const x10 = c010 + (c110 - c010) * ux; + const x01 = c001 + (c101 - c001) * ux; + const x11 = c011 + (c111 - c011) * ux; + + const y0 = x00 + (x10 - x00) * uy; + const y1 = x01 + (x11 - x01) * uy; + + return y0 + (y1 - y0) * uz; +} + +/** Signed fbm over `valueNoise3`, roughly -1..1. */ +function fbmValue(x, y, z, seed, octaves) { + let value = 0; + let amplitude = 0.5; + let frequency = 1; + for (let i = 0; i < octaves; i++) { + value += amplitude * (valueNoise3(x * frequency, y * frequency, z * frequency, seed + i * 7.7) * 2 - 1); + frequency *= 2.03; + amplitude *= 0.5; + } + return value; +} + +/** + * A meteor: a fractured, cratered ball of rock. + * + * Unit space — an icosphere of radius 1 whose vertices are pushed in and out + * along their own direction, so an instance scales it straight to metres and + * `local` reads as a direction on the rock. That matters because + * `materials/MeteorMaterial.js` samples its lava seams in exactly that space: + * the cracks are welded to the rock and tumble with it instead of swimming + * through it. + * + * Three things stack to make it read as *stone* rather than as a wobbly ball: + * + * 1. **fbm lumps** — the big shape, so no two directions look alike. + * 2. **planar cuts** — the rock is sliced by a handful of random half-spaces: + * anything outside a plane is pushed back onto it. That leaves genuine flat + * faces meeting at hard edges, which is what quarried and shattered stone + * actually looks like, and it is the single biggest difference between this + * and a displaced sphere. + * 3. **craters** — bowls with a heaped ejecta rim, punched through the lot. + * + * The displacement is a pure function of the vertex *direction*, which is what + * lets the geometry be non-indexed (needed for flat facets) without splitting + * open — duplicated vertices share a direction, so they are moved identically. + * + * @param {object} options + * @param {number} [options.seed] deterministic shape seed + * @param {number} [options.detail] icosphere subdivisions, 0–3 + * @param {number} [options.lumpiness] low-frequency deformation, × the radius + * @param {number} [options.noiseScale] lumps per unit radius + * @param {number} [options.roughness] high-frequency chipping + * @param {number} [options.cuts] planar fracture faces sliced off it + * @param {number} [options.cutDepth] how far in those planes bite, × the radius + * @param {number} [options.craters] impact bowls punched into it + * @param {number} [options.craterDepth] how deep those bowls go, × the radius + * @param {number} [options.craterSize] their angular radius, radians + */ +export function createAsteroidGeometry({ + seed = 1, + detail = 3, + lumpiness = 0.26, + noiseScale = 1.5, + roughness = 0.16, + cuts = 7, + cutDepth = 0.2, + craters = 5, + craterDepth = 0.18, + craterSize = 0.5 +} = {}) { + const geometry = new IcosahedronGeometry(1, clamp(Math.round(detail), 0, 3)).toNonIndexed(); + const array = geometry.attributes.position.array; + + /** A deterministic point on the unit sphere. */ + const direction = (a, b) => { + const phi = Math.acos(2 * hash11(a) - 1); + const theta = hash11(b) * TAU; + const sinPhi = Math.sin(phi); + return { x: sinPhi * Math.cos(theta), y: Math.cos(phi), z: sinPhi * Math.sin(theta) }; + }; + + // Cuts and craters are picked once per seed and shared by every vertex, so the + // vertex loop below stays a pure lookup. + const planes = []; + for (let i = 0; i < Math.max(0, Math.round(cuts)); i++) { + const n = direction(seed * 2.3 + i * 9.1, seed * 5.7 + i * 4.3); + // How far along its own normal the plane sits: 1 is tangent (no bite), less + // shaves a face off. Kept above 0.55 so a cut never lops the rock in half. + n.offset = 1 - cutDepth * (0.35 + 0.9 * hash11(seed * 13.1 + i * 6.7)); + planes.push(n); + } + + const bowls = []; + for (let i = 0; i < Math.max(0, Math.round(craters)); i++) { + const c = direction(seed * 3.1 + i * 12.9, seed * 7.7 + i * 5.3); + c.radius = Math.max(0.08, craterSize * (0.45 + 0.8 * hash11(seed * 11.3 + i * 3.7))); + c.depth = craterDepth * (0.5 + hash11(seed * 17.9 + i * 2.1)); + bowls.push(c); + } + + for (let i = 0; i < array.length; i += 3) { + // IcosahedronGeometry(1) hands us unit-length vertices already. + const x = array[i]; + const y = array[i + 1]; + const z = array[i + 2]; + + /* --- 1. the lumpy body --- */ + let radius = 1; + radius += fbmValue(x * noiseScale, y * noiseScale, z * noiseScale, seed, 3) * lumpiness; + radius += + fbmValue(x * noiseScale * 4.3, y * noiseScale * 4.3, z * noiseScale * 4.3, seed + 31.7, 2) * + roughness * + 0.5; + + /* --- 2. craters, before the cuts so a cut can shear one in half --- */ + for (const bowl of bowls) { + const angle = Math.acos(clamp(x * bowl.x + y * bowl.y + z * bowl.z, -1, 1)); + const q = angle / bowl.radius; + if (q >= 1.4) continue; + radius -= bowl.depth * Math.max(0, 1 - q * q); + radius += bowl.depth * 0.5 * smoothstep(0.72, 1.0, q) * (1 - smoothstep(1.0, 1.4, q)); + } + + radius = Math.max(0.35, radius); + let px = x * radius; + let py = y * radius; + let pz = z * radius; + + /* --- 3. slice off the flat faces --- */ + for (const plane of planes) { + const along = px * plane.x + py * plane.y + pz * plane.z; + const over = along - plane.offset; + if (over <= 0) continue; + // Project back onto the plane. Every vertex outside it lands *on* it, so + // the result is a genuinely flat facet, not a squashed curve. + px -= plane.x * over; + py -= plane.y * over; + pz -= plane.z * over; + } + + array[i] = px; + array[i + 1] = py; + array[i + 2] = pz; + } + + geometry.attributes.position.needsUpdate = true; + // Non-indexed + per-face normals: the facets stay crisp, like the crystals. + geometry.computeVertexNormals(); + geometry.computeBoundingSphere(); + return geometry; +} + +/** + * The strip a lightning filament is drawn on — a flat ladder of quads, in + * *parameter* space rather than world space. + * + * Every vertex carries `position = (t, side, 0)`, where `t` runs 0 → 1 from the + * caster's hand to the impact point and `side` is ±1 across the ribbon. There + * are no metres in here at all: `materials/LightningMaterial.js` turns that pair + * into a world position every frame, so one strip serves a bolt of any length, + * any shape and any width, and the whole path stays a live slider. + * + * One instance is one filament, and `aStrand` is simply its index — the shader + * derives the filament's seed, its place in the fan and its width from it. + * + * @param {number} nodes samples along the bolt; the kink detail ceiling + * @param {number} strands instance capacity (the live count is `instanceCount`) + */ +export function createBoltRibbonGeometry(nodes = 72, strands = 24) { + const steps = Math.max(2, Math.round(nodes)); + const count = Math.max(1, Math.round(strands)); + + const positions = new Float32Array(steps * 2 * 3); + for (let i = 0; i < steps; i++) { + const t = i / (steps - 1); + const o = i * 6; + positions[o + 0] = t; + positions[o + 1] = -1; + positions[o + 3] = t; + positions[o + 4] = 1; + } + + const indices = new Uint16Array((steps - 1) * 6); + for (let i = 0; i < steps - 1; i++) { + const a = i * 2; + const o = i * 6; + indices[o + 0] = a; + indices[o + 1] = a + 1; + indices[o + 2] = a + 2; + indices[o + 3] = a + 1; + indices[o + 4] = a + 3; + indices[o + 5] = a + 2; + } + + const strandIndex = new Float32Array(count); + for (let i = 0; i < count; i++) strandIndex[i] = i; + + const geometry = new InstancedBufferGeometry(); + geometry.setAttribute('position', new BufferAttribute(positions, 3)); + geometry.setAttribute('aStrand', new InstancedBufferAttribute(strandIndex, 1)); + geometry.setIndex(new BufferAttribute(indices, 1)); + geometry.instanceCount = count; + // The bolt is built in world space in the vertex shader, so the geometry's own + // bounds are meaningless — cull it manually instead (the ability sets + // `frustumCulled = false`). + geometry.boundingSphere = new Sphere(new Vector3(), 1e4); + return geometry; +} + +/* ---------------------------------------------------------------------- */ +/* Beam */ +/* ---------------------------------------------------------------------- */ + +/** + * The column a beam is drawn on — a tube in *parameter* space. + * + * Same trick as the bolt ribbon, one dimension richer: every vertex carries + * `position = (t, a, 0)`, where `t` runs 0 → 1 from the muzzle to the impact + * point and `a` runs 0 → 1 once around the barrel. There are no metres in here + * either; `materials/BeamMaterial.js` turns that pair into a world position + * every frame, so one tube serves a beam of any length and any profile. + * + * Real tube rather than the camera-facing ribbon the bolt uses, because a beam + * this thick has to *have* a cross-section: the silhouette has to bow correctly + * when you orbit around it, the far wall has to add through the near one, and + * the shock rings have to be able to hug it. A ribbon can fake none of that. + * + * The seam column is duplicated so `a` reaches a full 1.0 instead of wrapping + * to 0 — the angular noise in the shader would otherwise show a hard join line + * down the length of the beam. + * + * @param {number} nodes samples along the column; the profile detail ceiling + * @param {number} sides facets around the barrel (20–32 reads clean) + */ +export function createBeamTubeGeometry(nodes = 96, sides = 26) { + const steps = Math.max(2, Math.round(nodes)); + const facets = Math.max(3, Math.round(sides)); + const columns = facets + 1; + + const positions = new Float32Array(steps * columns * 3); + let v = 0; + for (let i = 0; i < steps; i++) { + const t = i / (steps - 1); + for (let j = 0; j < columns; j++) { + positions[v++] = t; + positions[v++] = j / facets; + positions[v++] = 0; + } + } + + const indices = new Uint16Array((steps - 1) * facets * 6); + let k = 0; + for (let i = 0; i < steps - 1; i++) { + for (let j = 0; j < facets; j++) { + const a = i * columns + j; + const b = a + columns; + indices[k++] = a; + indices[k++] = b; + indices[k++] = a + 1; + indices[k++] = b; + indices[k++] = b + 1; + indices[k++] = a + 1; + } + } + + const geometry = new BufferGeometry(); + geometry.setAttribute('position', new BufferAttribute(positions, 3)); + geometry.setIndex(new BufferAttribute(indices, 1)); + // Placed in world space by the vertex shader, like the bolt — its own bounds + // mean nothing (the ability sets `frustumCulled = false`). + geometry.boundingSphere = new Sphere(new Vector3(), 1e4); + return geometry; +} + +/** + * The shock discs that race down a beam — an instanced annulus, again in + * parameter space: `position = (band, a, 0)` with `band` 0 at the inner lip and + * 1 at the outer one, and `a` once around. + * + * One instance is one disc, and `aRing` is only its index — the shader spaces + * the discs evenly along the column from it and slides them downrange, so the + * whole train is a function of the clock rather than a queue on the CPU. + * + * @param {number} rings instance capacity (the live count is `instanceCount`) + * @param {number} segments facets around one disc + */ +export function createBeamRingGeometry(rings = 10, segments = 44) { + const count = Math.max(1, Math.round(rings)); + const facets = Math.max(6, Math.round(segments)); + const columns = facets + 1; + + const positions = new Float32Array(2 * columns * 3); + let v = 0; + for (let band = 0; band < 2; band++) { + for (let j = 0; j < columns; j++) { + positions[v++] = band; + positions[v++] = j / facets; + positions[v++] = 0; + } + } + + const indices = new Uint16Array(facets * 6); + let k = 0; + for (let j = 0; j < facets; j++) { + const a = j; + const b = columns + j; + indices[k++] = a; + indices[k++] = b; + indices[k++] = a + 1; + indices[k++] = b; + indices[k++] = b + 1; + indices[k++] = a + 1; + } + + const ringIndex = new Float32Array(count); + for (let i = 0; i < count; i++) ringIndex[i] = i; + + const geometry = new InstancedBufferGeometry(); + geometry.setAttribute('position', new BufferAttribute(positions, 3)); + geometry.setAttribute('aRing', new InstancedBufferAttribute(ringIndex, 1)); + geometry.setIndex(new BufferAttribute(indices, 1)); + geometry.instanceCount = count; + geometry.boundingSphere = new Sphere(new Vector3(), 1e4); + return geometry; +} diff --git a/src/skillshot/abilities/Ability.js b/src/skillshot/abilities/Ability.js new file mode 100644 index 0000000..864771a --- /dev/null +++ b/src/skillshot/abilities/Ability.js @@ -0,0 +1,279 @@ +import { Group, Vector3, Color } from 'three'; +import { settings } from '../linearSettings.js'; +import { saturate, Easing } from '../../utils/math.js'; +import { getColor } from '../../utils/color.js'; +import { LAYER } from '../../core/Layers.js'; + +export const AbilityPhase = Object.freeze({ + IDLE: 'idle', + TRAVEL: 'travel', + IMPACT: 'impact', + FADE: 'fade', + DONE: 'done' +}); + +const _up = new Vector3(0, 1, 0); + +/** + * Abstract base for a linear skillshot. + * + * The sandbox used to cast along a freehand spline; it now casts along a **line** + * chosen with the aim indicator, so this base is correspondingly small. What it + * still owns, because every ability wants it and none of them should re-derive + * it: + * + * - the phase machine (travel → impact → fade → done) + * - a front that advances along the line at a constant metres-per-second, + * frame-rate independent and eased off a standstill + * - the local frame (`direction`, `side`) every effect places itself in + * - dynamic light bookkeeping, including a decaying impact punch + * - the pooling contract: `spawn` must fully reset state, `destroy` must + * release, and neither may allocate + * + * Subclasses implement: `createShaders`, `createParticles`, `onSpawn`, + * `onTravel`, `onImpact`, `onFade`, `onDestroy`. + * + * A second ability is therefore one new file plus a settings block — nothing + * else in the project changes. + */ +export class Ability { + /** + * @param {string} element key into `settings` ('ice') + * @param {object} context shared systems (see AbilityManager) + */ + constructor(element, context) { + this.element = element; + this.ctx = context; + + this.group = new Group(); + this.group.name = `Ability:${element}`; + this.group.layers.set(LAYER.VFX); + this.group.matrixAutoUpdate = false; + + this.phase = AbilityPhase.IDLE; + + /** Where the cast came from, on the floor. */ + this.origin = new Vector3(); + /** Unit heading, flat. */ + this.direction = new Vector3(0, 0, 1); + /** Unit lateral, `direction × up`. */ + this.side = new Vector3(1, 0, 0); + /** How far the cast reaches, metres. */ + this.length = 1; + + /** Metres the fracture front has travelled. */ + this.front = 0; + /** That front as a fraction of `length`. */ + this.u = 0; + /** World position of the front — what the camera frames. */ + this.position = new Vector3(); + + this.age = 0; + this.impactTime = 0; + this.fadeTime = 0; + + this.light = null; + this.lightColor = new Color(); + /** Transient additive light punch (impacts). Decays on its own. */ + this.lightBoost = 0; + + this.createShaders(); + this.createParticles(); + } + + /** Live settings block for this element. */ + get config() { + return settings[this.element]; + } + + get isActive() { + return this.phase !== AbilityPhase.IDLE && this.phase !== AbilityPhase.DONE; + } + + get isFinished() { + return this.phase === AbilityPhase.DONE; + } + + /** Instanced geometry this cast is currently drawing. HUD readout only. */ + get instanceCount() { + return 0; + } + + /* ------------------------------------------------------------------ */ + /* Subclass hooks */ + /* ------------------------------------------------------------------ */ + + /** Build materials/meshes once, at construction. */ + createShaders() {} + + /** Register the shared particle systems this element needs. */ + createParticles() {} + + /** Called at the start of every cast, after the base state was reset. */ + onSpawn() {} + + /** Per-frame while the front is still travelling. */ + onTravel(_dt) {} + + /** One-shot when the front reaches the end of the line. */ + onImpact() {} + + /** + * Per-frame after the impact. + * @param {number} t 0..1 through the impact phase, then 1..2 through the fade. + */ + onFade(_dt, _t) {} + + /** Release any per-cast resources. */ + onDestroy() {} + + /** How long the impact and fade phases last. Overridable per element. */ + get impactDuration() { + return 1.1; + } + + get fadeDuration() { + return 1.2; + } + + /** + * Per-frame multiplier on the dynamic light's intensity. + * + * The default is a slow shimmer rather than a flicker: ice glints, it does not + * gutter. Elements that *should* gutter override this. + */ + lightShimmer() { + return 0.9 + 0.1 * Math.sin(this.age * 9.3) * Math.sin(this.age * 3.7); + } + + /* ------------------------------------------------------------------ */ + /* Lifecycle */ + /* ------------------------------------------------------------------ */ + + /** + * Begin a cast. + * + * @param {THREE.Vector3} origin on the floor + * @param {THREE.Vector3} direction unit, flat + * @param {number} distance metres + */ + spawn(origin, direction, distance) { + this.origin.set(origin.x, 0, origin.z); + this.direction.copy(direction).setY(0).normalize(); + this.side.crossVectors(this.direction, _up).normalize(); + this.length = Math.max(0.1, distance); + + this.front = 0; + this.u = 0; + this.age = 0; + this.impactTime = 0; + this.fadeTime = 0; + this.lightBoost = 0; + this.phase = AbilityPhase.TRAVEL; + + this.position.copy(this.origin); + this.light = this.ctx.lights.acquire(); + + this.group.visible = true; + this.onSpawn(); + } + + /** A point on the cast line. `s` is 0..1 along it. */ + pointAt(s, out) { + return out.copy(this.origin).addScaledVector(this.direction, s * this.length); + } + + /** + * Advance the front. Delta-time driven, so the eruption travels at a constant + * metres-per-second regardless of frame rate or how long the cast is. + * + * @returns {boolean} true on the frame the front reaches the end + */ + advance(dt) { + const speed = this.config.speed * settings.global.speed; + // Ease off the standstill so the front has weight. Keyed off elapsed time + // rather than progress: keying it off `u` would multiply the very first step + // by zero and the front could never leave the caster. + const easeIn = Easing.outQuad(saturate(this.age / 0.08)); + + this.front += speed * easeIn * dt; + const previousU = this.u; + this.u = saturate(this.front / this.length); + this.pointAt(this.u, this.position); + + return this.u >= 1 && previousU < 1; + } + + update(dt) { + if (!this.isActive) return; + this.age += dt; + + switch (this.phase) { + case AbilityPhase.TRAVEL: { + const reachedEnd = this.advance(dt); + this.onTravel(dt); + this._updateLight(dt, 1); + if (reachedEnd) { + this.phase = AbilityPhase.IMPACT; + this.impactTime = 0; + this.onImpact(); + } + break; + } + + case AbilityPhase.IMPACT: { + this.impactTime += dt; + const t = saturate(this.impactTime / this.impactDuration); + this.onFade(dt, t); + this._updateLight(dt, 1 - Easing.inQuad(t) * 0.45); + if (t >= 1) { + this.phase = AbilityPhase.FADE; + this.fadeTime = 0; + } + break; + } + + case AbilityPhase.FADE: { + this.fadeTime += dt; + const t = saturate(this.fadeTime / this.fadeDuration); + this.onFade(dt, 1 + t); + this._updateLight(dt, (1 - t) * 0.35); + if (t >= 1) this.phase = AbilityPhase.DONE; + break; + } + + default: + break; + } + } + + _updateLight(dt, scale) { + if (!this.light) return; + const cfg = this.config; + this.lightColor.copy(getColor(cfg.lightColor)); + const shimmer = this.lightShimmer(); + this.ctx.lights.set( + this.light, + this.position, + this.lightColor, + cfg.lightIntensity * scale * shimmer + this.lightBoost, + cfg.lightRadius * (1 + this.lightBoost * 0.02), + dt + ); + this.lightBoost = Math.max(0, this.lightBoost - this.lightBoost * 4.5 * dt - 0.5 * dt); + } + + /** Return to the pool. Must leave the instance reusable. */ + destroy() { + this.onDestroy(); + this.ctx.lights.release(this.light); + this.light = null; + this.group.visible = false; + this.phase = AbilityPhase.IDLE; + } + + /** Free GPU resources (app teardown only — not part of pooling). */ + dispose() { + this.group.parent?.remove(this.group); + } +} diff --git a/src/skillshot/abilities/AbilityManager.js b/src/skillshot/abilities/AbilityManager.js new file mode 100644 index 0000000..1c6410e --- /dev/null +++ b/src/skillshot/abilities/AbilityManager.js @@ -0,0 +1,122 @@ +import { IceAbility } from './IceAbility.js'; +import { ThunderAbility } from './ThunderAbility.js'; +import { MeteorAbility } from './MeteorAbility.js'; +import { BeamAbility } from './BeamAbility.js'; +import { SnareAbility } from './SnareAbility.js'; +import { GlacierAbility } from './GlacierAbility.js'; +import { ELEMENTS } from '../linearSettings.js'; +import { ObjectPool } from '../../utils/ObjectPool.js'; + +/** Registry: adding an ability means adding one line here. */ +const ABILITY_TYPES = { + ice: IceAbility, + thunder: ThunderAbility, + meteor: MeteorAbility, + beam: BeamAbility, + snare: SnareAbility, + glacier: GlacierAbility +}; + +const MAX_CONCURRENT = 4; + +/** + * Spawns, updates and recycles abilities. + * + * Instances are pooled per type: casting fifty times constructs at most a + * handful of objects per ability, and every one of them keeps its meshes and + * materials for the lifetime of the app. Nothing is built during a cast. + * + * `MAX_CONCURRENT` is shared across types, so mixing abilities retires the + * oldest cast whichever element it was. + */ +export class AbilityManager { + /** + * @param {object} context shared systems handed to every ability: + * { scene, camera, environment, particles, lights, decals, bursts, shake, flash } + */ + constructor(context) { + this.ctx = context; + this.active = []; + this.selected = ELEMENTS[0]; + + this.pools = new Map(); + for (const [element, Type] of Object.entries(ABILITY_TYPES)) { + this.pools.set( + element, + new ObjectPool(() => { + const ability = new Type(this.ctx); + this.ctx.scene.add(ability.group); + ability.group.visible = false; + return ability; + }) + ); + } + } + + select(element) { + if (!ABILITY_TYPES[element]) return; + this.selected = element; + } + + /** + * Cast the selected ability along a line. + * + * A far cast takes the same three arguments and simply works from the far end + * of that line — which is why adding zone targeting needed nothing here. + * + * @param {THREE.Vector3} origin on the floor + * @param {THREE.Vector3} direction unit, flat + * @param {number} distance metres + * @returns {import('./Ability.js').Ability|null} + */ + cast(origin, direction, distance, element = this.selected) { + if (!ABILITY_TYPES[element]) return null; + + // Retire the oldest cast rather than letting the scene grow without bound. + if (this.active.length >= MAX_CONCURRENT) { + const oldest = this.active.shift(); + oldest.destroy(); + this.pools.get(oldest.element).release(oldest); + } + + const ability = this.pools.get(element).acquire(); + ability.spawn(origin, direction, distance); + this.active.push(ability); + return ability; + } + + update(dt) { + for (let i = this.active.length - 1; i >= 0; i--) { + const ability = this.active[i]; + ability.update(dt); + if (ability.isFinished) { + this.active.splice(i, 1); + ability.destroy(); + this.pools.get(ability.element).release(ability); + } + } + } + + /** Cancel everything currently in flight. */ + clear() { + for (const ability of this.active) { + ability.destroy(); + this.pools.get(ability.element).release(ability); + } + this.active.length = 0; + } + + /** The most recent still-running cast — used to frame the camera. */ + get focus() { + for (let i = this.active.length - 1; i >= 0; i--) { + if (this.active[i].isActive) return this.active[i]; + } + return null; + } + + dispose() { + this.clear(); + for (const pool of this.pools.values()) pool.dispose((ability) => ability.dispose()); + this.pools.clear(); + } +} diff --git a/src/skillshot/abilities/BeamAbility.js b/src/skillshot/abilities/BeamAbility.js new file mode 100644 index 0000000..c7fdac4 --- /dev/null +++ b/src/skillshot/abilities/BeamAbility.js @@ -0,0 +1,989 @@ +import { IcosahedronGeometry, Mesh, Vector3 } from 'three'; +import { Ability, AbilityPhase } from './Ability.js'; +import { createBeamMaterial, BeamPass } from '../materials/BeamMaterial.js'; +import { + createBeamTubeGeometry, + createBeamRingGeometry, + createBoltRibbonGeometry +} from '../ProceduralGeometry.js'; +import { ParticleShape } from '../../particles/ParticleSystem.js'; +import { RateEmitter } from '../../particles/ParticleEngine.js'; +import { DecalType } from '../../effects/GroundDecals.js'; +import { BurstMode } from '../../effects/BurstSphere.js'; +import { LAYER } from '../../core/Layers.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, lerp, smoothstep, Easing, randRange } from '../../utils/math.js'; + +/** Hard ceilings. The editor's `coils` / `rings` sliders clamp here. */ +const MAX_COILS = 8; +const MAX_RINGS = 12; + +/** Tessellation of the column. Nothing about the *shape* lives in these. */ +const TUBE_NODES = 96; +const TUBE_SIDES = 26; +const COIL_NODES = 128; +const RING_SEGMENTS = 44; + +/** + * How many points along the column one frame's sparks are split between. As + * with the bolt: a single origin makes every batch read as a starburst, and a + * beam sheds along its whole length. + */ +const SPARK_BATCHES = 5; + +const TAU = Math.PI * 2; + +const _emit = {}; +const _pos = new Vector3(); +const _dir = new Vector3(); +const _radial = new Vector3(); +const _target = new Vector3(); +const _up = new Vector3(0, 1, 0); + +/** + * BEAM — a sustained column of light held on the aimed line. + * + * Four beats where the other three abilities have three. The caster winds a + * ball of light up in both hands (`charge`), lets it out as a column that races + * downrange at `speed`, *holds* it there burning into the floor for `lifetime`, + * and finally lets it collapse back to a thread over `fadeTime`. The hold is + * the point of the ability: it is the only cast in the sandbox that is still + * happening a second after it landed. + * + * The extra beat needed nothing from the base class. `advance()` simply refuses + * to let the front leave the hand until the orb is up to power, so the phase + * machine still runs travel → impact → fade and `IMPACT` becomes the burn. + * + * Everything is generated: the column is one parametric tube drawn three times + * at three radii, the coils are the bolt's ribbon strip bent into a helix, the + * shock discs are an instanced annulus slid downrange by the clock, the charge + * is a noise-eroded sphere, and the sparks, motes, steam and chips are GPU + * particles. Nothing is loaded from disk and nothing is a texture. + * + * **Not electric.** The Storm Lance's whole character is that its noise is + * piecewise-linear and keeps its corners; every noise term here is smooth, + * stretched hard along the flow and crawling downrange, because a beam that + * kinks is a bolt. The two abilities share a colour family on purpose — what + * separates them is entirely how they move. + * + * **The rule that makes the editor work.** A cast captures exactly one number — + * `_seed`, so two beams do not draw the identical column — plus timestamps. + * Every metre, radian and second is resolved against `settings.beam` each + * frame, on a zero-length frame included. Dragging `radius` re-bores a beam + * that is already burning; dragging `coilTurns` re-winds its ribbons; dragging + * `flare` re-opens the cone at the far end. That is what pausing with **P** + * mid-burn is for. + */ +export class BeamAbility extends Ability { + constructor(context) { + super('beam', context); + } + + /* ------------------------------------------------------------------ */ + /* Construction */ + /* ------------------------------------------------------------------ */ + + createShaders() { + this.tubeGeometry = createBeamTubeGeometry(TUBE_NODES, TUBE_SIDES); + this.coilGeometry = createBoltRibbonGeometry(COIL_NODES, MAX_COILS); + this.ringGeometry = createBeamRingGeometry(MAX_RINGS, RING_SEGMENTS); + this.orbGeometry = new IcosahedronGeometry(1, 4); + + /* + * Six passes over three geometries. The three tube passes are the same + * mesh at three radii — see `materials/BeamMaterial.js` for why the core is + * weighted inside-out compared to the sheath around it. + * + * Everything here is additive with `depthWrite` off, so the draw order is + * not about correctness; the render orders only keep this ability sitting + * in the same band as the rest of the VFX. + */ + const passes = [ + [BeamPass.HALO, this.tubeGeometry, 11], + [BeamPass.SHELL, this.tubeGeometry, 12], + [BeamPass.CORE, this.tubeGeometry, 13], + [BeamPass.COIL, this.coilGeometry, 13], + [BeamPass.RING, this.ringGeometry, 13], + [BeamPass.ORB, this.orbGeometry, 14] + ]; + + this.materials = []; + this.meshes = []; + for (const [pass, geometry, renderOrder] of passes) { + const material = createBeamMaterial(pass); + const mesh = new Mesh(geometry, material); + mesh.frustumCulled = false; + mesh.layers.set(LAYER.VFX); + mesh.renderOrder = renderOrder; + // Every pass but the orb is placed in world space by its vertex shader. + // The orb is the one thing that rides a model matrix, because the ability + // has to move and scale it as the charge builds. + if (pass === BeamPass.ORB) this.orbMesh = mesh; + else mesh.matrixAutoUpdate = false; + this.group.add(mesh); + this.materials.push(material); + this.meshes.push(mesh); + } + + /** Re-rolled per cast so no two beams draw the same column. */ + this._seed = 0; + this._coilCount = 1; + this._ringCount = 1; + /** Whether the orb has let go yet — the release only fires once. */ + this._fired = false; + /** Metres of front travel already paid out in ground burns. */ + this._burnDistance = 0; + /** The light sitting in the caster's hands. Released with the cast. */ + this.muzzleLight = null; + + // Scratch state handed to all six materials each frame. One object, reused — + // syncing the beam allocates nothing. + this._state = { + origin: new Vector3(), + target: new Vector3(), + side: new Vector3(), + progress: 0, + fade: 1, + widthFade: 1, + charge: 0, + seed: 0, + coils: 1, + rings: 1 + }; + } + + createParticles() { + const particles = this.ctx.particles; + + // Sparks shed off the column: velocity-stretched streaks, thrown radially + // and then dragged downrange by `sparkForward`. + this.sparks = particles.get('beam.sparks', { + capacity: 5000, + shape: ParticleShape.STREAK, + additive: true, + stretch: true, + softFade: 0.25 + }); + this.sparks.uniforms.uDrag.value = 1.5; + this.sparks.uniforms.uEndSize.value = 0.2; + this.sparks.uniforms.uSizeIn.value = 0.02; + this.sparks.uniforms.uFadeIn.value = 0.03; + this.sparks.uniforms.uFadeOut.value = 0.4; + + // Motes: the intake spiralling into the orb while it charges, and the drift + // hanging around the column once it is firing. One system, two jobs — they + // are the same glow, differing only in which way they are thrown. + this.motes = particles.get('beam.motes', { + capacity: 3600, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.4 + }); + this.motes.uniforms.uDrag.value = 1.1; + this.motes.uniforms.uEndSize.value = 0.12; + this.motes.uniforms.uSizeIn.value = 0.05; + this.motes.uniforms.uFadeIn.value = 0.07; + this.motes.uniforms.uFadeOut.value = 0.35; + + // Steam scoured off the floor under the burn. Non-additive so it occludes. + this.smoke = particles.get('beam.smoke', { + capacity: 2200, + shape: ParticleShape.SMOKE, + additive: false, + curl: true, + softFade: 1.1 + }); + this.smoke.uniforms.uDrag.value = 1.7; + this.smoke.uniforms.uEndSize.value = 3.2; + this.smoke.uniforms.uSizeIn.value = 0.12; + this.smoke.uniforms.uFadeIn.value = 0.18; + this.smoke.uniforms.uFadeOut.value = 0.3; + + // Chips torn off the floor along the burn line. + this.debris = particles.get('beam.debris', { + capacity: 1800, + shape: ParticleShape.CHIP, + additive: false, + lit: true, + softFade: 0.25 + }); + this.debris.uniforms.uDrag.value = 0.25; + this.debris.uniforms.uEndSize.value = 0.8; + this.debris.uniforms.uFadeOut.value = 0.7; + + this.sparkEmitter = new RateEmitter(); + this.moteEmitter = new RateEmitter(); + this.intakeEmitter = new RateEmitter(); + this.smokeEmitter = new RateEmitter(); + this.debrisEmitter = new RateEmitter(); + this.splashEmitter = new RateEmitter(); + // Decals and shells are throttled through the same fractional-rate emitter + // the particles use, so "rings per second" stays frame-rate independent. + this.pulseEmitter = new RateEmitter(); + this.dustEmitter = new RateEmitter(); + this.shockEmitter = new RateEmitter(); + } + + /* ------------------------------------------------------------------ */ + /* Timing */ + /* ------------------------------------------------------------------ */ + + get instanceCount() { + // Three tube passes, the coils, the discs and the orb. + return 4 + this._coilCount + this._ringCount; + } + + /** The beam burns where it landed, then collapses. */ + get impactDuration() { + return Math.max(0.05, settings.beam.lifetime * settings.global.lifetime); + } + + get fadeDuration() { + return Math.max(0.05, settings.beam.fadeTime); + } + + /** How far the orb has wound up, 0..1. */ + get charge() { + return saturate(this.age / Math.max(0.01, settings.beam.charge)); + } + + /** + * A beam hums where lightning gutters — a slow sine, not a quantised stutter. + * The one thing that must *not* happen here is a flicker: it would immediately + * read as the Storm Lance's light. + */ + lightShimmer() { + const c = settings.beam; + return 1 - c.lightPulse * (0.5 - 0.5 * Math.cos(this.age * c.lightPulseSpeed * TAU)); + } + + /** + * Hold the front at the hand until the orb is up to power. + * + * This is the whole of the fourth beat. The base class eases off a standstill + * over the first fraction of a second; here the same ease is keyed off the + * moment of release instead of the moment of the cast. + */ + advance(dt) { + const c = this.config; + const charge = Math.max(0, c.charge); + if (this.age < charge) return false; + + const speed = c.speed * settings.global.speed; + const since = this.age - charge; + this.front += speed * Easing.outQuad(saturate(since / 0.05)) * dt; + + const previousU = this.u; + this.u = saturate(this.front / this.length); + this.pointAt(this.u, this.position); + return this.u >= 1 && previousU < 1; + } + + /* ------------------------------------------------------------------ */ + /* Geometry of the beam — every metre resolved from live settings */ + /* ------------------------------------------------------------------ */ + + /** + * Where the column leaves the caster, in world space. + * + * The base class puts `origin` on the floor because that is what the aim + * indicator targets; a beam comes out of a pair of hands, so the offsets are + * applied here rather than baked into the cast. + */ + _handPoint(out) { + const c = settings.beam; + out + .copy(this.origin) + .addScaledVector(this.direction, c.handForward) + .addScaledVector(this.side, c.handSide); + out.y = c.handHeight; + return out; + } + + /** Where it lands. */ + _impactPoint(out) { + this.pointAt(1, out); + out.y = settings.beam.endHeight; + return out; + } + + /** + * A point on the column's *axis* at `s` along it, 0..1. + * + * Mirrors the first stage of the vertex shader, minus the sub-decimetre + * drift, so the sparks and burns the CPU spawns sit on the beam the GPU + * actually draws instead of near it. + */ + _axisPoint(s, out) { + const c = settings.beam; + const t = saturate(s); + out + .copy(this.origin) + .addScaledVector(this.direction, lerp(c.handForward, this.length, t)) + .addScaledVector(this.side, c.handSide * (1 - t)); + out.y = lerp(c.handHeight, c.endHeight, t); + return out; + } + + /** + * Half-width of the column at `s`, metres — how far off the axis sparks are + * thrown. Mirrors `beamRadius()` in the shader, minus the throb, so the spray + * sits on the surface the GPU is drawing. + */ + _beamRadius(s) { + const c = settings.beam; + const t = saturate(s); + const r = lerp(c.radiusNear, c.radius, Math.pow(t, Math.max(0.01, c.radiusCurve))); + return r * (1 + c.flare * smoothstep(1 - Math.max(1e-3, c.flareWidth), 1, t)); + } + + /** A unit vector perpendicular to the column, `a` radians around it. */ + _radialAt(a, out) { + return out + .copy(this.side) + .multiplyScalar(Math.cos(a)) + .addScaledVector(_up, Math.sin(a)) + .normalize(); + } + + /* ------------------------------------------------------------------ */ + /* Casting */ + /* ------------------------------------------------------------------ */ + + onSpawn() { + for (const emitter of [ + this.sparkEmitter, + this.moteEmitter, + this.intakeEmitter, + this.smokeEmitter, + this.debrisEmitter, + this.splashEmitter, + this.pulseEmitter, + this.dustEmitter, + this.shockEmitter + ]) { + emitter.reset(); + } + + this._burnDistance = 0; + this._fired = false; + + // The one thing a cast captures. Everything else is resolved per frame. + this._seed = Math.random() * 100; + + // A second light, sitting in the caster's hands, so the wind-up actually + // lights the body holding it. The pool can hand back null when several + // casts are in flight — every use below is guarded. + this.muzzleLight = this.ctx.lights.acquire(); + + this._syncUniforms(0, 1, 1); + } + + /* ------------------------------------------------------------------ */ + /* Feedback */ + /* ------------------------------------------------------------------ */ + + /** + * Push the live settings and the current cast state into all six passes. + * + * @param {number} dt seconds, for the muzzle light's easing + * @param {number} fade 1 while the beam is lit, ramping to 0 as it dies + * @param {number} widthFade the same for the column's *width* — it collapses + * to a thread faster than it dims, which is what + * makes the cut-out read as a snap + */ + _syncUniforms(dt, fade, widthFade) { + const c = settings.beam; + const g = settings.global; + const state = this._state; + + this._handPoint(state.origin); + this._impactPoint(state.target); + state.side.copy(this.side); + state.progress = this.phase === AbilityPhase.TRAVEL ? this.u : 1; + state.fade = fade; + state.widthFade = widthFade; + state.charge = this.charge; + state.seed = this._seed; + + this._coilCount = Math.max(1, Math.min(MAX_COILS, Math.round(c.coils))); + this._ringCount = Math.max(1, Math.min(MAX_RINGS, Math.round(c.rings))); + state.coils = this._coilCount; + state.rings = this._ringCount; + this.coilGeometry.instanceCount = this._coilCount; + this.ringGeometry.instanceCount = this._ringCount; + + for (const material of this.materials) material.userData.sync(state); + + /* --- the orb: placed and sized here, shaded in the shader --- */ + const swell = 1 + c.orbThrob * Math.sin(this.age * c.orbThrobSpeed * TAU); + const size = c.orbSize * (0.28 + 0.72 * Easing.outCubic(state.charge)) * swell * widthFade; + this.orbMesh.position.copy(state.origin); + this.orbMesh.scale.setScalar(Math.max(0.01, size)); + + if (this.muzzleLight) { + this.ctx.lights.set( + this.muzzleLight, + state.origin, + getColor(c.lightColor), + c.muzzleLightIntensity * state.charge * fade, + c.muzzleLightRadius, + dt + ); + } + + /* --- the particle systems, all four of them --- */ + this.sparks.setGradient( + getColor(c.colorSparkA), + getColor(c.colorSparkB), + getColor(c.colorSparkC), + getColor(c.colorSparkD) + ); + this.sparks.uniforms.uGravity.value.set(0, c.sparkGravity, 0); + this.sparks.uniforms.uSizeScale.value = c.sparkSize * g.particleSize * 7; + this.sparks.uniforms.uLifeScale.value = c.sparkLifetime * 0.5 * g.particleLifetime; + this.sparks.uniforms.uSpeedScale.value = g.particleSpeed; + this.sparks.uniforms.uOpacity.value = g.opacity; + this.sparks.uniforms.uGlow.value = c.glow * 0.6 * g.glow; + this.sparks.uniforms.uStretch.value = c.sparkStretch; + this.sparks.uniforms.uTurbulence.value = 0.25 * g.turbulence; + + this.motes.setGradient( + getColor(c.colorMoteA), + getColor(c.colorMoteB), + getColor(c.colorMoteC), + getColor(c.colorMoteD) + ); + this.motes.uniforms.uGravity.value.set(0, c.moteRise, 0); + this.motes.uniforms.uSizeScale.value = c.moteSize * g.particleSize * 7; + this.motes.uniforms.uLifeScale.value = c.moteLifetime * 0.5 * g.particleLifetime; + this.motes.uniforms.uSpeedScale.value = g.particleSpeed; + this.motes.uniforms.uOpacity.value = g.opacity; + this.motes.uniforms.uGlow.value = 1.1 * g.glow; + this.motes.uniforms.uTurbulence.value = c.moteTurbulence * g.turbulence; + + this.smoke.setGradient( + getColor(c.colorSmokeA), + getColor(c.colorSmokeB), + getColor(c.colorSmokeC), + getColor(c.colorSmokeD) + ); + this.smoke.uniforms.uGravity.value.set(0, c.smokeRise, 0); + this.smoke.uniforms.uSizeScale.value = c.smokeSize * g.particleSize; + this.smoke.uniforms.uLifeScale.value = c.smokeLifetime * 0.5 * g.particleLifetime; + this.smoke.uniforms.uSpeedScale.value = c.smokeSpeed * g.particleSpeed; + this.smoke.uniforms.uOpacity.value = c.smokeOpacity * g.opacity; + this.smoke.uniforms.uTurbulence.value = 0.4 * g.turbulence; + + this.debris.setGradient( + getColor(c.colorDebrisA), + getColor(c.colorDebrisB), + getColor(c.colorDebrisC), + getColor(c.colorDebrisD) + ); + this.debris.uniforms.uGravity.value.set(0, c.debrisGravity, 0); + this.debris.uniforms.uSizeScale.value = c.debrisSize * g.particleSize * 7; + this.debris.uniforms.uLifeScale.value = g.particleLifetime; + this.debris.uniforms.uSpeedScale.value = g.particleSpeed; + this.debris.uniforms.uOpacity.value = g.opacity; + } + + /** + * The wind-up: light pulled *inward* out of the air and swallowed by the orb. + * + * This is the same mote system the column sheds later, thrown the other way — + * emitted on a shell around the hands with its velocity pointing at them. The + * batches are small and their directions independent, because a single + * direction per frame reads as a stream rather than as an intake. + */ + _intakeFx(dt) { + const c = settings.beam; + const g = settings.global; + const charge = this.charge; + const time = frame.uTime.value; + + let count = Math.round(this.intakeEmitter.tick(dt, c.intakeRate * (0.35 + charge)) * g.particleCount); + if (count <= 0) return; + + this._handPoint(_target); + _emit.radius = 0.12; + _emit.speedVariance = 0.35; + _emit.spread = 0.12; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.09; + _emit.sizeVariance = 0.6; + _emit.lifeVariance = 0.3; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + // The shell tightens as the orb fills, so the intake visibly closes in. + const shell = c.intakeRadius * lerp(1, 0.45, charge); + const per = Math.max(1, Math.round(count / 6)); + + while (count > 0) { + const theta = Math.random() * TAU; + const phi = Math.acos(randRange(-1, 1)); + const s = Math.sin(phi); + _dir.set(s * Math.cos(theta), Math.cos(phi) * 0.7, s * Math.sin(theta)).normalize(); + + _pos.copy(_target).addScaledVector(_dir, shell * randRange(0.7, 1.15)); + _emit.position = _pos; + _emit.direction = _dir.multiplyScalar(-1); + _emit.speed = c.intakeSpeed; + _emit.life = (shell / Math.max(0.5, c.intakeSpeed)) * 1.5; + this.motes.emit(Math.min(per, count), _emit); + count -= per; + } + } + + /** The release: the orb lets go and the column is thrown out of it. */ + _releaseFx() { + const c = settings.beam; + const g = settings.global; + + this._handPoint(_pos); + + // A pressure shell rather than a fireball — nothing burns here, the air is + // simply shoved out of the way. + this.ctx.bursts.spawn(BurstMode.AIR, _pos, { + radius: c.muzzleSize * 0.2, + endRadius: c.muzzleSize * g.explosionIntensity, + life: 0.4, + intensity: c.muzzleIntensity, + opacity: 0.9, + fresnel: 2.0, + displace: 0.35, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstC) + }); + + // ... and the same shove across the floor at the caster's feet. + this.ctx.decals.spawn(DecalType.SHOCKWAVE, this.origin, { + radius: c.muzzleSize * 2.2 * g.explosionIntensity, + life: 0.5, + width: 0.06, + intensity: 0.9, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + _emit.position = _pos; + _emit.radius = 0.22; + _emit.direction = _dir.copy(this.direction); + _emit.speed = c.sparkSpeed * 1.8; + _emit.speedVariance = 0.8; + _emit.spread = 0.7; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.2; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = frame.uTime.value; + this.sparks.emit(Math.round(70 * g.particleCount), _emit); + + this.ctx.flash.trigger(getColor(c.colorCastFlash), c.castFlash * g.explosionIntensity); + this.ctx.shake.add( + c.impactShake * 0.45 * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 30 + ); + this.lightBoost = c.lightIntensity * 0.7 * g.explosionIntensity; + } + + /** + * What the column sheds along its length. + * @param {number} scale 0..1 — thinned out as the beam collapses + */ + _columnFx(dt, scale) { + const c = settings.beam; + const g = settings.global; + const time = frame.uTime.value; + // Only the drawn part of the column is allowed to throw anything. + const reach = this.phase === AbilityPhase.TRAVEL ? Math.max(0.02, this.u) : 1; + + let sparkCount = Math.round(this.sparkEmitter.tick(dt, c.sparkRate * scale) * g.particleCount); + if (sparkCount > 0) { + _emit.speed = c.sparkSpeed; + _emit.speedVariance = 0.8; + _emit.spread = 0.35; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.15; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.55; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + const per = Math.ceil(sparkCount / Math.min(sparkCount, SPARK_BATCHES)); + while (sparkCount > 0) { + const s = randRange(0.04, 1) * reach; + const radius = this._beamRadius(s); + this._radialAt(Math.random() * TAU, _radial); + this._axisPoint(s, _pos).addScaledVector(_radial, radius * randRange(0.8, 1.25)); + + _emit.position = _pos; + _emit.radius = radius * 0.3; + // Thrown off the surface, then dragged downrange by the flow. This is + // the read that says "pressure", and it is the one thing the sparks + // must not share with the bolt's, which fall. + _emit.direction = _dir + .copy(_radial) + .addScaledVector(this.direction, c.sparkForward) + .normalize(); + this.sparks.emit(Math.min(per, sparkCount), _emit); + sparkCount -= per; + } + } + + const moteCount = Math.round(this.moteEmitter.tick(dt, c.moteRate * scale) * g.particleCount); + if (moteCount > 0) { + const s = Math.random() * reach; + this._axisPoint(s, _pos); + _emit.position = _pos; + _emit.radius = this._beamRadius(s) * 2.2; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.5).setY(0.7).normalize(); + _emit.speed = c.moteSpeed; + _emit.speedVariance = 0.8; + _emit.spread = 0.9; + _emit.size = 0.09; + _emit.sizeVariance = 0.6; + _emit.life = c.moteLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.time = time; + this.motes.emit(moteCount, _emit); + } + + const smokeCount = Math.round(this.smokeEmitter.tick(dt, c.smokeRate * scale) * g.particleCount); + if (smokeCount > 0) { + // Steam comes off the *floor*, not the column, and is blown out sideways + // from under it rather than lifted. + const s = Math.random() * reach; + this.pointAt(s, _pos).setY(0.12); + this._radialAt(randRange(0, TAU), _radial).setY(Math.abs(_radial.y) * 0.35); + _emit.position = _pos; + _emit.radius = c.scorchRadius * 1.6; + _emit.direction = _dir.copy(_radial).normalize(); + _emit.speed = c.smokeSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.55; + _emit.size = 0.9; + _emit.sizeVariance = 0.5; + _emit.life = c.smokeLifetime; + _emit.lifeVariance = 0.4; + _emit.spin = 0.4; + _emit.time = time; + this.smoke.emit(smokeCount, _emit); + } + + const debrisCount = Math.round(this.debrisEmitter.tick(dt, c.debrisRate * scale) * g.particleCount); + if (debrisCount > 0) { + this.pointAt(Math.random() * reach, _pos).setY(0.06); + _emit.position = _pos; + _emit.radius = c.scorchRadius * 1.4; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.35).setY(1).normalize(); + _emit.speed = c.debrisSpeed; + _emit.speedVariance = 0.75; + _emit.spread = 0.8; + _emit.size = 0.1; + _emit.sizeVariance = 0.7; + _emit.life = c.debrisLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 9; + _emit.time = time; + this.debris.emit(debrisCount, _emit); + } + } + + /** Burns laid on the floor as the leading edge passes over it. */ + _groundFx() { + const c = settings.beam; + const step = 1 / Math.max(0.05, c.scorchRate); + + while (this.front - this._burnDistance >= step) { + this._burnDistance += step; + const s = saturate(this._burnDistance / this.length); + this.pointAt(s, _pos); + // Jittered off the axis so the line does not read as a row of stamps. + const wander = this._beamRadius(s) * 0.5; + _pos.x += this.side.x * randRange(-wander, wander); + _pos.z += this.side.z * randRange(-wander, wander); + + this.ctx.decals.spawn(DecalType.SCORCH, _pos, { + radius: c.scorchRadius * randRange(0.8, 1.3), + life: c.scorchLife, + intensity: c.scorchIntensity, + colorA: getColor(c.colorScorch), + colorB: getColor(c.colorEmber), + height: 0.015 + }); + } + } + + /** + * What the far end does while the beam stands on it. + * + * This is the half of the ability the other three do not have: an impact that + * keeps happening. Sparks are thrown *back* up the beam, pressure shells are + * shed off the burning point, and dust and shockwave rings are pushed out + * across the floor — all rate-throttled, so they are frame-rate independent + * and every rate is a live slider. + * + * @param {number} scale 0..1 — thinned out as the beam collapses + */ + _burnFx(dt, scale) { + const c = settings.beam; + const g = settings.global; + const time = frame.uTime.value; + + this._impactPoint(_pos); + this.pointAt(1, _target); + + const splash = Math.round(this.splashEmitter.tick(dt, c.splashRate * scale) * g.particleCount); + if (splash > 0) { + _emit.position = _pos; + _emit.radius = this._beamRadius(1) * 0.7; + // Back up the line and upward: the wash coming off something being + // drilled, not an explosion leaving it. + _emit.direction = _dir.copy(this.direction).multiplyScalar(-0.65).setY(0.75).normalize(); + _emit.speed = c.sparkSpeed * 1.4; + _emit.speedVariance = 0.9; + _emit.spread = 0.95; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.18; + _emit.sizeVariance = 0.8; + _emit.life = c.sparkLifetime * 1.2; + _emit.lifeVariance = 0.6; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.sparks.emit(splash, _emit); + } + + if (this.pulseEmitter.tick(dt, c.pulseRate * scale) > 0) { + this.ctx.bursts.spawn(BurstMode.AIR, _pos, { + radius: c.pulseSize * 0.25, + endRadius: c.pulseSize * g.explosionIntensity, + life: 0.45, + intensity: c.pulseIntensity, + opacity: 0.8, + fresnel: 2.2, + displace: 0.3, + squash: 0.85, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstC) + }); + this.lightBoost = Math.max(this.lightBoost, c.lightIntensity * 0.2 * g.explosionIntensity); + } + + if (this.dustEmitter.tick(dt, c.dustRate * scale) > 0) { + this.ctx.decals.spawn(DecalType.DUSTRING, _target, { + radius: c.dustRadius * randRange(0.75, 1.15), + life: c.dustLife, + intensity: 0.7, + colorA: getColor(c.colorDustA), + colorB: getColor(c.colorDustB), + height: 0.02 + }); + } + + if (this.shockEmitter.tick(dt, c.shockRate * scale) > 0) { + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _target, { + radius: c.shockRadius * 0.55 * g.explosionIntensity, + life: 0.45, + width: 0.04, + intensity: 0.8, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + } + } + + /* ------------------------------------------------------------------ */ + /* Phases */ + /* ------------------------------------------------------------------ */ + + onTravel(dt) { + const c = settings.beam; + const g = settings.global; + + this._syncUniforms(dt, 1, 1); + + if (this.age < c.charge) { + // Still winding up. The camera frames the caster, not the empty line. + this._handPoint(this.position); + this._intakeFx(dt); + this.ctx.shake.rumble(c.chargeShake * this.charge * g.cameraShake, dt); + return; + } + + if (!this._fired) { + this._fired = true; + this._releaseFx(); + } + + // The light rides the leading edge, not the floor under it. `advance()` has + // already put `position` on the ground line, so lift it onto the axis. + this._axisPoint(this.u, this.position); + + this._columnFx(dt, 1); + this._groundFx(); + this.ctx.shake.rumble(c.rumble * g.cameraShake, dt); + } + + onImpact() { + const c = settings.beam; + const g = settings.global; + const time = frame.uTime.value; + + this._impactPoint(_pos); + this.pointAt(1, _target); + + /* the shell of compressed air where it grounded out */ + this.ctx.bursts.spawn(BurstMode.STORM, _pos, { + radius: c.burstSize * 0.18, + endRadius: c.burstSize * g.explosionIntensity, + life: 0.75, + intensity: c.burstIntensity, + opacity: 0.9, + fresnel: 1.8, + displace: 0.5, + squash: 0.85, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + /* the ring that snaps outward across the floor */ + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _target, { + radius: c.shockRadius * g.explosionIntensity, + life: 0.65, + width: 0.05, + intensity: 1.0, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + /* a wide scorch under the burning point */ + this.ctx.decals.spawn(DecalType.SCORCH, _target, { + radius: c.scorchRadius * 3.2, + life: c.scorchLife * 1.4, + intensity: c.scorchIntensity * 1.4, + colorA: getColor(c.colorScorch), + colorB: getColor(c.colorEmber), + height: 0.015 + }); + this.ctx.decals.spawn(DecalType.DUSTRING, _target, { + radius: c.dustRadius * 1.8, + life: c.dustLife * 1.6, + intensity: 0.85, + colorA: getColor(c.colorDustA), + colorB: getColor(c.colorDustB), + height: 0.02 + }); + + /* sparks and chips blown out of the strike */ + _emit.position = _pos; + _emit.radius = 0.35; + _emit.direction = _dir.copy(this.direction).multiplyScalar(-0.35).setY(0.85).normalize(); + _emit.speed = c.sparkSpeed * 2.4; + _emit.speedVariance = 0.85; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.24; + _emit.sizeVariance = 0.8; + _emit.life = c.sparkLifetime * 1.6; + _emit.lifeVariance = 0.6; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.sparks.emit(Math.round(c.burstSparks * g.particleCount), _emit); + + _emit.position = _target; + _emit.radius = c.scorchRadius * 2.2; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.debrisSpeed * 1.9; + _emit.spread = 0.85; + _emit.size = 0.14; + _emit.life = c.debrisLifetime * 1.3; + _emit.spin = 11; + this.debris.emit(Math.round(c.burstDebris * g.particleCount), _emit); + + _emit.speed = c.smokeSpeed * 2.4; + _emit.spread = 1.0; + _emit.size = 1.4; + _emit.life = c.smokeLifetime * 1.2; + _emit.spin = 0.5; + this.smoke.emit(Math.round(55 * g.particleCount), _emit); + + this.ctx.shake.add( + c.impactShake * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 24 + ); + this.ctx.flash.trigger(getColor(c.colorFlash), c.impactFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 1.2 * g.explosionIntensity; + } + + onFade(dt, t) { + const c = settings.beam; + const g = settings.global; + + // `t` runs 0..1 while the beam burns, then 1..2 while it collapses. + let fade = 1; + let widthFade = 1; + if (t > 1) { + const k = saturate(t - 1); + // Brightness hangs on and then goes; the *width* pinches early. Together + // they read as the column snapping back to a thread and blinking out, + // rather than as a fat beam dimming. + fade = 1 - Easing.inCubic(k); + widthFade = Math.max(0.04, 1 - Easing.outCubic(k) * 0.94); + } + + this._syncUniforms(dt, fade, widthFade); + + // The light stays on the burning point once the front has arrived. + this._axisPoint(1, this.position); + + const scale = t <= 1 ? 1 : fade * 0.5; + this._columnFx(dt, scale * 0.85); + this._burnFx(dt, scale); + + if (t <= 1) this.ctx.shake.rumble(c.burnShake * g.cameraShake, dt); + } + + onDestroy() { + this.ctx.lights.release(this.muzzleLight); + this.muzzleLight = null; + this._coilCount = 1; + this._ringCount = 1; + this.coilGeometry.instanceCount = 1; + this.ringGeometry.instanceCount = 1; + for (const material of this.materials) { + material.uniforms.uFade.value = 0; + material.uniforms.uCharge.value = 0; + } + } + + dispose() { + this.tubeGeometry.dispose(); + this.coilGeometry.dispose(); + this.ringGeometry.dispose(); + this.orbGeometry.dispose(); + for (const material of this.materials) material.dispose(); + super.dispose(); + } +} diff --git a/src/skillshot/abilities/GlacierAbility.js b/src/skillshot/abilities/GlacierAbility.js new file mode 100644 index 0000000..06bb142 --- /dev/null +++ b/src/skillshot/abilities/GlacierAbility.js @@ -0,0 +1,1319 @@ +import { + InstancedMesh, + InstancedBufferAttribute, + Mesh, + PlaneGeometry, + CylinderGeometry, + Object3D, + Vector3, + Quaternion +} from 'three'; +import { Ability, AbilityPhase } from './Ability.js'; +import { createGlacierMaterial } from '../materials/GlacierMaterial.js'; +import { createFrostFieldMaterial, createFrostVeilMaterial } from '../materials/FrostFieldMaterial.js'; +import { createCrystalGeometry } from '../ProceduralGeometry.js'; +import { ParticleShape } from '../../particles/ParticleSystem.js'; +import { RateEmitter } from '../../particles/ParticleEngine.js'; +import { DecalType } from '../../effects/GroundDecals.js'; +import { BurstMode } from '../../effects/BurstSphere.js'; +import { LAYER } from '../../core/Layers.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, lerp, Easing, randRange } from '../../utils/math.js'; + +/** Hard ceiling on shards per cast. The editor's count slider clamps to this. */ +const MAX_SPIKES = 320; +/** + * Distinct crystal shapes in the crown. Each is its own InstancedMesh — three + * draw calls buys real silhouette variety that per-instance scaling alone + * cannot, because the *facets* differ, not just the proportions. + */ +const VARIANTS = 3; +const SLOTS = Math.ceil(MAX_SPIKES / VARIANTS); +const TAU = Math.PI * 2; + +/** + * What a shard is for. The role decides where it stands, how tall it is, which + * way it leans and — the part that carries the whole cast — *when* it goes up. + */ +const Role = Object.freeze({ + RING: 0, // the wall of blades on the boundary + SKIRT: 1, // the broken shards banked against its foot, inside and out + CORE: 2 // the spire in the middle — off by default, the middle stays clear +}); + +const _emit = {}; +const _pos = new Vector3(); +const _dir = new Vector3(); +const _lean = new Vector3(); +const _axis = new Vector3(); +const _up = new Vector3(0, 1, 0); +const _dummy = new Object3D(); +const _spin = new Quaternion(); +const _tilt = new Quaternion(); + +/** Shortest signed angle from `b` to `a`, radians, -π..π. */ +function angleDelta(a, b) { + const d = a - b; + return Math.atan2(Math.sin(d), Math.cos(d)); +} + +/** + * GLACIAL CROWN — the second **far cast**, and the one that comes out of the + * floor. Reference for the look: `Hud7Xfg3LH.jpg`. + * + * The Voltaic Snare answers a circle with a cage of current standing *in* the + * air. This one answers it with geometry: a cold front races along the floor to + * the aimed point, the disc freezes outward to the boundary the indicator drew, + * and a wall of crystal tears up out of the ground *around* it — a ring of + * blades leaning outward with a skirt of wreckage banked against their feet — + * which stands, glints, breathes cold off its rim, and then shatters plate by + * plate and sinks back into the floor. + * + * The **middle stays open**. Every shard is seated in a band about `zoneRadius` + * and nothing is planted in the centre of the footprint, because the read of the + * ability is a wall you are looking *into*: fill the disc and the ring stops + * being a ring. What lives inside it is air — snow falling through it, glitter + * lifting off the sheet — and the frozen ground under that. The spire is kept as + * a control (`coreShare`) but ships at zero. + * + * Four beats, though only three phases: + * + * 1. **travel** — the front runs out across the floor, laying rime as it goes. + * 2. **bloom** — the first `snapTime` of the impact phase: the sheet freezes + * outward, and the ring erupts as a *sweep*. The blade nearest the caster + * goes first and the wave runs around both sides to meet at the far side, + * so the crown closes rather than appearing, with the skirt banking up + * against its feet behind the wave. + * 3. **hold** — the rest of `lifetime`: the ice glints, a curtain of cold air + * spills off the rim, snow drifts down through it, rime creeps around the + * boundary and late shards keep pushing up through the sheet. + * 4. **collapse** — the shards break into plates from the inside out, the + * wreckage sinks, and the sheet thaws back toward the middle. + * + * Everything is generated. The shards are procedural crystals shaded by + * `materials/GlacierMaterial.js` (which owns the freeze front and the shatter), + * the sheet and the curtain are two shaders in `materials/FrostFieldMaterial.js` + * and the mist, chips, glitter and snow are GPU particles. + * + * **The rule that makes the editor work.** A shard record holds nothing but + * dice — a role, a bearing, a unitless radial fraction and a handful of jitters. + * Not one metre, radian or second is captured: the seat of the ring, the height + * of a blade, the lean, the sheet and the curtain are all resolved against + * `settings.glacier` inside the update loop, which runs on a zero-length frame + * too. Dragging `zoneRadius` re-scales a crown that is already standing, with + * the clock stopped. + * + * The only values a record *does* capture are timestamps — the moment its own + * eruption was triggered. Those are events, not dimensions. + */ +export class GlacierAbility extends Ability { + constructor(context) { + super('glacier', context); + } + + /* ------------------------------------------------------------------ */ + /* Construction */ + /* ------------------------------------------------------------------ */ + + createShaders() { + const environment = this.ctx.environment; + + this.material = createGlacierMaterial(environment); + + /** Signature of the geometry controls, so a rebuild only happens on a change. */ + this._shapeKey = ''; + + this.meshes = []; + this.seedAttributes = []; + this.birthAttributes = []; + this.growAttributes = []; + this.shatterAttributes = []; + + for (let v = 0; v < VARIANTS; v++) { + const geometry = this._buildGeometry(v); + + const seeds = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + const births = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + const grows = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + const shatters = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + for (let i = 0; i < SLOTS; i++) seeds.array[i] = Math.random() * 10; + geometry.setAttribute('aSeed', seeds); + geometry.setAttribute('aBirth', births); + geometry.setAttribute('aGrow', grows); + geometry.setAttribute('aShatter', shatters); + + const mesh = new InstancedMesh(geometry, this.material, SLOTS); + mesh.castShadow = true; + mesh.receiveShadow = true; + mesh.frustumCulled = false; + mesh.count = 0; + // Solid world geometry: it belongs in the depth prepass so the mist, the + // snow and the curtain all fade softly where they intersect it. + mesh.layers.set(LAYER.WORLD); + mesh.renderOrder = 2; + this.group.add(mesh); + + this.meshes.push(mesh); + this.seedAttributes.push(seeds); + this.birthAttributes.push(births); + this.growAttributes.push(grows); + this.shatterAttributes.push(shatters); + } + + /* ---- the sheet of ice on the floor ---- */ + this.fieldGeometry = new PlaneGeometry(1, 1, 1, 1).rotateX(-Math.PI / 2); + this.fieldMaterial = createFrostFieldMaterial(); + this.field = new Mesh(this.fieldGeometry, this.fieldMaterial); + this.field.name = 'FrostField'; + this.field.layers.set(LAYER.VFX); + this.field.renderOrder = 7; // over the decals, under the crystals + this.field.frustumCulled = false; + this.field.visible = false; + this.group.add(this.field); + + /* ---- the curtain of cold air standing on the boundary ---- */ + // Open-ended unit cylinder: radius 1, height 1 about the origin, so placing + // it is a scale and a lift. The flare and the billow happen in the shader. + this.veilGeometry = new CylinderGeometry(1, 1, 1, 72, 12, true); + this.veilMaterial = createFrostVeilMaterial(); + this.veil = new Mesh(this.veilGeometry, this.veilMaterial); + this.veil.name = 'FrostVeil'; + this.veil.layers.set(LAYER.VFX); + this.veil.renderOrder = 9; + this.veil.frustumCulled = false; + this.veil.visible = false; + this.group.add(this.veil); + + /** + * Fixed-size record pool — a cast allocates nothing. + * + * See the class comment: dice only, no dimensions. + */ + this.records = []; + for (let i = 0; i < MAX_SPIKES; i++) { + this.records.push({ + role: Role.SKIRT, + angle: 0, // bearing about the centre, radians + radial: 0, // RING: -1..1 seat jitter. SKIRT: 0..1 across the band. CORE: 0..1 out + late: false, // held back to push up during the hold + rubble: false, // demoted to ankle-height wreckage + heightJitter: 0, + radiusJitter: 0, + leanJitter: 0, + fanJitter: 0, // -1..1 splay off its own radius + yaw: 0, + stagger: 0, // 0..1 of the per-role scatter + eruptTime: -1, // absolute age it was triggered at, or -1 + breached: false, + crumbled: false + }); + } + + this._activeCount = 0; + this._drawn = 0; + /** Re-rolled per cast so no two crowns draw the same ring. */ + this._seed = 0; + /** Seconds since the crown started opening. Drives the bloom, nothing else. */ + this._openTime = 0; + /** Bearing the front arrived on — where the sweep starts. */ + this._entryAngle = 0; + this._frostDistance = 0; + + // Scratch state handed to the two shaders each frame. One object apiece, + // reused — syncing a standing crown allocates nothing. + this._state = { centre: new Vector3() }; + this._fieldState = { radius: 1, quadSize: 1, freeze: 0, fade: 1, seed: 0 }; + this._veilState = { fade: 1, seed: 0 }; + } + + /** One crystal shape. Variant index only perturbs the seed. */ + _buildGeometry(variant) { + const c = settings.glacier; + return createCrystalGeometry({ + seed: 3.9 + variant * 17.3, + sides: c.facets, + taper: c.taper, + roughness: c.roughness, + bend: c.bend + }); + } + + /** + * Regenerate the crystal meshes when a *shape* control moves. + * + * Facet count, taper, roughness and bend cannot be expressed as a per-instance + * transform, so they are baked into the geometry — and a six-sided crystal is + * 108 triangles, cheap enough to simply rebuild rather than approximate in a + * vertex shader. That is what keeps them live sliders. + */ + _syncGeometry() { + const c = settings.glacier; + const key = `${Math.round(c.facets)}|${c.taper.toFixed(3)}|${c.roughness.toFixed(3)}|${c.bend.toFixed(3)}`; + if (key === this._shapeKey) return; + this._shapeKey = key; + + for (let v = 0; v < VARIANTS; v++) { + const mesh = this.meshes[v]; + const previous = mesh.geometry; + const geometry = this._buildGeometry(v); + // The per-instance attributes are state, not shape — carry them over. + geometry.setAttribute('aSeed', this.seedAttributes[v]); + geometry.setAttribute('aBirth', this.birthAttributes[v]); + geometry.setAttribute('aGrow', this.growAttributes[v]); + geometry.setAttribute('aShatter', this.shatterAttributes[v]); + mesh.geometry = geometry; + previous.dispose(); + } + } + + createParticles() { + const particles = this.ctx.particles; + + // Cold air rolling off the ice. Non-additive: it has to *occlude*, which is + // what gives the crown depth from the outside. + this.mist = particles.get('glacier.mist', { + capacity: 3600, + shape: ParticleShape.SMOKE, + additive: false, + curl: true, + softFade: 1.2 + }); + this.mist.uniforms.uDrag.value = 2.1; + this.mist.uniforms.uEndSize.value = 3.6; + this.mist.uniforms.uSizeIn.value = 0.1; + this.mist.uniforms.uFadeIn.value = 0.14; + this.mist.uniforms.uFadeOut.value = 0.28; + + // Chips torn off the shards as they punch through the floor, and again as + // they break up. + this.shards = particles.get('glacier.shards', { + capacity: 3000, + shape: ParticleShape.CHIP, + additive: false, + lit: true, + softFade: 0.25 + }); + this.shards.uniforms.uDrag.value = 0.2; + this.shards.uniforms.uEndSize.value = 0.8; + this.shards.uniforms.uFadeOut.value = 0.74; + + // The glitter lifting off the field — the same read as the Frost Lance's + // plume, kept inside the ring here. + this.glitter = particles.get('glacier.glitter', { + capacity: 3000, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.4 + }); + this.glitter.uniforms.uDrag.value = 1.1; + this.glitter.uniforms.uEndSize.value = 0.18; + this.glitter.uniforms.uSizeIn.value = 0.05; + this.glitter.uniforms.uFadeIn.value = 0.06; + this.glitter.uniforms.uFadeOut.value = 0.35; + + // This ability's signature system: ice dust falling *through* the crown from + // above it. Everything else in the project is thrown upward, and a slow fall + // inside the ring is what says the air over it is freezing rather than + // burning. + this.snow = particles.get('glacier.snow', { + capacity: 2600, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.5 + }); + this.snow.uniforms.uDrag.value = 1.6; + this.snow.uniforms.uEndSize.value = 0.5; + this.snow.uniforms.uSizeIn.value = 0.08; + this.snow.uniforms.uFadeIn.value = 0.12; + this.snow.uniforms.uFadeOut.value = 0.5; + + this.mistEmitter = new RateEmitter(); + this.glitterEmitter = new RateEmitter(); + this.snowEmitter = new RateEmitter(); + this.rimeEmitter = new RateEmitter(); + this.vapourEmitter = new RateEmitter(); + this.ringEmitter = new RateEmitter(); + } + + /* ------------------------------------------------------------------ */ + /* Timing */ + /* ------------------------------------------------------------------ */ + + get instanceCount() { + return this._drawn; + } + + /** The crown blooms, then stands. */ + get impactDuration() { + return Math.max(0.2, settings.glacier.lifetime * settings.global.lifetime); + } + + /** The collapse: a stagger, then the break-up and the sink. */ + get fadeDuration() { + const c = settings.glacier; + return Math.max(0.2, c.shatterDelay + c.shatterStagger + c.sinkTime); + } + + /** The live footprint, metres. What the indicator measured out. */ + get radius() { + return Math.max(0.05, settings.glacier.zoneRadius); + } + + /* ------------------------------------------------------------------ */ + /* Geometry — every metre resolved from live settings */ + /* ------------------------------------------------------------------ */ + + /** The centre of the crown — the far end of the aimed line. */ + _centrePoint(out) { + return this.pointAt(1, out).setY(0); + } + + /** Where the front leaves the caster, in world space. */ + _handPoint(out) { + const c = settings.glacier; + out + .copy(this.origin) + .addScaledVector(this.direction, c.handForward) + .addScaledVector(this.side, c.handSide); + out.y = c.handHeight; + return out; + } + + /** + * How far the sheet has spread, 0..1 of the boundary. + * + * A pure function of `_openTime` against the live `snapTime`, so the bloom + * re-times itself if the slider moves mid-cast. + */ + _openAmount() { + const snap = Math.max(0.02, settings.glacier.snapTime); + return Easing.outCubic(saturate(this._openTime / snap)); + } + + /** How far the thaw has eaten back into the sheet, 0..1. */ + _thawAmount() { + if (this.phase !== AbilityPhase.FADE) return 0; + const c = settings.glacier; + const hold = c.shatterDelay * 0.35; + return saturate((this.fadeTime - hold) / Math.max(0.05, this.fadeDuration - hold)); + } + + /** Where a shard stands, at the live footprint settings. */ + _spikePosition(record, c, out) { + const centre = this._state.centre; + const R = this.radius; + let r; + + if (record.role === Role.RING) { + r = R * (c.ringSeat + record.radial * c.ringScatter); + } else if (record.role === Role.CORE) { + r = R * c.coreSpread * record.radial; + } else { + // A band around the wall, not a disc: the middle of the crown is meant to + // stay open, so the wreckage banks up against the ring on both sides of it + // rather than filling the footprint. + r = R * (c.skirtSeat + Math.pow(record.radial, c.skirtBias) * c.skirtBand); + } + + return out.set(centre.x + Math.cos(record.angle) * r, 0, centre.z + Math.sin(record.angle) * r); + } + + /** + * Full height of a shard, metres. + * + * The ring's height is modulated by two harmonics of its own bearing, seeded + * per cast: a wall of blades all the same height reads as a fence, and the + * uneven crest is most of what makes the silhouette look grown rather than + * placed. + */ + _spikeHeight(record, c, g) { + let h; + + if (record.role === Role.RING) { + const wave = + Math.sin(record.angle * 3 + this._seed) * 0.62 + + Math.sin(record.angle * 5 - this._seed * 2.1) * 0.38; + h = c.ringHeight * (1 + c.ringWave * wave); + } else if (record.role === Role.CORE) { + h = c.coreHeight * lerp(1, 0.55, record.radial); + } else { + // Tallest where it meets the wall and tapering away on both sides, so the + // skirt reads as ice heaved up by the ring rather than as a second one. + h = c.skirtHeight * lerp(0.55, 1.2, 1 - Math.abs(record.radial - 0.5) * 2); + } + + h *= 1 + record.heightJitter * c.heightJitter * g.randomness; + if (record.rubble) h *= c.rubbleScale; + + return Math.max(0.02, h); + } + + /** Base radius of a shard, metres. */ + _spikeRadius(record, c, g) { + const role = record.role === Role.RING ? 1 : record.role === Role.CORE ? 1.35 : 0.85; + const jitter = 1 + record.radiusJitter * c.radiusJitter * g.randomness; + return Math.max(0.01, c.radius * role * jitter * (record.rubble ? 1.3 : 1)); + } + + /** How far a shard leans away from the middle, radians. */ + _spikeLean(record, c, g) { + const base = + record.role === Role.RING ? c.ringLean : record.role === Role.CORE ? c.coreLean : c.skirtLean; + return base * (1 + record.leanJitter * c.leanJitter * g.randomness); + } + + /* ------------------------------------------------------------------ */ + /* Casting */ + /* ------------------------------------------------------------------ */ + + onSpawn() { + const c = settings.glacier; + + this.mistEmitter.reset(); + this.glitterEmitter.reset(); + this.snowEmitter.reset(); + this.rimeEmitter.reset(); + this.vapourEmitter.reset(); + this.ringEmitter.reset(); + + this._frostDistance = 0; + this._openTime = 0; + // The one thing a cast captures, besides the timestamps below. + this._seed = Math.random() * 100; + // The sweep starts on the near side: the bearing from the centre back toward + // the caster, which is the bearing the front arrives on. + this._entryAngle = Math.atan2(-this.direction.z, -this.direction.x); + + const wanted = Math.min(MAX_SPIKES, Math.max(1, Math.round(c.spikeCount * c.density))); + const ringCount = Math.round(wanted * saturate(c.ringShare)); + const coreCount = Math.round(wanted * saturate(c.coreShare)); + const lateCount = Math.round(wanted * saturate(c.lateShare)); + + this._activeCount = wanted; + + for (let i = 0; i < wanted; i++) { + const record = this.records[i]; + + record.eruptTime = -1; + record.breached = false; + record.crumbled = false; + record.yaw = Math.random() * TAU; + record.stagger = Math.random(); + record.heightJitter = randRange(-1, 1); + record.radiusJitter = randRange(-1, 1); + record.leanJitter = randRange(-1, 1); + record.fanJitter = randRange(-1, 1); + + if (i < ringCount) { + record.role = Role.RING; + // Evenly stepped around the circle with a jittered stride, so the wall + // has no gaps but never looks stamped. + record.angle = ((i + randRange(-0.4, 0.4)) / Math.max(1, ringCount)) * TAU; + record.radial = randRange(-1, 1); + record.rubble = Math.random() < c.rubble * 0.35; + } else if (i < ringCount + coreCount) { + record.role = Role.CORE; + record.angle = Math.random() * TAU; + record.radial = Math.sqrt(Math.random()); + record.rubble = false; + } else { + record.role = Role.SKIRT; + record.angle = Math.random() * TAU; + // Flat across the band. The skirt is an annulus, not a disc, so the + // sqrt() that keeps a disc evenly dense would only bias it outward. + record.radial = Math.random(); + record.rubble = Math.random() < c.rubble; + } + + // Only the skirt is held back. Sharing the wall out over the hold + // would break the sweep, which is the read the whole bloom is built on. + record.late = record.role === Role.SKIRT && i >= wanted - lateCount; + } + + for (let i = wanted; i < MAX_SPIKES; i++) this.records[i].eruptTime = -1; + for (let v = 0; v < VARIANTS; v++) this.meshes[v].count = 0; + this._drawn = 0; + + this.field.visible = false; + this.veil.visible = false; + + this._sync(1); + this._muzzleFx(); + } + + /** + * Hand every shard the moment it goes up. + * + * Timestamps, not dimensions — the same allowance `IceAbility` takes. The + * shape of the wave is the whole bloom: the ring sweeps around from the near + * side, the skirt follows it outward, the spire (if any) comes up last, and + * late bloomers are scattered across the hold. + */ + _scheduleEruption() { + const c = settings.glacier; + const hold = Math.max(0.2, c.lifetime * settings.global.lifetime); + + for (let i = 0; i < this._activeCount; i++) { + const record = this.records[i]; + let delay; + + if (record.late) { + delay = hold * (0.12 + record.stagger * saturate(c.bloomSpread)); + } else if (record.role === Role.RING) { + // 0 on the near side, 1 at the far side — the two arms of the wave meet + // behind the crown. + const around = Math.abs(angleDelta(record.angle, this._entryAngle)) / Math.PI; + delay = around * c.sweepTime + record.stagger * c.stagger; + } else if (record.role === Role.CORE) { + delay = c.coreDelay + record.stagger * c.stagger; + } else { + delay = c.skirtDelay + record.radial * c.skirtWave + record.stagger * c.stagger; + } + + record.eruptTime = this.age + delay; + } + } + + /* ------------------------------------------------------------------ */ + /* The eruption */ + /* ------------------------------------------------------------------ */ + + /** + * How far out of the ground a shard is, 0 → 1 with a springy overshoot past 1. + * Returns a negative number while it is still buried and waiting. + */ + _emergence(record, c) { + if (record.eruptTime < 0) return -1; + const elapsed = this.age - record.eruptTime; + if (elapsed < 0) return -1; + + const riseTime = Math.max(0.02, c.riseTime); + const rise = Easing.outQuint(saturate(elapsed / riseTime)); + if (elapsed <= riseTime) return rise; + + // The punch-through carries past full height and settles back. + const after = elapsed - riseTime; + const spring = Math.sin(after * 14) * Math.exp(-after / Math.max(0.05, c.settle)); + return 1 + c.riseOvershoot * spring; + } + + /** How far the ice has crystallised up the shard's own axis, 0..1. */ + _growth(record, c) { + if (record.eruptTime < 0) return 0; + const elapsed = this.age - record.eruptTime; + // Finished just before the shard tops out, so the freeze front is still + // travelling while the body is still moving. + return saturate(elapsed / Math.max(0.02, c.riseTime * 0.85)); + } + + /** How far a shard has broken up, 0..1. Only ever non-zero in the collapse. */ + _shatterAmount(record, c) { + if (this.phase !== AbilityPhase.FADE) return 0; + const delay = c.shatterDelay + record.stagger * c.shatterStagger; + return saturate((this.fadeTime - delay) / Math.max(0.05, c.sinkTime)); + } + + /** + * Rebuild every instance matrix and per-instance attribute from the live + * settings. + */ + _updateSpikes() { + const c = settings.glacier; + const g = settings.global; + const birthFade = Math.max(0.02, c.birthFade); + const used = [0, 0, 0]; + + for (let i = 0; i < this._activeCount; i++) { + const record = this.records[i]; + const variant = i % VARIANTS; + const slot = (i / VARIANTS) | 0; + const emerge = this._emergence(record, c); + + if (emerge < 0) { + // Still buried. Park it outside the view rather than drawing a + // degenerate matrix at the origin. + _dummy.position.set(0, -999, 0); + _dummy.quaternion.identity(); + _dummy.scale.setScalar(0.0001); + _dummy.updateMatrix(); + this.meshes[variant].setMatrixAt(slot, _dummy.matrix); + this.birthAttributes[variant].array[slot] = 0; + this.growAttributes[variant].array[slot] = 0; + this.shatterAttributes[variant].array[slot] = 0; + used[variant] = Math.max(used[variant], slot + 1); + continue; + } + + const height = this._spikeHeight(record, c, g); + const radius = this._spikeRadius(record, c, g); + const shatter = this._shatterAmount(record, c); + + /* --- chips as it breaks the surface, and again as it breaks up --- */ + if (!record.breached && emerge > 0.22) { + record.breached = true; + this._breachFx(record, c, g, radius); + } + if (!record.crumbled && shatter > 0.12) { + record.crumbled = true; + this._crumbleFx(record, c, g, radius, height); + } + + /* --- lean: thrown outward, and fanned off its own radius --- */ + // The fan is what makes the wall a starburst instead of a picket line: + // neighbouring blades are splayed to either side of the radius they stand + // on, so they cross in front of each other as they go over. + const bearing = record.angle + record.fanJitter * c.fan; + _lean.set(Math.cos(bearing), 0, Math.sin(bearing)); + if (_lean.lengthSq() < 1e-6) _lean.copy(this.direction); + _lean.normalize(); + + // Rotating about (up × lean) tips the crystal's own +Y toward `lean`. + _axis.crossVectors(_up, _lean).normalize(); + _tilt.setFromAxisAngle(_axis, this._spikeLean(record, c, g)); + _spin.setFromAxisAngle(_up, record.yaw * c.twist); + _tilt.multiply(_spin); + + /* --- slide it up out of the floor, and drop it back in as it goes --- */ + const settled = Math.min(1, emerge); + this._spikePosition(record, c, _dummy.position); + _dummy.position.y = (emerge - 1) * height * 0.9; + if (shatter > 0) { + _dummy.position.y -= Easing.inCubic(shatter) * (height * 0.45 + radius + 0.3); + } + + _dummy.quaternion.copy(_tilt); + _dummy.scale.set(radius, height, radius).multiplyScalar(lerp(0.86, 1, settled)); + _dummy.updateMatrix(); + + this.meshes[variant].setMatrixAt(slot, _dummy.matrix); + this.birthAttributes[variant].array[slot] = saturate( + 1 - (this.age - record.eruptTime) / birthFade + ); + this.growAttributes[variant].array[slot] = this._growth(record, c); + this.shatterAttributes[variant].array[slot] = shatter; + used[variant] = Math.max(used[variant], slot + 1); + } + + this._drawn = 0; + for (let v = 0; v < VARIANTS; v++) { + this.meshes[v].count = used[v]; + this.meshes[v].instanceMatrix.needsUpdate = true; + this.birthAttributes[v].needsUpdate = true; + this.growAttributes[v].needsUpdate = true; + this.shatterAttributes[v].needsUpdate = true; + this._drawn += used[v]; + } + } + + /* ------------------------------------------------------------------ */ + /* Feedback */ + /* ------------------------------------------------------------------ */ + + /** + * Push the live settings and the current cast state into the crystal + * material, the sheet, the curtain and the four particle systems. + * + * @param {number} fade 1 while the crown stands, ramping to 0 as it goes + */ + _sync(fade) { + const c = settings.glacier; + const g = settings.global; + const travelling = this.phase === AbilityPhase.TRAVEL; + + this._centrePoint(this._state.centre); + this._syncGeometry(); + this.material.userData.sync(); + + const centre = this._state.centre; + const radius = this.radius; + const open = travelling ? 0 : this._openAmount(); + const thaw = this._thawAmount(); + const freeze = open * (1 - thaw); + + /* --- the sheet --- */ + const fieldState = this._fieldState; + fieldState.radius = radius; + fieldState.quadSize = (radius + c.fieldBoundary + 0.8) * 2; + fieldState.freeze = freeze; + fieldState.fade = fade; + fieldState.seed = this._seed; + this.fieldMaterial.userData.sync(fieldState); + + this.field.visible = !travelling && freeze > 0.002 && fade > 0.002; + this.field.position.set(centre.x, c.fieldHeight, centre.z); + this.field.scale.set(fieldState.quadSize, 1, fieldState.quadSize); + + /* --- the curtain --- */ + const veilHeight = Math.max(0.05, c.veilHeight * Easing.outCubic(open)); + const veilState = this._veilState; + veilState.fade = fade * (1 - thaw); + veilState.seed = this._seed; + this.veilMaterial.userData.sync(veilState); + + this.veil.visible = !travelling && c.veil > 0.001 && open > 0.02 && veilState.fade > 0.004; + this.veil.position.set(centre.x, veilHeight * 0.5, centre.z); + this.veil.scale.set(radius * c.veilRadius, veilHeight, radius * c.veilRadius); + this.veil.rotation.y = this._seed + this.age * c.veilSpin * TAU; + + /* --- the four particle systems --- */ + this.mist.setGradient( + getColor(c.colorMistA), + getColor(c.colorMistB), + getColor(c.colorMistC), + getColor(c.colorMistD) + ); + this.mist.uniforms.uGravity.value.set(0, c.mistRise, 0); + this.mist.uniforms.uSizeScale.value = c.mistSize * g.particleSize; + this.mist.uniforms.uLifeScale.value = c.mistLifetime * 0.5 * g.particleLifetime; + this.mist.uniforms.uSpeedScale.value = c.mistSpeed * g.particleSpeed; + this.mist.uniforms.uOpacity.value = c.mistOpacity * g.opacity; + this.mist.uniforms.uTurbulence.value = c.mistTurbulence * g.turbulence; + + this.shards.setGradient( + getColor(c.colorShardA), + getColor(c.colorShardB), + getColor(c.colorShardC), + getColor(c.colorShardD) + ); + this.shards.uniforms.uGravity.value.set(0, c.shardGravity, 0); + this.shards.uniforms.uSizeScale.value = c.shardSize * g.particleSize * 7; + this.shards.uniforms.uLifeScale.value = g.particleLifetime; + this.shards.uniforms.uSpeedScale.value = g.particleSpeed; + this.shards.uniforms.uOpacity.value = g.opacity; + + this.glitter.setGradient( + getColor(c.colorGlitterA), + getColor(c.colorGlitterB), + getColor(c.colorGlitterC), + getColor(c.colorGlitterD) + ); + this.glitter.uniforms.uGravity.value.set(0, c.glitterRise, 0); + this.glitter.uniforms.uSizeScale.value = c.glitterSize * g.particleSize * 7; + this.glitter.uniforms.uLifeScale.value = c.glitterLifetime * 0.5 * g.particleLifetime; + this.glitter.uniforms.uSpeedScale.value = g.particleSpeed; + this.glitter.uniforms.uOpacity.value = g.opacity; + this.glitter.uniforms.uGlow.value = c.glitterGlow * g.glow; + this.glitter.uniforms.uTurbulence.value = c.glitterTurbulence * g.turbulence; + + this.snow.setGradient( + getColor(c.colorSnowA), + getColor(c.colorSnowB), + getColor(c.colorSnowC), + getColor(c.colorSnowD) + ); + // Negative: this is the one system in the project that is falling. + this.snow.uniforms.uGravity.value.set(0, c.snowFall, 0); + this.snow.uniforms.uSizeScale.value = c.snowSize * g.particleSize * 7; + this.snow.uniforms.uLifeScale.value = c.snowLifetime * 0.5 * g.particleLifetime; + this.snow.uniforms.uSpeedScale.value = g.particleSpeed; + this.snow.uniforms.uOpacity.value = g.opacity; + this.snow.uniforms.uGlow.value = c.snowGlow * g.glow; + this.snow.uniforms.uTurbulence.value = c.snowTurbulence * g.turbulence; + } + + /** The puff at the caster's hand as the front leaves it. */ + _muzzleFx() { + const c = settings.glacier; + const g = settings.global; + + this._handPoint(_pos); + + this.ctx.bursts.spawn(BurstMode.FROST, _pos, { + radius: c.muzzleSize * 0.25, + endRadius: c.muzzleSize * g.explosionIntensity, + life: 0.45, + intensity: c.muzzleIntensity, + opacity: 0.75, + fresnel: 1.4, + displace: 0.5, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + _emit.position = _pos; + _emit.radius = 0.18; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.6).setY(0.4).normalize(); + _emit.speed = c.shardSpeed * 0.9; + _emit.speedVariance = 0.8; + _emit.spread = 0.85; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.12; + _emit.sizeVariance = 0.7; + _emit.life = c.shardLifetime * 0.8; + _emit.lifeVariance = 0.5; + _emit.spin = 7; + _emit.tint = null; + _emit.time = frame.uTime.value; + this.shards.emit(Math.round(20 * g.particleCount), _emit); + + _emit.speed = c.glitterSpeed * 0.8; + _emit.spread = 0.9; + _emit.size = 0.09; + _emit.life = c.glitterLifetime * 0.7; + _emit.spin = 0; + this.glitter.emit(Math.round(30 * g.particleCount), _emit); + + this.ctx.flash.trigger(getColor(c.colorCastFlash), c.castFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 0.5 * g.explosionIntensity; + } + + /** Fog, glitter and rime laid under the front while it races out. */ + _frontFx(dt) { + const c = settings.glacier; + const g = settings.global; + const time = frame.uTime.value; + + /* --- fog boiling off the freezing floor --- */ + const mistCount = Math.round(this.mistEmitter.tick(dt, c.mistRate * 0.5) * g.particleCount); + if (mistCount > 0) { + _emit.position = _pos.copy(this.position).setY(0.12); + _emit.radius = 0.5; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.mistSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.7; + _emit.sizeVariance = 0.5; + _emit.life = c.mistLifetime * 0.8; + _emit.lifeVariance = 0.4; + _emit.spin = 0.5; + _emit.tint = null; + _emit.time = time; + this.mist.emit(mistCount, _emit); + } + + /* --- glitter thrown up off the fracture --- */ + const glitterCount = Math.round( + this.glitterEmitter.tick(dt, c.glitterRate * 0.4) * g.particleCount + ); + if (glitterCount > 0) { + _emit.position = _pos.copy(this.position).setY(0.3); + _emit.radius = 0.4; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.glitterSpeed; + _emit.speedVariance = 0.8; + _emit.spread = 0.85; + _emit.size = 0.08; + _emit.sizeVariance = 0.6; + _emit.life = c.glitterLifetime * 0.8; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.time = time; + this.glitter.emit(glitterCount, _emit); + } + + /* --- rime laid on the floor as the front passes over it --- */ + const frostStep = 1 / Math.max(0.05, c.trailFrostRate); + while (this.front - this._frostDistance >= frostStep) { + this._frostDistance += frostStep; + const s = saturate(this._frostDistance / this.length); + this.pointAt(s, _pos); + _pos.x += this.side.x * randRange(-0.9, 0.9); + _pos.z += this.side.z * randRange(-0.9, 0.9); + + this.ctx.decals.spawn(DecalType.FROST, _pos, { + radius: c.trailFrostRadius * randRange(0.55, 1.1), + life: c.frostLife * 0.8, + width: c.frostCrystals, + intensity: c.frostIntensity, + colorA: getColor(c.colorFrost), + colorB: getColor(c.colorFrostEdge) + }); + } + } + + /** Chips and a puff where a shard breaks the surface. */ + _breachFx(record, c, g, radius) { + const time = frame.uTime.value; + + this._spikePosition(record, c, _pos).setY(0.08); + + _emit.position = _pos; + _emit.radius = radius * 0.9; + // Chips are thrown outward, away from the middle of the crown — the same + // direction the shard itself leans. + _emit.direction = _dir + .set(Math.cos(record.angle) * 0.55, 1, Math.sin(record.angle) * 0.55) + .normalize(); + _emit.speed = c.shardSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.7; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.1; + _emit.sizeVariance = 0.7; + _emit.life = c.shardLifetime; + _emit.lifeVariance = 0.45; + _emit.spin = 7; + _emit.tint = null; + _emit.time = time; + this.shards.emit(Math.round(c.breachShards * g.particleCount), _emit); + + // Only some shards puff: a few hundred steaming at once buries the crown in + // haze and hides the silhouette that is the whole point. + if (Math.random() < 0.35) { + _emit.speed = c.mistSpeed * 0.8; + _emit.spread = 1.0; + _emit.size = 0.55; + _emit.sizeVariance = 0.5; + _emit.life = c.mistLifetime * 0.7; + _emit.spin = 0.5; + this.mist.emit(Math.round(2 * g.particleCount), _emit); + } + + // A collar of rime around the foot of a ring blade, so the wall is seated on + // the ground rather than stuck through it. + if (record.role === Role.RING && Math.random() < 0.5) { + this._spikePosition(record, c, _pos); + this.ctx.decals.spawn(DecalType.FROST, _pos, { + radius: radius * c.frostCollar * randRange(0.7, 1.3), + life: c.frostLife, + width: c.frostCrystals, + intensity: c.frostIntensity * 0.9, + colorA: getColor(c.colorFrost), + colorB: getColor(c.colorFrostEdge) + }); + } + } + + /** The burst of plates when a shard starts to come apart. */ + _crumbleFx(record, c, g, radius, height) { + const time = frame.uTime.value; + + this._spikePosition(record, c, _pos).setY(height * 0.45); + + _emit.position = _pos; + _emit.radius = radius * 1.1; + _emit.direction = _dir + .set(Math.cos(record.angle) * 0.7, 0.7, Math.sin(record.angle) * 0.7) + .normalize(); + _emit.speed = c.shardSpeed * 0.75; + _emit.speedVariance = 0.85; + _emit.spread = 0.9; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.13; + _emit.sizeVariance = 0.8; + _emit.life = c.shardLifetime * 1.2; + _emit.lifeVariance = 0.5; + _emit.spin = 9; + _emit.tint = null; + _emit.time = time; + this.shards.emit(Math.round(c.shatterShards * g.particleCount), _emit); + + if (Math.random() < 0.5) { + _emit.speed = c.glitterSpeed * 0.6; + _emit.spread = 1.0; + _emit.size = 0.07; + _emit.life = c.glitterLifetime; + _emit.spin = 0; + this.glitter.emit(Math.round(6 * g.particleCount), _emit); + } + } + + /** + * Everything the standing crown sheds: cold spilling off the rim, snow through + * the middle, glitter off the sheet, rime creeping around the boundary, + * vapour shells off the wall and pressure rings across the floor. + * + * @param {number} scale 0..1 — thinned out as the crown collapses + */ + _fieldFx(dt, scale) { + const c = settings.glacier; + const g = settings.global; + const time = frame.uTime.value; + const centre = this._state.centre; + const radius = this.radius; + const open = this._openAmount(); + + /* --- cold air pouring off the rim and out across the floor --- */ + const mistCount = Math.round(this.mistEmitter.tick(dt, c.mistRate * scale) * g.particleCount); + if (mistCount > 0) { + const a = Math.random() * TAU; + const r = radius * randRange(0.75, 1.05); + _pos.set(centre.x + Math.cos(a) * r, randRange(0.05, 0.5), centre.z + Math.sin(a) * r); + _emit.position = _pos; + _emit.radius = radius * 0.18; + // Outward and barely up: this is heavy air falling off a wall of ice, not + // smoke rising off a fire. + _emit.direction = _dir.set(Math.cos(a), 0.18, Math.sin(a)).normalize(); + _emit.speed = c.mistSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.6; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.85; + _emit.sizeVariance = 0.5; + _emit.life = c.mistLifetime; + _emit.lifeVariance = 0.4; + _emit.spin = 0.4; + _emit.tint = null; + _emit.time = time; + this.mist.emit(mistCount, _emit); + } + + /* --- glitter lifting off the sheet --- */ + const glitterCount = Math.round( + this.glitterEmitter.tick(dt, c.glitterRate * scale) * g.particleCount + ); + if (glitterCount > 0) { + const a = Math.random() * TAU; + const r = radius * Math.sqrt(Math.random()); + _pos.set(centre.x + Math.cos(a) * r, randRange(0.05, 0.6), centre.z + Math.sin(a) * r); + _emit.position = _pos; + _emit.radius = radius * 0.15; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.glitterSpeed; + _emit.speedVariance = 0.8; + _emit.spread = 0.8; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.08; + _emit.sizeVariance = 0.6; + _emit.life = c.glitterLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.glitter.emit(glitterCount, _emit); + } + + /* --- and ice dust falling back through it --- */ + const snowCount = Math.round(this.snowEmitter.tick(dt, c.snowRate * scale) * g.particleCount); + if (snowCount > 0) { + const a = Math.random() * TAU; + const r = radius * c.snowInset * Math.sqrt(Math.random()); + _pos.set( + centre.x + Math.cos(a) * r, + c.ringHeight * c.snowHeight * open, + centre.z + Math.sin(a) * r + ); + _emit.position = _pos; + _emit.radius = radius * 0.25; + _emit.direction = _dir.set(0, -1, 0); + _emit.speed = c.snowSpeed; + _emit.speedVariance = 0.6; + _emit.spread = 0.5; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.07; + _emit.sizeVariance = 0.7; + _emit.life = c.snowLifetime; + _emit.lifeVariance = 0.45; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.snow.emit(snowCount, _emit); + } + + /* --- rime creeping around the boundary --- */ + const rimeCount = this.rimeEmitter.tick(dt, c.rimeRate * scale); + for (let i = 0; i < rimeCount; i++) { + const a = Math.random() * TAU; + const r = radius * randRange(0.7, 1.05); + _pos.set(centre.x + Math.cos(a) * r, 0, centre.z + Math.sin(a) * r); + this.ctx.decals.spawn(DecalType.FROST, _pos, { + radius: c.rimeRadius * randRange(0.7, 1.3), + life: c.frostLife, + width: c.frostCrystals, + intensity: c.frostIntensity, + colorA: getColor(c.colorFrost), + colorB: getColor(c.colorFrostEdge) + }); + } + + /* --- vapour shells shed off the wall --- */ + // Kept low and squashed on purpose: a shell released halfway up the blades + // hangs in the air as a glass dome, and it needs the floor under it to read + // as cold coming off the ice rather than as a bubble parked in mid-air. + const vapourCount = this.vapourEmitter.tick(dt, c.vapourRate * scale); + for (let i = 0; i < vapourCount; i++) { + const a = Math.random() * TAU; + _pos.set( + centre.x + Math.cos(a) * radius * randRange(0.5, 0.95), + c.ringHeight * randRange(0.05, 0.3) * open, + centre.z + Math.sin(a) * radius * randRange(0.5, 0.95) + ); + this.ctx.bursts.spawn(BurstMode.FROST, _pos, { + radius: c.vapourSize * 0.3, + endRadius: c.vapourSize * g.explosionIntensity, + life: 0.75, + intensity: c.vapourIntensity, + opacity: 0.28, + fresnel: 1.8, + displace: 0.55, + squash: 0.5, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + } + + /* --- pressure rings pushed out across the floor --- */ + const ringCount = this.ringEmitter.tick(dt, c.ringRate * scale); + for (let i = 0; i < ringCount; i++) { + this.ctx.decals.spawn(DecalType.SHOCKWAVE, centre, { + radius: radius * 1.08, + life: 0.8, + width: 0.05, + intensity: 0.55, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + } + } + + /* ------------------------------------------------------------------ */ + /* Phases */ + /* ------------------------------------------------------------------ */ + + onTravel(dt) { + this._sync(1); + this._updateSpikes(); + + // The light rides the front, a little off the floor. + this.position.y = 0.35; + + this._frontFx(dt); + this.ctx.shake.rumble(settings.glacier.rumble * settings.global.cameraShake, dt); + } + + onImpact() { + const c = settings.glacier; + const g = settings.global; + const time = frame.uTime.value; + + this._openTime = 0; + this._scheduleEruption(); + this._centrePoint(_pos); + _pos.y = 0.5; + + /* the shell of freezing vapour thrown off as the sheet snaps outward */ + this.ctx.bursts.spawn(BurstMode.FROST, _pos, { + radius: c.burstSize * 0.25, + endRadius: c.burstSize * g.explosionIntensity, + life: 0.9, + intensity: c.burstIntensity, + opacity: 0.8, + fresnel: 1.4, + displace: 0.6, + squash: 0.6, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + /* the ring that snaps outward across the floor, past the boundary */ + this._centrePoint(_pos); + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _pos, { + radius: c.shockRadius * g.explosionIntensity, + life: 0.8, + width: 0.05, + intensity: 1.0, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + /* the sheet of rime the crown is seated on */ + this.ctx.decals.spawn(DecalType.FROST, _pos, { + radius: this.radius * c.frostSpread, + life: c.frostLife * 1.4, + width: c.frostCrystals, + intensity: c.frostIntensity, + colorA: getColor(c.colorFrost), + colorB: getColor(c.colorFrostEdge) + }); + + /* chips, fog and glitter blown out of the bloom */ + this._centrePoint(_pos).setY(0.4); + _emit.position = _pos; + _emit.radius = this.radius * 0.5; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.shardSpeed * 1.7; + _emit.speedVariance = 0.8; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.15; + _emit.sizeVariance = 0.8; + _emit.life = c.shardLifetime * 1.3; + _emit.lifeVariance = 0.5; + _emit.spin = 9; + _emit.tint = null; + _emit.time = time; + this.shards.emit(Math.round(c.burstShards * g.particleCount), _emit); + + _emit.radius = this.radius * 0.8; + _emit.speed = c.mistSpeed * 2.2; + _emit.spread = 1.0; + _emit.size = 1.3; + _emit.life = c.mistLifetime * 1.3; + _emit.spin = 0.6; + this.mist.emit(Math.round(c.burstMist * g.particleCount), _emit); + + _emit.radius = this.radius * 0.6; + _emit.speed = c.glitterSpeed * 1.5; + _emit.spread = 0.9; + _emit.size = 0.1; + _emit.life = c.glitterLifetime * 1.2; + _emit.spin = 0; + this.glitter.emit(Math.round(c.burstGlitter * g.particleCount), _emit); + + this.ctx.shake.add( + c.impactShake * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 21 + ); + this.ctx.flash.trigger(getColor(c.colorFlash), c.impactFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 1.5 * g.explosionIntensity; + } + + onFade(dt, t) { + const c = settings.glacier; + this._openTime += dt; + + // `t` runs 0..1 while the crown stands, then 1..2 while it collapses. The + // sheet and the curtain are carried out by the thaw inside `_sync`, so the + // fade here only has to take the last of the alpha with it. + const fade = t <= 1 ? 1 : 1 - Easing.inQuad(saturate(t - 1)); + + this._sync(fade); + this._updateSpikes(); + + // The light climbs into the crown and stays there. + this._centrePoint(this.position); + this.position.y = c.ringHeight * saturate(c.lightHeight) * this._openAmount(); + + this._fieldFx(dt, fade * (t <= 1 ? 1 : 0.35)); + this.ctx.shake.rumble(c.holdShake * fade * settings.global.cameraShake, dt); + } + + onDestroy() { + this._activeCount = 0; + this._drawn = 0; + for (let v = 0; v < VARIANTS; v++) this.meshes[v].count = 0; + this.field.visible = false; + this.veil.visible = false; + } + + dispose() { + for (const mesh of this.meshes) { + mesh.geometry.dispose(); + mesh.dispose(); + } + this.material.dispose(); + this.fieldGeometry.dispose(); + this.fieldMaterial.dispose(); + this.veilGeometry.dispose(); + this.veilMaterial.dispose(); + super.dispose(); + } +} diff --git a/src/skillshot/abilities/IceAbility.js b/src/skillshot/abilities/IceAbility.js new file mode 100644 index 0000000..e5faa5d --- /dev/null +++ b/src/skillshot/abilities/IceAbility.js @@ -0,0 +1,781 @@ +import { + InstancedMesh, + InstancedBufferAttribute, + Object3D, + Vector3, + Quaternion +} from 'three'; +import { Ability, AbilityPhase } from './Ability.js'; +import { createIceMaterial } from '../materials/IceMaterial.js'; +import { createCrystalGeometry } from '../ProceduralGeometry.js'; +import { ParticleShape } from '../../particles/ParticleSystem.js'; +import { RateEmitter } from '../../particles/ParticleEngine.js'; +import { DecalType } from '../../effects/GroundDecals.js'; +import { BurstMode } from '../../effects/BurstSphere.js'; +import { LAYER } from '../../core/Layers.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, lerp, smoothstep, Easing, randRange } from '../../utils/math.js'; + +/** Hard ceiling on crystals per cast. The editor's count slider clamps to this. */ +const MAX_SPIKES = 288; +/** + * Distinct crystal shapes in the field. Each is its own InstancedMesh — three + * draw calls buys real silhouette variety that per-instance scaling alone + * cannot, because the *facets* differ, not just the proportions. + */ +const VARIANTS = 3; +const SLOTS = Math.ceil(MAX_SPIKES / VARIANTS); +const TAU = Math.PI * 2; + +const _emit = {}; +const _pos = new Vector3(); +const _dir = new Vector3(); +const _lean = new Vector3(); +const _axis = new Vector3(); +const _up = new Vector3(0, 1, 0); +const _dummy = new Object3D(); +const _spin = new Quaternion(); +const _tilt = new Quaternion(); + +/** + * ICE — a glacial eruption along the aimed line. + * + * One beat, drawn out: a fracture front races away from the caster at `speed`, + * and a field of crystal spikes tears out of the floor behind it — dense and + * ankle-high at the caster, opening into a wall of blades at the far end, with a + * final cluster thrown up around the impact point. Rime spreads on the ground, + * fog rolls off the base and a plume of glitter drifts up out of the wreckage. + * + * Everything is generated: the crystals are procedural geometry, their shading + * is a patched standard material (so they take the stage's shadows and probe), + * the rime is a shader on a quad and the mist, chips and glitter are GPU + * particles. Nothing is loaded from disk. + * + * **The rule that makes the editor work.** A spike record stores only what the + * dice decided — a position *fraction* along the line, a signed lateral + * *fraction*, and a handful of unitless jitters. Not one metre, radian or second + * is captured at spawn time; all of them are resolved against `settings.ice` + * inside the update loop. Dragging `height` therefore re-grows a field that is + * already standing, and it does so with the clock paused, which is when the + * silhouette is actually worth tuning. + * + * The only values a record *does* capture are timestamps — the moment its own + * eruption was triggered. Those are events, not dimensions. + */ +export class IceAbility extends Ability { + constructor(context) { + super('ice', context); + } + + /* ------------------------------------------------------------------ */ + /* Construction */ + /* ------------------------------------------------------------------ */ + + createShaders() { + const environment = this.ctx.environment; + + this.material = createIceMaterial(environment); + + /** Signature of the geometry controls, so a rebuild only happens on a change. */ + this._shapeKey = ''; + + this.meshes = []; + this.seedAttributes = []; + this.birthAttributes = []; + + for (let v = 0; v < VARIANTS; v++) { + const geometry = this._buildGeometry(v); + + const seeds = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + const births = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + for (let i = 0; i < SLOTS; i++) seeds.array[i] = Math.random() * 10; + geometry.setAttribute('aSeed', seeds); + geometry.setAttribute('aBirth', births); + + const mesh = new InstancedMesh(geometry, this.material, SLOTS); + mesh.castShadow = true; + mesh.receiveShadow = true; + mesh.frustumCulled = false; + mesh.count = 0; + // Solid world geometry: it belongs in the depth prepass so the mist and + // the glitter fade softly where they intersect it. + mesh.layers.set(LAYER.WORLD); + mesh.renderOrder = 2; + this.group.add(mesh); + + this.meshes.push(mesh); + this.seedAttributes.push(seeds); + this.birthAttributes.push(births); + } + + /** + * Fixed-size record pool — a cast allocates nothing. + * + * See the class comment: dice only, no dimensions. + */ + this.records = []; + for (let i = 0; i < MAX_SPIKES; i++) { + this.records.push({ + along: 0, // 0..1 down the cast line + lateral: 0, // -1..1 across the band, before clumping + scatter: 0, // -1..1 extra lateral jitter + angle: 0, // impact-cluster bearing, radians + radial: 0, // impact-cluster distance, 0..1 + impact: false, // held back for the cluster at the far end + rubble: false, // demoted to ankle-height wreckage + heightJitter: 0, + radiusJitter: 0, + leanJitter: 0, + pitchJitter: 0, + yaw: 0, + stagger: 0, // 0..1 of `riseStagger` + eruptTime: -1, // absolute age it was triggered at, or -1 + shattered: false + }); + } + + this._activeCount = 0; + this._impactStart = MAX_SPIKES; + } + + /** One crystal shape. Variant index only perturbs the seed. */ + _buildGeometry(variant) { + const c = settings.ice; + return createCrystalGeometry({ + seed: 7.3 + variant * 21.7, + sides: c.facets, + taper: c.taper, + roughness: c.roughness, + bend: c.bend + }); + } + + /** + * Regenerate the crystal meshes when a *shape* control moves. + * + * Facet count, taper, roughness and bend cannot be expressed as a per-instance + * transform, so they are baked into the geometry — and a six-sided crystal is + * 108 triangles, cheap enough to simply rebuild rather than approximate in a + * vertex shader. That is what keeps them live sliders. + */ + _syncGeometry() { + const c = settings.ice; + const key = `${Math.round(c.facets)}|${c.taper.toFixed(3)}|${c.roughness.toFixed(3)}|${c.bend.toFixed(3)}`; + if (key === this._shapeKey) return; + this._shapeKey = key; + + for (let v = 0; v < VARIANTS; v++) { + const mesh = this.meshes[v]; + const previous = mesh.geometry; + const geometry = this._buildGeometry(v); + // The per-instance attributes are state, not shape — carry them over. + geometry.setAttribute('aSeed', this.seedAttributes[v]); + geometry.setAttribute('aBirth', this.birthAttributes[v]); + mesh.geometry = geometry; + previous.dispose(); + } + } + + createParticles() { + const particles = this.ctx.particles; + + // Ground fog rolling off the eruption. Non-additive: it has to *occlude*, + // which is what gives the field depth in the reference look. + this.mist = particles.get('ice.mist', { + capacity: 3200, + shape: ParticleShape.SMOKE, + additive: false, + curl: true, + softFade: 1.2 + }); + this.mist.uniforms.uDrag.value = 1.9; + this.mist.uniforms.uEndSize.value = 3.4; + this.mist.uniforms.uSizeIn.value = 0.1; + this.mist.uniforms.uFadeIn.value = 0.14; + this.mist.uniforms.uFadeOut.value = 0.3; + + // Chips torn off the crystals as they punch through the floor. + this.shards = particles.get('ice.shards', { + capacity: 2400, + shape: ParticleShape.CHIP, + additive: false, + lit: true, + softFade: 0.25 + }); + this.shards.uniforms.uDrag.value = 0.22; + this.shards.uniforms.uEndSize.value = 0.85; + this.shards.uniforms.uFadeOut.value = 0.72; + + // The rising glitter plume — the signature of the reference frame. + this.glitter = particles.get('ice.glitter', { + capacity: 2800, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.4 + }); + this.glitter.uniforms.uDrag.value = 1.1; + this.glitter.uniforms.uEndSize.value = 0.18; + this.glitter.uniforms.uSizeIn.value = 0.05; + this.glitter.uniforms.uFadeIn.value = 0.06; + this.glitter.uniforms.uFadeOut.value = 0.35; + + this.mistEmitter = new RateEmitter(); + this.glitterEmitter = new RateEmitter(); + this.frostEmitter = new RateEmitter(); + } + + /* ------------------------------------------------------------------ */ + /* Timing */ + /* ------------------------------------------------------------------ */ + + get instanceCount() { + return this._activeCount; + } + + /** The field stands for its lifetime, then the fade withdraws it. */ + get impactDuration() { + return Math.max(0.2, settings.ice.lifetime * settings.global.lifetime); + } + + get fadeDuration() { + const c = settings.ice; + return Math.max(0.2, c.shatterDelay + c.sinkTime); + } + + /* ------------------------------------------------------------------ */ + /* Casting */ + /* ------------------------------------------------------------------ */ + + onSpawn() { + const c = settings.ice; + + this.mistEmitter.reset(); + this.glitterEmitter.reset(); + this.frostEmitter.reset(); + this._frostDistance = 0; + + const wanted = Math.min(MAX_SPIKES, Math.max(1, Math.round(c.spikeCount * c.density))); + // The last slice is held back for the cluster thrown up at the impact point. + const impactCount = Math.round(wanted * 0.22); + this._activeCount = wanted; + this._impactStart = wanted - impactCount; + + for (let i = 0; i < wanted; i++) { + const record = this.records[i]; + const impact = i >= this._impactStart; + + record.impact = impact; + record.eruptTime = -1; + record.shattered = false; + record.yaw = Math.random() * TAU; + record.stagger = Math.random(); + record.heightJitter = randRange(-1, 1); + record.radiusJitter = randRange(-1, 1); + record.leanJitter = randRange(-1, 1); + record.pitchJitter = randRange(-1, 1); + record.scatter = randRange(-1, 1); + record.rubble = Math.random() < c.rubble; + + if (impact) { + record.angle = Math.random() * TAU; + // sqrt keeps the cluster evenly dense rather than piled in the middle. + record.radial = Math.sqrt(Math.random()); + record.along = 1; + } else { + // `frontBias` < 1 crowds the field toward the impact point. + record.along = Math.pow((i + Math.random()) / Math.max(1, this._impactStart), c.frontBias); + record.lateral = randRange(-1, 1); + } + } + + for (let i = wanted; i < MAX_SPIKES; i++) this.records[i].eruptTime = -1; + for (let v = 0; v < VARIANTS; v++) this.meshes[v].count = 0; + } + + /* ------------------------------------------------------------------ */ + /* Resolving a spike — every metre, radian and second comes from here */ + /* ------------------------------------------------------------------ */ + + /** Half-width of the band at `s` along the line, metres. */ + _halfWidth(s, c) { + return lerp(c.widthNear, c.width, Math.pow(saturate(s), c.widthCurve)); + } + + /** Signed lateral offset of a record, as a fraction of the local half-width. */ + _lateralNorm(record, c) { + const raw = record.lateral; + const clumped = Math.sign(raw) * Math.pow(Math.abs(raw), c.clumping); + return clumped + record.scatter * c.scatter; + } + + /** Where a spike currently stands, at the live footprint settings. */ + _spikePosition(record, c, out) { + if (record.impact) { + const reach = this._halfWidth(1, c) * 1.25 * record.radial; + this.pointAt(1, out); + out.x += Math.cos(record.angle) * reach; + out.z += Math.sin(record.angle) * reach; + return out; + } + + this.pointAt(record.along, out); + return out.addScaledVector(this.side, this._lateralNorm(record, c) * this._halfWidth(record.along, c)); + } + + /** Full height of a spike, metres. */ + _spikeHeight(record, c, g) { + let h = lerp(c.heightNear, c.height, Math.pow(saturate(record.along), c.heightCurve)); + + // The swell at the impact point. + h *= 1 + (c.peak - 1) * smoothstep(1 - c.peakWidth, 1, record.along); + + // Domed silhouette: the blades on the flanks are shorter than the spine. + const edge = record.impact ? record.radial : saturate(Math.abs(this._lateralNorm(record, c))); + h *= lerp(1, 1 - saturate(c.crown), Math.pow(edge, 1.4)); + + h *= 1 + record.heightJitter * c.heightJitter * g.randomness; + if (record.rubble) h *= c.rubbleScale; + + return Math.max(0.02, h); + } + + /** Base radius of a spike, metres. */ + _spikeRadius(record, c, g) { + const grow = lerp(0.72, 1.15, Math.pow(saturate(record.along), 0.6)); + const jitter = 1 + record.radiusJitter * c.radiusJitter * g.randomness; + return Math.max(0.01, c.radius * grow * jitter * (record.rubble ? 1.25 : 1)); + } + + /* ------------------------------------------------------------------ */ + /* The eruption */ + /* ------------------------------------------------------------------ */ + + /** + * Trigger every spike the fracture front has now reached. + * `limit` is how far down the line the front has got, 0..1. + */ + _triggerUpTo(limit, includeImpact) { + const c = settings.ice; + for (let i = 0; i < this._activeCount; i++) { + const record = this.records[i]; + if (record.eruptTime >= 0) continue; + if (record.impact && !includeImpact) continue; + if (!record.impact && record.along > limit) continue; + record.eruptTime = this.age + record.stagger * c.riseStagger; + } + } + + /** + * How far out of the ground a spike is, 0 → 1 with a springy overshoot past 1. + * Returns a negative number while it is still buried and waiting. + */ + _emergence(record, c) { + if (record.eruptTime < 0) return -1; + const elapsed = this.age - record.eruptTime; + if (elapsed < 0) return -1; + + const riseTime = Math.max(0.02, c.riseTime); + const rise = Easing.outQuint(saturate(elapsed / riseTime)); + if (elapsed <= riseTime) return rise; + + // The punch-through carries past full height and settles back. + const after = elapsed - riseTime; + const spring = Math.sin(after * 14) * Math.exp(-after / Math.max(0.05, c.settle)); + return 1 + c.riseOvershoot * spring; + } + + /** + * Rebuild every instance matrix from the live settings. + * @param {number} retract 0..1 — the whole field withdrawing into the floor. + */ + _updateSpikes(dt, retract) { + const c = settings.ice; + const g = settings.global; + const birthFade = Math.max(0.02, c.birthFade); + const used = [0, 0, 0]; + + for (let i = 0; i < this._activeCount; i++) { + const record = this.records[i]; + const variant = i % VARIANTS; + const slot = (i / VARIANTS) | 0; + const emerge = this._emergence(record, c); + + if (emerge < 0) { + // Still buried. Park it outside the view rather than drawing a degenerate + // matrix at the origin. + _dummy.position.set(0, -999, 0); + _dummy.quaternion.identity(); + _dummy.scale.setScalar(0.0001); + _dummy.updateMatrix(); + this.meshes[variant].setMatrixAt(slot, _dummy.matrix); + this.birthAttributes[variant].array[slot] = 0; + used[variant] = Math.max(used[variant], slot + 1); + continue; + } + + const height = this._spikeHeight(record, c, g); + const radius = this._spikeRadius(record, c, g); + + /* --- chips at the moment it breaks the surface --- */ + if (!record.shattered && emerge > 0.25) { + record.shattered = true; + this._breachFx(record, c, g, radius); + } + + /* --- lean: away from the caster, and outward across the band --- */ + const outward = record.impact + ? Math.sign(Math.cos(record.angle)) * record.radial + : this._lateralNorm(record, c); + _lean + .copy(this.direction) + .multiplyScalar(0.75) + .addScaledVector(this.side, outward * 0.85); + if (_lean.lengthSq() < 1e-6) _lean.copy(this.direction); + _lean.normalize(); + + const leanAngle = + c.lean * + (0.35 + 0.65 * record.along) * + (1 + record.leanJitter * c.leanJitter * g.randomness); + + // Rotating about (up × lean) tips the crystal's own +Y toward `lean`. + _axis.crossVectors(_up, _lean).normalize(); + _tilt.setFromAxisAngle(_axis, leanAngle); + _spin.setFromAxisAngle(_up, record.yaw * c.twist); + _tilt.multiply(_spin); + + /* --- slide it up out of the floor --- */ + const settled = Math.min(1, emerge); + this._spikePosition(record, c, _dummy.position); + _dummy.position.y = (emerge - 1) * height * 0.85; + + if (retract > 0) { + const sink = Easing.inCubic(retract); + _dummy.position.y -= sink * (height + radius + 0.4); + } + + _dummy.quaternion.copy(_tilt); + _dummy.scale.set(radius, height, radius).multiplyScalar(lerp(0.86, 1, settled)); + _dummy.updateMatrix(); + + this.meshes[variant].setMatrixAt(slot, _dummy.matrix); + this.birthAttributes[variant].array[slot] = + saturate(1 - (this.age - record.eruptTime) / birthFade); + used[variant] = Math.max(used[variant], slot + 1); + } + + for (let v = 0; v < VARIANTS; v++) { + this.meshes[v].count = used[v]; + this.meshes[v].instanceMatrix.needsUpdate = true; + this.birthAttributes[v].needsUpdate = true; + } + } + + /* ------------------------------------------------------------------ */ + /* Feedback */ + /* ------------------------------------------------------------------ */ + + _syncUniforms() { + const c = settings.ice; + const g = settings.global; + + this._syncGeometry(); + this.material.userData.sync(); + + this.mist.setGradient( + getColor(c.colorMistA), + getColor(c.colorMistB), + getColor(c.colorMistC), + getColor(c.colorMistD) + ); + this.mist.uniforms.uGravity.value.set(0, c.mistRise, 0); + this.mist.uniforms.uSizeScale.value = c.mistSize * g.particleSize; + this.mist.uniforms.uLifeScale.value = c.mistLifetime * 0.5 * g.particleLifetime; + this.mist.uniforms.uSpeedScale.value = c.mistSpeed * g.particleSpeed; + this.mist.uniforms.uOpacity.value = c.mistOpacity * g.opacity; + this.mist.uniforms.uTurbulence.value = 0.4 * g.turbulence; + + this.shards.setGradient( + getColor(c.colorShardA), + getColor(c.colorShardB), + getColor(c.colorShardC), + getColor(c.colorShardD) + ); + this.shards.uniforms.uGravity.value.set(0, c.shardGravity, 0); + this.shards.uniforms.uSizeScale.value = c.shardSize * g.particleSize * 7; + this.shards.uniforms.uLifeScale.value = g.particleLifetime; + this.shards.uniforms.uSpeedScale.value = g.particleSpeed; + this.shards.uniforms.uOpacity.value = g.opacity; + + this.glitter.setGradient( + getColor(c.colorSparkleA), + getColor(c.colorSparkleB), + getColor(c.colorSparkleC), + getColor(c.colorSparkleD) + ); + this.glitter.uniforms.uGravity.value.set(0, c.sparkleRise, 0); + this.glitter.uniforms.uSizeScale.value = c.sparkleSize * g.particleSize * 7; + this.glitter.uniforms.uLifeScale.value = c.sparkleLifetime * 0.5 * g.particleLifetime; + this.glitter.uniforms.uSpeedScale.value = g.particleSpeed; + this.glitter.uniforms.uOpacity.value = g.opacity; + this.glitter.uniforms.uGlow.value = 0.9 * g.glow; + this.glitter.uniforms.uTurbulence.value = c.sparkleTurbulence * g.turbulence; + } + + /** Chips and a puff where a crystal breaks the surface. */ + _breachFx(record, c, g, radius) { + const time = frame.uTime.value; + + this._spikePosition(record, c, _pos).setY(0.08); + + _emit.position = _pos; + _emit.radius = radius * 0.8; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.35).setY(1).normalize(); + _emit.speed = c.shardSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.75; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.1; + _emit.sizeVariance = 0.7; + _emit.life = c.shardLifetime; + _emit.lifeVariance = 0.45; + _emit.spin = 7; + _emit.tint = null; + _emit.time = time; + this.shards.emit(Math.round(3 * g.particleCount), _emit); + + // Only some spikes puff: a few hundred smoking at once buries the field in + // haze and hides the silhouette that is the whole point. + if (Math.random() < 0.4) { + _emit.speed = 0.7; + _emit.spread = 1.0; + _emit.size = 0.6; + _emit.sizeVariance = 0.5; + _emit.life = c.mistLifetime * 0.7; + _emit.spin = 0.5; + this.mist.emit(Math.round(2 * g.particleCount), _emit); + } + } + + /** Fog, glitter and rime shed continuously along the travelling front. */ + _frontFx(dt) { + const c = settings.ice; + const g = settings.global; + const time = frame.uTime.value; + const halfWidth = this._halfWidth(this.u, c); + + const mistCount = Math.round(this.mistEmitter.tick(dt, c.mistRate) * g.particleCount); + if (mistCount > 0) { + _emit.position = _pos.copy(this.position).setY(0.18); + _emit.radius = halfWidth * 0.9; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.mistSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.9; + _emit.sizeVariance = 0.5; + _emit.life = c.mistLifetime; + _emit.lifeVariance = 0.4; + _emit.spin = 0.5; + _emit.tint = null; + _emit.time = time; + this.mist.emit(mistCount, _emit); + } + + const glitterCount = Math.round(this.glitterEmitter.tick(dt, c.sparkleRate) * g.particleCount); + if (glitterCount > 0) { + _emit.position = _pos.copy(this.position).setY(0.5); + _emit.radius = halfWidth; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.sparkleSpeed; + _emit.speedVariance = 0.8; + _emit.spread = 0.85; + _emit.size = 0.09; + _emit.sizeVariance = 0.6; + _emit.life = c.sparkleLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.time = time; + this.glitter.emit(glitterCount, _emit); + } + + /* rime laid on the floor as the fracture passes over it */ + const step = 1 / Math.max(0.1, c.frostRate); + while (this.front - this._frostDistance >= step) { + this._frostDistance += step; + const s = saturate(this._frostDistance / this.length); + const width = this._halfWidth(s, c); + this.pointAt(s, _pos); + _pos.x += this.side.x * randRange(-0.7, 0.7) * width; + _pos.z += this.side.z * randRange(-0.7, 0.7) * width; + + this.ctx.decals.spawn(DecalType.FROST, _pos, { + radius: width * c.frostSpread * randRange(0.6, 1.15), + life: c.frostLife, + width: c.frostCrystals, + intensity: c.frostIntensity, + colorA: getColor(c.colorFrost), + colorB: getColor(c.colorFrostEdge) + }); + } + } + + /* ------------------------------------------------------------------ */ + /* Phases */ + /* ------------------------------------------------------------------ */ + + onTravel(dt) { + this._syncUniforms(); + this._triggerUpTo(this.u, false); + this._updateSpikes(dt, 0); + this._frontFx(dt); + + this.ctx.shake.rumble(settings.ice.rumble * settings.global.cameraShake, dt); + } + + onImpact() { + const c = settings.ice; + const g = settings.global; + const time = frame.uTime.value; + + // Everything still buried goes up now, including the terminal cluster. + this._triggerUpTo(1, true); + + // One scratch vector for the whole sequence: every consumer below copies + // what it needs out of it, so it can be re-aimed in place. + this.pointAt(1, _pos).setY(0.6); + + /* the shell of freezing vapour */ + this.ctx.bursts.spawn(BurstMode.FROST, _pos, { + radius: c.burstSize * 0.3, + endRadius: c.burstSize * g.explosionIntensity, + life: 0.95, + intensity: c.burstIntensity, + opacity: 0.85, + fresnel: 1.3, + displace: 0.55, + squash: 0.72, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + /* the ring that snaps outward across the floor */ + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _pos, { + radius: c.shockRadius * g.explosionIntensity, + life: 0.75, + width: 0.06, + intensity: 0.9, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + /* a broad rime patch under the cluster */ + this.ctx.decals.spawn(DecalType.FROST, _pos, { + radius: this._halfWidth(1, c) * c.frostSpread * 2.2, + life: c.frostLife * 1.3, + width: c.frostCrystals, + intensity: c.frostIntensity, + colorA: getColor(c.colorFrost), + colorB: getColor(c.colorFrostEdge) + }); + + /* chips, fog and glitter thrown out of the impact */ + _emit.position = _pos.setY(0.4); + _emit.radius = this._halfWidth(1, c) * 0.8; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.shardSpeed * 1.8; + _emit.speedVariance = 0.75; + _emit.spread = 0.9; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.16; + _emit.sizeVariance = 0.8; + _emit.life = c.shardLifetime * 1.4; + _emit.lifeVariance = 0.5; + _emit.spin = 9; + _emit.tint = null; + _emit.time = time; + this.shards.emit(Math.round(c.burstShards * g.particleCount), _emit); + + _emit.speed = c.mistSpeed * 2.4; + _emit.spread = 1.0; + _emit.size = 1.6; + _emit.life = c.mistLifetime * 1.5; + _emit.spin = 0.6; + this.mist.emit(Math.round(90 * g.particleCount), _emit); + + _emit.speed = c.sparkleSpeed * 1.6; + _emit.spread = 0.8; + _emit.size = 0.1; + _emit.life = c.sparkleLifetime * 1.3; + _emit.spin = 0; + this.glitter.emit(Math.round(150 * g.particleCount), _emit); + + this.ctx.shake.add( + c.impactShake * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 19 + ); + this.ctx.flash.trigger(getColor(c.colorFlash), c.impactFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 1.6 * g.explosionIntensity; + } + + onFade(dt) { + const c = settings.ice; + this._syncUniforms(); + + let retract = 0; + if (this.phase === AbilityPhase.FADE) { + retract = saturate((this.fadeTime - c.shatterDelay) / Math.max(0.05, c.sinkTime)); + } + + this._updateSpikes(dt, retract); + + // The field keeps steaming while it stands, then stops as it withdraws. + if (retract < 0.6) { + const g = settings.global; + const time = frame.uTime.value; + const count = Math.round(this.glitterEmitter.tick(dt, c.sparkleRate * 0.35) * g.particleCount); + if (count > 0) { + this.pointAt(randRange(0.25, 1), _pos).setY(0.35); + _emit.position = _pos; + _emit.radius = this._halfWidth(1, c); + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.sparkleSpeed * 0.7; + _emit.speedVariance = 0.8; + _emit.spread = 0.9; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.08; + _emit.sizeVariance = 0.6; + _emit.life = c.sparkleLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.glitter.emit(count, _emit); + } + } + } + + onDestroy() { + this._activeCount = 0; + for (let v = 0; v < VARIANTS; v++) this.meshes[v].count = 0; + } + + dispose() { + for (const mesh of this.meshes) { + mesh.geometry.dispose(); + mesh.dispose(); + } + this.material.dispose(); + super.dispose(); + } +} diff --git a/src/skillshot/abilities/MeteorAbility.js b/src/skillshot/abilities/MeteorAbility.js new file mode 100644 index 0000000..dce4162 --- /dev/null +++ b/src/skillshot/abilities/MeteorAbility.js @@ -0,0 +1,1081 @@ +import { + InstancedMesh, + InstancedBufferAttribute, + Mesh, + Object3D, + Quaternion, + Vector3 +} from 'three'; +import { Ability, AbilityPhase } from './Ability.js'; +import { createMeteorMaterial } from '../materials/MeteorMaterial.js'; +import { VolumetricFireMaterial, fireHullReach } from '../materials/VolumetricFireMaterial.js'; +import { createAsteroidGeometry } from '../ProceduralGeometry.js'; +import { RibbonGeometry, RibbonMode } from '../../effects/RibbonGeometry.js'; +import { ParticleShape } from '../../particles/ParticleSystem.js'; +import { RateEmitter } from '../../particles/ParticleEngine.js'; +import { DecalType } from '../../effects/GroundDecals.js'; +import { BurstMode } from '../../effects/BurstSphere.js'; +import { LAYER } from '../../core/Layers.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, lerp, smoothstep, Easing, randRange } from '../../utils/math.js'; + +/** Hard ceiling on debris chunks. The editor's `chunkCount` slider clamps here. */ +const MAX_CHUNKS = 28; +/** Instance 0 is the meteor; 1.. are the chunks it breaks into. */ +const SLOTS = MAX_CHUNKS + 1; +/** How many points along the frame's travel the particle trail is split between. */ +const TRAIL_BATCHES = 3; +/** + * Samples along the centre line of the flame's proxy hull. The trail is + * *derived* from the arc rather than recorded, so this is purely how finely the + * hull is tessellated. + */ +const TRAIL_NODES = 48; +const TAU = Math.PI * 2; + +/** + * Radius profile along the stream, 0 = tail, 1 = head, in units of `trailWidth`. + * Must stay in step with `flameProfile()` in VolumetricFireMaterial — the JS + * side sizes the proxy hull, the shader side shapes the actual volume, and the + * hull has to contain it. + */ +function radiusProfile(u, headSize, wakeSpread) { + const body = (0.34 + 0.66 * Math.pow(u, 0.55)) * (1 + wakeSpread * Math.pow(1 - u, 1.8)); + return body * (1 + (headSize - 1) * smoothstep(0.62, 1, u)); +} + +const _emit = {}; +const _pos = new Vector3(); +const _dir = new Vector3(); +const _back = new Vector3(); +const _heading = new Vector3(); +const _impact = new Vector3(); +const _ahead = new Vector3(); +const _behind = new Vector3(); +const _axis = new Vector3(); +const _dummy = new Object3D(); +const _spin = new Quaternion(); + +/** + * METEOR — a burning rock thrown along the aimed line, which detonates on + * arrival. + * + * One beat with a real run-up: a ball of asteroid leaves the caster's hand, + * lobs downrange on an arc, and heats up the whole way — the lava seams split + * wider and brighter as it comes in, so the explosion is something you watched + * arrive. It lands, bursts, and throws its own shattered chunks across the + * floor, where they cool while the crater burns out. + * + * Everything is generated: the rock is procedural geometry, its shading is a + * patched standard material (so it takes the stage's shadows and probe), the + * fire streaming off it is a black-body volume raymarched inside a camera-facing + * proxy hull, and the embers, sparks, smoke and grit are GPU particles. Nothing + * is loaded from disk and nothing is a texture. + * + * **The rule that makes the editor work.** `IceAbility` keeps spike records + * holding nothing but dice; the same applies here. A cast captures one seed, one + * tumble axis and a handful of unitless rolls per chunk — an angle, an + * elevation, a speed jitter. Not one metre, radian or second. The trajectory, + * the rock's size, the width of its seams, how hard the debris is thrown and how + * fast it cools are all resolved against `settings.meteor` inside the update + * loop, on a zero-length frame included. Dragging `arc` re-lofts a meteor that + * is already in the air, and dragging `chunkSpeed` re-throws debris that has + * already landed. + * + * The chunks are ballistic rather than integrated for exactly that reason: + * position is a closed-form function of the time since impact, so changing the + * numbers rewrites the whole flight instead of only what happens next. + */ +export class MeteorAbility extends Ability { + constructor(context) { + super('meteor', context); + } + + /* ------------------------------------------------------------------ */ + /* Construction */ + /* ------------------------------------------------------------------ */ + + createShaders() { + this.material = createMeteorMaterial(this.ctx.environment); + + /** Signature of the geometry controls, so a rebuild only happens on a change. */ + this._shapeKey = ''; + this.geometry = this._buildGeometry(); + + this.seeds = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + this.heats = new InstancedBufferAttribute(new Float32Array(SLOTS), 1); + for (let i = 0; i < SLOTS; i++) this.seeds.array[i] = Math.random() * 10; + this.geometry.setAttribute('aSeed', this.seeds); + this.geometry.setAttribute('aHeat', this.heats); + + // One instanced mesh for the meteor *and* its debris: same rock, same + // shading, one draw call, and the chunks differ only by `aSeed`, which + // offsets the crack field so no two are split the same way. + this.rock = new InstancedMesh(this.geometry, this.material, SLOTS); + this.rock.castShadow = true; + this.rock.receiveShadow = true; + this.rock.frustumCulled = false; + this.rock.count = 0; + // Solid world geometry: it belongs in the depth prepass so the smoke and the + // embers fade softly where they intersect it. + this.rock.layers.set(LAYER.WORLD); + this.rock.renderOrder = 2; + this.group.add(this.rock); + + /* + * The fire trail: a burning volume along the section of arc behind the rock. + * + * The mesh below is *not* the flame. It is only a camera-facing proxy hull + * around the flight path; `VolumetricFireMaterial` reconstructs the arc's + * local frame per fragment off the ribbon's `aCenter` / `aTangent`, fires a + * ray from the camera and integrates emission and soot absorption through + * the field described in that file. The hull's one job is to *contain* the + * volume — see `_updateTrail` for the padding that guarantees it. + * + * The centre line is *derived* from `_arcPoint`, not recorded, so the trail + * is a live function of the trajectory settings: dragging `arc` re-lofts the + * fire that is already in the air along with the rock making it. + * + * Two points beyond the sampled window pad the hull so the volume's end caps + * are covered by geometry, hence the `+ 2` on the segment budget. + */ + this.ribbon = new RibbonGeometry(TRAIL_NODES + 2, { frame: true }); + this.trailPoints = []; + for (let i = 0; i <= TRAIL_NODES; i++) this.trailPoints.push(new Vector3()); + this.hullPoints = []; + for (let i = 0; i <= TRAIL_NODES + 2; i++) this.hullPoints.push(new Vector3()); + this.trailCount = 0; + + this.trailMaterial = new VolumetricFireMaterial(); + this.trailMesh = new Mesh(this.ribbon.geometry, this.trailMaterial); + this.trailMesh.frustumCulled = false; + this.trailMesh.matrixAutoUpdate = false; + this.trailMesh.layers.set(LAYER.VFX); + // After the non-additive smoke (10) so the plume can occlude it, before the + // additive particles (12) so embers in front are not dimmed by its + // absorption. + this.trailMesh.renderOrder = 11; + this.group.add(this.trailMesh); + + /** + * Fixed-size record pool — an impact allocates nothing. + * + * See the class comment: dice only, no dimensions. + */ + this.chunks = []; + for (let i = 0; i < MAX_CHUNKS; i++) { + this.chunks.push({ + angle: 0, // bearing of the ejecta, radians + elevation: 0, // 0..1 of the loft cone + speed: 0, // 0..1 speed jitter + size: 0, // 0..1 size jitter + spin: 0, // -1..1 tumble rate + spinAxis: new Vector3(0, 1, 0) + }); + } + + this._chunkCount = 0; + this._used = 0; + this._seed = 0; + this._tumbleAxis = new Vector3(0, 1, 0); + /** How far along the line the trail was last paid out from. */ + this._lastU = 0; + } + + /** The rock. Rebuilt when a *shape* control moves — see `_syncGeometry`. */ + _buildGeometry() { + const c = settings.meteor; + return createAsteroidGeometry({ + seed: 11.7, + detail: c.facets, + lumpiness: c.lumpiness, + noiseScale: c.lumpScale, + roughness: c.surfaceRoughness, + cuts: c.cuts, + cutDepth: c.cutDepth, + craters: c.craters, + craterDepth: c.craterDepth, + craterSize: c.craterSize + }); + } + + /** + * Regenerate the rock when a *shape* control moves. + * + * Lumps, fracture cuts and craters displace real vertices, so they cannot be + * faked in the vertex shader — the silhouette, the flat facets and the shadow + * all have to see them. A subdivision-3 asteroid is 1280 triangles, cheap + * enough to simply rebuild, which is what keeps those nine controls live. + */ + _syncGeometry() { + const c = settings.meteor; + const key = [ + Math.round(c.facets), + c.lumpiness.toFixed(3), + c.lumpScale.toFixed(3), + c.surfaceRoughness.toFixed(3), + Math.round(c.cuts), + c.cutDepth.toFixed(3), + Math.round(c.craters), + c.craterDepth.toFixed(3), + c.craterSize.toFixed(3) + ].join('|'); + if (key === this._shapeKey) return; + this._shapeKey = key; + + const previous = this.geometry; + this.geometry = this._buildGeometry(); + // The per-instance attributes are state, not shape — carry them over. + this.geometry.setAttribute('aSeed', this.seeds); + this.geometry.setAttribute('aHeat', this.heats); + this.rock.geometry = this.geometry; + previous.dispose(); + } + + createParticles() { + const particles = this.ctx.particles; + + // Embers streaming off the rock. Buoyant, so the trail rises and hangs. + this.embers = particles.get('meteor.embers', { + capacity: 4000, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.4 + }); + this.embers.uniforms.uDrag.value = 1.2; + this.embers.uniforms.uEndSize.value = 0.06; + this.embers.uniforms.uSizeIn.value = 0.05; + this.embers.uniforms.uFadeIn.value = 0.05; + this.embers.uniforms.uFadeOut.value = 0.3; + + // Sparks: velocity-stretched streaks under gravity. + this.sparks = particles.get('meteor.sparks', { + capacity: 3000, + shape: ParticleShape.STREAK, + additive: true, + stretch: true, + softFade: 0.25 + }); + this.sparks.uniforms.uDrag.value = 0.7; + this.sparks.uniforms.uEndSize.value = 0.25; + this.sparks.uniforms.uSizeIn.value = 0.02; + this.sparks.uniforms.uFadeOut.value = 0.5; + + // The smoke column. Non-additive: it has to *occlude*, which is what gives + // the trail and the crater depth. + this.smoke = particles.get('meteor.smoke', { + capacity: 2600, + shape: ParticleShape.SMOKE, + additive: false, + curl: true, + softFade: 1.2 + }); + this.smoke.uniforms.uDrag.value = 1.7; + this.smoke.uniforms.uEndSize.value = 3.6; + this.smoke.uniforms.uSizeIn.value = 0.12; + this.smoke.uniforms.uFadeIn.value = 0.18; + this.smoke.uniforms.uFadeOut.value = 0.32; + + // Grit blown off the floor. Lit, so it reads as rock rather than as light. + this.debris = particles.get('meteor.debris', { + capacity: 1800, + shape: ParticleShape.CHIP, + additive: false, + lit: true, + softFade: 0.25 + }); + this.debris.uniforms.uDrag.value = 0.25; + this.debris.uniforms.uEndSize.value = 0.85; + this.debris.uniforms.uFadeOut.value = 0.72; + + this.emberEmitter = new RateEmitter(); + this.sparkEmitter = new RateEmitter(); + this.smokeEmitter = new RateEmitter(); + } + + /* ------------------------------------------------------------------ */ + /* Timing */ + /* ------------------------------------------------------------------ */ + + get instanceCount() { + return this._used; + } + + /** The crater burns for `lifetime`, then everything withdraws over `fadeTime`. */ + get impactDuration() { + return Math.max(0.2, settings.meteor.lifetime * settings.global.lifetime); + } + + get fadeDuration() { + return Math.max(0.2, settings.meteor.fadeTime); + } + + /** Fire gutters where ice glints — a fast, uneven guttering. */ + lightShimmer() { + const c = settings.meteor; + const rate = Math.max(0.1, c.lightFlickerSpeed); + const wobble = Math.sin(this.age * rate) * Math.sin(this.age * rate * 0.37 + 1.7); + return 1 - saturate(c.lightFlicker) * (0.5 + 0.5 * wobble); + } + + /* ------------------------------------------------------------------ */ + /* The trajectory — every metre resolved from live settings */ + /* ------------------------------------------------------------------ */ + + /** + * Where the meteor leaves the caster, in world space. + * + * The base class puts `origin` on the floor because that is what the aim + * indicator targets; a rock is thrown from a hand, so the offsets are applied + * here rather than baked into the cast. + */ + _launchPoint(out) { + const c = settings.meteor; + out + .copy(this.origin) + .addScaledVector(this.direction, c.handForward) + .addScaledVector(this.side, c.handSide); + out.y = c.handHeight; + return out; + } + + /** A point on the flight path, `s` from 0 (the hand) to 1 (the target). */ + _arcPoint(s, out) { + const c = settings.meteor; + const t = saturate(s); + out + .copy(this.origin) + .addScaledVector(this.direction, lerp(c.handForward, this.length, t)) + .addScaledVector(this.side, c.handSide * (1 - t)); + out.y = + lerp(c.handHeight, c.endHeight, t) + + c.arc * Math.pow(Math.sin(t * Math.PI), Math.max(0.05, c.arcCurve)); + return out; + } + + /** + * Unit direction of travel at `s`. + * + * Differentiated numerically off `_arcPoint` rather than derived by hand: the + * arc is a live slider, and a hand-written derivative is one more thing that + * silently stops matching it. + */ + _headingAt(s, out) { + const eps = 0.01; + this._arcPoint(s + eps, _ahead); + this._arcPoint(s - eps, _behind); + out.subVectors(_ahead, _behind); + if (out.lengthSq() < 1e-8) return out.copy(this.direction); + return out.normalize(); + } + + /** Radius of the meteor, metres. */ + _radius() { + return Math.max(0.02, settings.meteor.radius); + } + + /** How hot the rock is, 0 at the hand → 1 at the target. */ + _charge() { + const c = settings.meteor; + if (this.phase !== AbilityPhase.TRAVEL) return 1; + return Math.pow(saturate(this.u), Math.max(0.05, c.chargeCurve)); + } + + /* ------------------------------------------------------------------ */ + /* The debris */ + /* ------------------------------------------------------------------ */ + + _chunkRadius(record, c) { + return Math.max(0.01, this._radius() * c.chunkScale * lerp(0.45, 1.15, record.size)); + } + + /** Unit direction a chunk is thrown in — a loft cone, biased downrange. */ + _chunkDirection(record, c, out) { + const elevation = lerp(0.12, 1.4, record.elevation) * Math.max(0, c.chunkLoft); + const flat = Math.cos(elevation); + out.set(0, Math.sin(elevation), 0); + out.addScaledVector(this.side, Math.cos(record.angle) * flat); + out.addScaledVector(this.direction, Math.sin(record.angle) * flat + c.chunkForward); + if (out.lengthSq() < 1e-8) out.set(0, 1, 0); + return out.normalize(); + } + + /** + * Where a chunk is `elapsed` seconds after the impact. + * + * Closed-form ballistics rather than an integration: the whole flight is a + * function of the live settings, so dragging `chunkSpeed` re-throws debris + * that has already landed instead of only changing what it does next. The + * landing time is solved for so a chunk settles on the floor rather than + * sinking through it. + * + * @returns {number} the chunk's own clock, frozen at the moment it lands + */ + _chunkFlight(record, c, elapsed, out) { + const speed = c.chunkSpeed * lerp(0.45, 1.35, record.speed) * settings.global.particleSpeed; + const gravity = Math.min(-0.01, c.chunkGravity); + const rest = this._chunkRadius(record, c) * 0.8; + + this._chunkDirection(record, c, _dir); + this._arcPoint(1, _impact); + + const vy = _dir.y * speed; + const y0 = _impact.y; + // 0.5·g·t² + vy·t + (y0 − rest) = 0, positive root. + const discriminant = Math.max(0, vy * vy - 2 * gravity * (y0 - rest)); + const landing = (vy + Math.sqrt(discriminant)) / -gravity; + const t = Math.min(elapsed, landing); + + out.copy(_impact).addScaledVector(_dir, speed * t); + out.y = Math.max(rest, y0 + vy * t + 0.5 * gravity * t * t); + return t; + } + + /** Seconds since the meteor landed. */ + get _sinceImpact() { + if (this.phase === AbilityPhase.FADE) return this.impactDuration + this.fadeTime; + return this.phase === AbilityPhase.IMPACT ? this.impactTime : 0; + } + + /* ------------------------------------------------------------------ */ + /* Casting */ + /* ------------------------------------------------------------------ */ + + onSpawn() { + this.emberEmitter.reset(); + this.sparkEmitter.reset(); + this.smokeEmitter.reset(); + this._lastU = 0; + this._chunkCount = 0; + this._used = 1; + + // The only things a cast captures: a seed, a tumble axis and the chunk dice. + this._seed = Math.random() * 100; + this.trailMaterial.uniforms.uSeed.value = this._seed * 0.2; + this.trailMaterial.uniforms.uHeadFade.value = 0; + + const phi = Math.acos(randRange(-1, 1)); + const theta = Math.random() * TAU; + const sinPhi = Math.sin(phi); + this._tumbleAxis.set(sinPhi * Math.cos(theta), Math.cos(phi), sinPhi * Math.sin(theta)); + + for (const record of this.chunks) { + record.angle = Math.random() * TAU; + record.elevation = Math.random(); + record.speed = Math.random(); + record.size = Math.random(); + record.spin = randRange(-1, 1); + const p = Math.acos(randRange(-1, 1)); + const a = Math.random() * TAU; + const s = Math.sin(p); + record.spinAxis.set(s * Math.cos(a), Math.cos(p), s * Math.sin(a)); + } + + this.rock.count = 1; + + this._syncUniforms(0); + this._updateRock(0); + this._updateTrail(0); + this._launchFx(); + } + + /* ------------------------------------------------------------------ */ + /* Per-frame state */ + /* ------------------------------------------------------------------ */ + + /** + * Push the live settings into every material and particle system. + * @param {number} burn 0 while the rock is flying, → 1 as the trail dies + */ + _syncUniforms(burn) { + const c = settings.meteor; + const g = settings.global; + + this._syncGeometry(); + + const s = this.phase === AbilityPhase.TRAVEL ? this.u : 1; + this._headingAt(s, _heading); + this.material.userData.sync(this._charge(), _heading); + + // The volume is no longer being fed once the rock is gone: it thins out, + // goes cold and tears apart rather than simply turning invisible. `sync()` + // has just written the editor's values, so these override on top of it. + this.trailMaterial.sync(); + const u = this.trailMaterial.uniforms; + u.uOpacity.value = lerp(c.trailOpacity * g.opacity, 0, burn); + u.uHeadFade.value = burn * 0.35; + u.uDetachment.value = lerp(c.trailDetachment, 1.1, burn); + + /* --- the four particle systems --- */ + this.embers.setGradient( + getColor(c.colorEmberA), + getColor(c.colorEmberB), + getColor(c.colorEmberC), + getColor(c.colorEmberD) + ); + this.embers.uniforms.uGravity.value.set(0, c.emberRise, 0); + this.embers.uniforms.uSizeScale.value = c.emberSize * g.particleSize * 7; + this.embers.uniforms.uLifeScale.value = c.emberLifetime * 0.5 * g.particleLifetime; + this.embers.uniforms.uSpeedScale.value = g.particleSpeed; + this.embers.uniforms.uOpacity.value = g.opacity; + this.embers.uniforms.uGlow.value = c.emberGlow * g.glow; + this.embers.uniforms.uTurbulence.value = c.emberTurbulence * g.turbulence; + + this.sparks.setGradient( + getColor(c.colorSparkA), + getColor(c.colorSparkB), + getColor(c.colorSparkC), + getColor(c.colorSparkD) + ); + this.sparks.uniforms.uGravity.value.set(0, c.sparkGravity, 0); + this.sparks.uniforms.uSizeScale.value = c.sparkSize * g.particleSize * 7; + this.sparks.uniforms.uLifeScale.value = c.sparkLifetime * 0.5 * g.particleLifetime; + this.sparks.uniforms.uSpeedScale.value = g.particleSpeed; + this.sparks.uniforms.uOpacity.value = g.opacity; + this.sparks.uniforms.uGlow.value = c.emberGlow * 0.8 * g.glow; + this.sparks.uniforms.uStretch.value = c.sparkStretch; + + this.smoke.setGradient( + getColor(c.colorSmokeA), + getColor(c.colorSmokeB), + getColor(c.colorSmokeC), + getColor(c.colorSmokeD) + ); + this.smoke.uniforms.uGravity.value.set(0, c.smokeRise, 0); + this.smoke.uniforms.uSizeScale.value = c.smokeSize * g.particleSize; + this.smoke.uniforms.uLifeScale.value = c.smokeLifetime * 0.5 * g.particleLifetime; + this.smoke.uniforms.uSpeedScale.value = c.smokeSpeed * g.particleSpeed; + this.smoke.uniforms.uOpacity.value = c.smokeOpacity * g.opacity; + this.smoke.uniforms.uTurbulence.value = 0.45 * g.turbulence; + + this.debris.setGradient( + getColor(c.colorDebrisA), + getColor(c.colorDebrisB), + getColor(c.colorDebrisC), + getColor(c.colorDebrisD) + ); + this.debris.uniforms.uGravity.value.set(0, c.debrisGravity, 0); + this.debris.uniforms.uSizeScale.value = c.debrisSize * g.particleSize * 7; + this.debris.uniforms.uLifeScale.value = g.particleLifetime; + this.debris.uniforms.uSpeedScale.value = g.particleSpeed; + this.debris.uniforms.uOpacity.value = g.opacity; + } + + /** + * Rebuild every instance matrix from the live settings. + * @param {number} retract 0..1 — the debris withdrawing into the floor + */ + _updateRock(retract) { + const c = settings.meteor; + const travelling = this.phase === AbilityPhase.TRAVEL; + + /* --- instance 0: the meteor itself --- */ + if (travelling) { + this._arcPoint(this.u, _dummy.position); + _axis.copy(this._tumbleAxis); + _spin.setFromAxisAngle(_axis, this.age * c.spin); + _dummy.quaternion.copy(_spin); + _dummy.scale.setScalar(this._radius()); + this.heats.array[0] = 1; + } else { + // It shattered. Park it rather than drawing a degenerate matrix. + _dummy.position.set(0, -999, 0); + _dummy.quaternion.identity(); + _dummy.scale.setScalar(0.0001); + this.heats.array[0] = 0; + } + _dummy.updateMatrix(); + this.rock.setMatrixAt(0, _dummy.matrix); + + /* --- the chunks --- */ + const since = this._sinceImpact; + const cool = Math.max(0.05, c.chunkCool); + + for (let i = 0; i < this._chunkCount; i++) { + const record = this.chunks[i]; + const radius = this._chunkRadius(record, c); + const flight = this._chunkFlight(record, c, since, _pos); + + if (retract > 0) _pos.y -= Easing.inCubic(retract) * (radius * 2 + 0.5); + + _dummy.position.copy(_pos); + _axis.copy(record.spinAxis); + _spin.setFromAxisAngle(_axis, record.spin * c.chunkSpin * flight); + _dummy.quaternion.copy(_spin); + _dummy.scale.setScalar(radius); + _dummy.updateMatrix(); + + this.rock.setMatrixAt(1 + i, _dummy.matrix); + this.heats.array[1 + i] = saturate(1 - since / cool); + } + + this._used = travelling ? 1 : 1 + this._chunkCount; + this.rock.count = this._used; + this.rock.instanceMatrix.needsUpdate = true; + this.heats.needsUpdate = true; + } + + /** + * Rebuild the camera-facing proxy hull the raymarcher renders inside. + * + * Its centre line is sampled straight off `_arcPoint` rather than recorded + * from past frames, so the trail is a pure function of the trajectory settings + * — which is what lets `arc`, `endHeight` and `trailSpan` reshape fire that is + * already in the air, with the clock paused. + * + * The hull only has to *contain* the volume: it is a billboard strip whose + * half-width follows the same radius profile as the density field, widened by + * the volume's upward stretch and by how far the shred can push density + * outward, and padded by a cap radius at both ends. A hull that is even + * slightly too narrow shows up as a dead straight edge cutting across the + * flame, which is the one way this technique fails visibly. + * + * @param {number} burn 0 while the rock is flying, 0 → 1 as the trail dies + */ + _updateTrail(burn) { + const c = settings.meteor; + const head = this.phase === AbilityPhase.TRAVEL ? this.u : 1; + const span = Math.max(0.05, c.trailSpan) / Math.max(0.1, this.length); + // Once the rock is gone the tail eats its way up to the head. + const tail = saturate(head - span * (1 - burn)); + + const visible = (head - tail) * this.length > 0.06; + this.trailMesh.visible = visible; + if (!visible) { + this.ribbon.clear(); + this.trailCount = 0; + return; + } + + const count = TRAIL_NODES + 1; + for (let i = 0; i < count; i++) { + this._arcPoint(lerp(tail, head, i / (count - 1)), this.trailPoints[i]); + } + this.trailCount = count; + + let length = 0; + for (let i = 1; i < count; i++) length += this.trailPoints[i].distanceTo(this.trailPoints[i - 1]); + if (length < 1e-3) { + this.trailMesh.visible = false; + this.ribbon.clear(); + this.trailCount = 0; + return; + } + + // The volume thins out as it dies rather than only fading. + const radius = Math.max(0.01, c.trailWidth) * (1 - burn * 0.65); + const headSize = c.trailHeadSize; + const wakeSpread = c.trailWakeSpread; + const headRadius = radius * headSize; + // How far outside the nominal tube the shred *and* the silhouette bulge can + // push density. Read live: both are editable while a cast is in flight. + const reach = fireHullReach(); + const tailPad = radius * radiusProfile(0, 1, wakeSpread) * reach; + const headPad = headRadius * reach; + + const hull = this.hullPoints; + // Cap padding along the arc's own heading at each end. + this._headingAt(tail, _heading); + hull[0].copy(this.trailPoints[0]).addScaledVector(_heading, -tailPad); + + // The wake is hot gas: the further behind the rock it is, the longer it has + // had to climb. Lifting the hull's centre line is enough — the raymarcher + // reads its axis straight off this geometry. + for (let i = 0; i < count; i++) { + const age = 1 - i / (count - 1); + hull[i + 1].copy(this.trailPoints[i]); + hull[i + 1].y += c.trailRise * Math.pow(age, 1.5); + } + hull[0].y += c.trailRise; + + this._headingAt(head, _heading); + hull[count + 1].copy(this.trailPoints[count - 1]).addScaledVector(_heading, headPad); + + const arcLength = tailPad + length + headPad; + + const u = this.trailMaterial.uniforms; + u.uRadius.value = radius; // overrides sync() so the burn-out can shrink it + u.uStreamLength.value = length; + u.uArcLength.value = arcLength; + u.uTailPad.value = tailPad; + + // The density field is stretched upward *and* pushed outward by the noise, + // so the hull has to be considerably wider than the tube is round. + const cover = 1.08 * reach * Math.max(1, c.trailPlume); + + this.ribbon.build(hull, { + count: count + 2, + width: 2 * headRadius * cover, + mode: RibbonMode.BILLBOARD, + cameraPosition: this.ctx.camera.position, + widthProfile: (t) => + radiusProfile(saturate((t * arcLength - tailPad) / length), headSize, wakeSpread) / headSize + }); + } + + /* ------------------------------------------------------------------ */ + /* Feedback */ + /* ------------------------------------------------------------------ */ + + /** The flash at the caster's hand as the rock leaves it. */ + _launchFx() { + const c = settings.meteor; + const g = settings.global; + + this._launchPoint(_pos); + + // Off by default: an expanding shell this close to the camera-facing side of + // the body reads as a ball stuck to the caster rather than as a flare, and + // the sparks below plus the screen flash already sell the release. Dial + // `muzzleSize` up if you want it back. + if (c.muzzleSize > 0) { + this.ctx.bursts.spawn(BurstMode.FIRE, _pos, { + radius: c.muzzleSize * 0.25, + endRadius: c.muzzleSize * g.explosionIntensity, + life: 0.4, + intensity: c.muzzleIntensity, + opacity: 0.85, + fresnel: 1.2, + displace: 0.45, + colorA: getColor(c.colorHot), + colorB: getColor(c.colorFlameMid), + colorC: getColor(c.colorFlameEdge) + }); + } + + _emit.position = _pos; + _emit.radius = 0.22; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.5).setY(0.7).normalize(); + _emit.speed = c.sparkSpeed * 0.9; + _emit.speedVariance = 0.8; + _emit.spread = 0.9; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.16; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime * 0.8; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = frame.uTime.value; + this.sparks.emit(Math.round(30 * g.particleCount), _emit); + + this.ctx.flash.trigger(getColor(c.colorCastFlash), c.castFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 0.6 * g.explosionIntensity; + } + + /** + * Embers, sparks and smoke shed along the section of arc covered this frame. + * + * Spread over several points between the last frame's position and this one: + * a meteor moving at 20 m/s covers a metre between frames, and firing every + * particle from the head leaves the trail visibly beaded. + */ + _trailFx(dt) { + const c = settings.meteor; + const g = settings.global; + const time = frame.uTime.value; + const radius = this._radius(); + + // The trail is pushed off the *back* of the rock, and the rock is gone. + this._headingAt(this.u, _heading); + _back.copy(_heading).multiplyScalar(-1); + + let emberCount = Math.round(this.emberEmitter.tick(dt, c.emberRate) * g.particleCount); + if (emberCount > 0) { + _emit.direction = _dir.copy(_back).setY(_back.y + 0.35).normalize(); + _emit.speed = c.emberSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.85; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.13; + _emit.sizeVariance = 0.6; + _emit.life = c.emberLifetime; + _emit.lifeVariance = 0.45; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + const batches = Math.min(emberCount, TRAIL_BATCHES); + const per = Math.ceil(emberCount / batches); + while (emberCount > 0) { + this._arcPoint(lerp(this._lastU, this.u, Math.random()), _pos); + _emit.position = _pos; + _emit.radius = radius * 0.9; + this.embers.emit(Math.min(per, emberCount), _emit); + emberCount -= per; + } + } + + const sparkCount = Math.round(this.sparkEmitter.tick(dt, c.sparkRate) * g.particleCount); + if (sparkCount > 0) { + this._arcPoint(lerp(this._lastU, this.u, Math.random()), _pos); + _emit.position = _pos; + _emit.radius = radius * 0.7; + _emit.direction = _dir.copy(_back).setY(_back.y + 0.2).normalize(); + _emit.speed = c.sparkSpeed; + _emit.speedVariance = 0.85; + _emit.spread = 0.95; + _emit.size = 0.15; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.55; + _emit.spin = 0; + _emit.time = time; + this.sparks.emit(sparkCount, _emit); + } + + const smokeCount = Math.round(this.smokeEmitter.tick(dt, c.smokeRate) * g.particleCount); + if (smokeCount > 0) { + this._arcPoint(lerp(this._lastU, this.u, Math.random()), _pos); + _emit.position = _pos; + _emit.radius = radius * 1.1; + _emit.direction = _dir.copy(_back); + _emit.speed = c.smokeSpeed; + _emit.speedVariance = 0.6; + _emit.spread = 0.75; + _emit.size = 0.85; + _emit.sizeVariance = 0.5; + _emit.life = c.smokeLifetime; + _emit.lifeVariance = 0.4; + _emit.spin = 0.5; + _emit.time = time; + this.smoke.emit(smokeCount, _emit); + } + } + + /** The crater keeps burning after the rock is gone. */ + _craterFx(dt, scale) { + const c = settings.meteor; + const g = settings.global; + const time = frame.uTime.value; + + this._arcPoint(1, _impact); + + const emberCount = Math.round(this.emberEmitter.tick(dt, c.emberRate * 0.5 * scale) * g.particleCount); + if (emberCount > 0) { + _emit.position = _pos.copy(_impact).setY(0.25); + _emit.radius = c.scorchRadius * 0.9; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.emberSpeed * 0.8; + _emit.speedVariance = 0.75; + _emit.spread = 0.9; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.14; + _emit.sizeVariance = 0.6; + _emit.life = c.emberLifetime * 1.2; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.embers.emit(emberCount, _emit); + } + + const smokeCount = Math.round(this.smokeEmitter.tick(dt, c.smokeRate * 0.8 * scale) * g.particleCount); + if (smokeCount > 0) { + _emit.position = _pos.copy(_impact).setY(0.2); + _emit.radius = c.scorchRadius * 1.1; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.smokeSpeed * 1.2; + _emit.speedVariance = 0.6; + _emit.spread = 0.85; + _emit.size = 1.3; + _emit.sizeVariance = 0.5; + _emit.life = c.smokeLifetime * 1.4; + _emit.lifeVariance = 0.4; + _emit.spin = 0.6; + _emit.time = time; + this.smoke.emit(smokeCount, _emit); + } + } + + /* ------------------------------------------------------------------ */ + /* Phases */ + /* ------------------------------------------------------------------ */ + + onTravel(dt) { + this._syncUniforms(0); + + // The light rides the meteor, not the floor under it. `advance()` has + // already put `position` on the ground line, so lift it onto the arc. + this._arcPoint(this.u, this.position); + + this._updateRock(0); + this._updateTrail(0); + this._trailFx(dt); + this._lastU = this.u; + + this.ctx.shake.rumble(settings.meteor.rumble * settings.global.cameraShake, dt); + } + + onImpact() { + const c = settings.meteor; + const g = settings.global; + const time = frame.uTime.value; + const scale = c.burstSize * g.explosionIntensity; + + this._chunkCount = Math.min(MAX_CHUNKS, Math.max(0, Math.round(c.chunkCount))); + + const hot = getColor(c.colorHot); + const mid = getColor(c.colorFlameMid); + const edge = getColor(c.colorFlameEdge); + + this._arcPoint(1, _impact); + _pos.copy(_impact).setY(_impact.y + scale * 0.28); + + /* the fireball, and a faster inner flash that sells the detonation */ + this.ctx.bursts.spawn(BurstMode.FIRE, _pos, { + radius: scale * 0.22, + endRadius: scale, + life: 0.9, + intensity: c.burstIntensity, + opacity: 0.95, + fresnel: 1.1, + displace: 0.55, + turbulence: c.burstTurbulence * 0.6, + colorA: hot, + colorB: mid, + colorC: edge + }); + this.ctx.bursts.spawn(BurstMode.FIRE, _pos, { + radius: scale * 0.1, + endRadius: scale * 0.55, + life: 0.35, + intensity: c.burstIntensity * 2.2, + opacity: 1.0, + displace: 0.2, + colorA: hot, + colorB: hot, + colorC: mid + }); + + /* the ring that snaps outward across the floor */ + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _impact, { + radius: c.shockRadius * g.explosionIntensity, + life: 0.65, + width: 0.07, + intensity: 1.2, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + /* + * The crater: real molten cracks torn through the floor. Not a decal — a + * grown network of ribbons with basalt heaved up along its lips. See + * `effects/GroundFissures.js`. + */ + this.ctx.fissures.spawn(_impact, { + radius: c.fissureRadius * g.explosionIntensity, + life: c.fissureLife + }); + + /* a burnt patch under the lot */ + this.ctx.decals.spawn(DecalType.SCORCH, _impact, { + radius: c.scorchRadius * g.explosionIntensity, + life: c.scorchLife, + intensity: c.scorchIntensity, + colorA: getColor(c.colorScorch), + colorB: getColor(c.colorCrack), + height: 0.015 + }); + + /* everything thrown out of it */ + _emit.position = _pos; + _emit.radius = scale * 0.22; + _emit.direction = _dir.set(0, 0.6, 0); + _emit.speed = c.emberSpeed * 2.6; + _emit.speedVariance = 0.8; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.2; + _emit.sizeVariance = 0.7; + _emit.life = c.emberLifetime * 1.5; + _emit.lifeVariance = 0.55; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.embers.emit(Math.round(c.burstEmbers * g.particleCount), _emit); + + _emit.speed = c.sparkSpeed * 2.4; + _emit.size = 0.22; + _emit.life = c.sparkLifetime * 1.6; + this.sparks.emit(Math.round(c.burstSparks * g.particleCount), _emit); + + _emit.position = _pos.copy(_impact).setY(0.25); + _emit.radius = c.scorchRadius * 0.7; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.debrisSpeed; + _emit.speedVariance = 0.75; + _emit.spread = 0.85; + _emit.size = 0.15; + _emit.life = c.debrisLifetime; + _emit.spin = 9; + this.debris.emit(Math.round(c.burstDebris * g.particleCount), _emit); + + _emit.speed = c.smokeSpeed * 2.6; + _emit.spread = 1.0; + _emit.size = 1.7; + _emit.sizeVariance = 0.5; + _emit.life = c.smokeLifetime * 1.6; + _emit.spin = 0.6; + this.smoke.emit(Math.round(c.burstSmoke * g.particleCount), _emit); + + this.ctx.shake.add( + c.impactShake * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 22 + ); + this.ctx.flash.trigger(getColor(c.colorFlash), c.impactFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 2.4 * g.explosionIntensity; + + this._updateRock(0); + this._updateTrail(0); + } + + onFade(dt, t) { + const c = settings.meteor; + + // The trail is no longer being fed, so it burns back from the head. + const burn = saturate(this._sinceImpact / Math.max(0.05, c.trailBurnout)); + this._syncUniforms(burn); + + // The light drops onto the crater and stays there. + this._arcPoint(1, this.position); + + let retract = 0; + if (this.phase === AbilityPhase.FADE) { + retract = saturate((this.fadeTime - c.chunkLinger) / Math.max(0.05, c.chunkSink)); + } + + this._updateRock(retract); + this._updateTrail(burn); + + // The crater burns while the debris lies there, and stops as it withdraws. + // `t` runs 0..1 through the burn, then 1..2 while everything is cleared. + if (retract < 0.6) this._craterFx(dt, t <= 1 ? 1 - t * 0.5 : 0.25); + } + + onDestroy() { + this._chunkCount = 0; + this._used = 0; + this.trailCount = 0; + this.rock.count = 0; + this.ribbon.clear(); + this.trailMesh.visible = false; + } + + dispose() { + this.geometry.dispose(); + this.ribbon.dispose(); + this.rock.dispose(); + this.material.dispose(); + this.trailMaterial.dispose(); + super.dispose(); + } +} diff --git a/src/skillshot/abilities/SnareAbility.js b/src/skillshot/abilities/SnareAbility.js new file mode 100644 index 0000000..ec2348e --- /dev/null +++ b/src/skillshot/abilities/SnareAbility.js @@ -0,0 +1,856 @@ +import { Mesh, PlaneGeometry, Vector3 } from 'three'; +import { Ability, AbilityPhase } from './Ability.js'; +import { createSnareCageMaterial, createSnareFieldMaterial, SnarePass } from '../materials/SnareMaterial.js'; +import { createBoltRibbonGeometry } from '../ProceduralGeometry.js'; +import { ParticleShape } from '../../particles/ParticleSystem.js'; +import { RateEmitter } from '../../particles/ParticleEngine.js'; +import { DecalType } from '../../effects/GroundDecals.js'; +import { BurstMode } from '../../effects/BurstSphere.js'; +import { LAYER } from '../../core/Layers.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, Easing, randRange } from '../../utils/math.js'; + +/** + * Hard ceilings per role. The editor's sliders clamp here, and their sum is the + * instance count the strip is built with — one buffer, allocated once. + */ +const MAX_LEASH = 6; +const MAX_COLUMN = 16; +const MAX_TENDRIL = 20; +const MAX_RIM = 14; +const MAX_FILAMENTS = MAX_LEASH + MAX_COLUMN + MAX_TENDRIL + MAX_RIM; + +/** + * Samples along one filament. The ceiling on how fine a kink can be: anything + * higher-frequency than one kink per two nodes just aliases. + */ +const NODES = 64; + +/** How many points one frame's sparks are split between. A single origin reads as a starburst. */ +const SPARK_BATCHES = 5; +/** ... and the same for the updraft, which would otherwise be a rotating hose. */ +const UPDRAFT_BATCHES = 4; + +const TAU = Math.PI * 2; + +const _emit = {}; +const _pos = new Vector3(); +const _dir = new Vector3(); + +/** + * SNARE — the first **far cast**: a trap planted at a point. + * + * The other four abilities are shots fired along a line. This one is aimed with + * a circle (see `effects/ZoneIndicator.js`), and the deal that circle strikes + * with the player is the whole design of the ability: the thick boundary it + * draws before the click is where the tendrils stop, where the rim arcs run and + * where the field burns after it. One number, `zoneRadius`, drives the + * indicator and every part of the effect, so dragging it re-scales the promise + * and the payoff together — including on a trap that is already standing. + * + * Four beats, though only three phases: + * + * 1. **travel** — a leash of current is whipped out across the floor toward + * the point, laying burns as it goes. + * 2. **snap** — the first `snapTime` of the impact phase: the leash is + * dropped, the ring slams open past its radius and pulls back onto it, and + * the column tears up out of the middle. + * 3. **hold** — the rest of `lifetime`: the cage re-strikes, tendrils crawl, + * rim arcs travel, and the air over the whole disc is hauled up into the + * pillar. + * 4. **collapse** — `fadeTime`: the pillar thins to a thread and goes out. + * + * Everything is generated. The cage is one instanced ribbon strip whose four + * roles are decided in the vertex shader (`materials/SnareMaterial.js`), the + * field is one quad of signed distance and warped noise, the burns are the + * shared decals, and the sparks, updraft, smoke and chips are GPU particles. + * + * **The rule that makes the editor work.** A cast captures one number — a seed + * — and a handful of timestamps. Not one metre, radian or second is recorded: + * the footprint, the column, the tendrils, the rim arcs and the field are all + * resolved against `settings.snare` inside the update loop, which runs on a + * zero-length frame too. + */ +export class SnareAbility extends Ability { + constructor(context) { + super('snare', context); + } + + /* ------------------------------------------------------------------ */ + /* Construction */ + /* ------------------------------------------------------------------ */ + + createShaders() { + this.geometry = createBoltRibbonGeometry(NODES, MAX_FILAMENTS); + + // Two passes over the same filaments: a wide, soft halo underneath and the + // hot cores on top. Drawing the glow as real ribbon rather than leaving it + // to bloom is what keeps it attached to every kink. + this.glowMaterial = createSnareCageMaterial(SnarePass.GLOW); + this.coreMaterial = createSnareCageMaterial(SnarePass.CORE); + this.cageMaterials = [this.glowMaterial, this.coreMaterial]; + + this.meshes = []; + for (const [index, material] of this.cageMaterials.entries()) { + const mesh = new Mesh(this.geometry, material); + mesh.frustumCulled = false; + mesh.matrixAutoUpdate = false; + mesh.layers.set(LAYER.VFX); + mesh.renderOrder = 11 + index * 2; + this.group.add(mesh); + this.meshes.push(mesh); + } + + /* ---- the field on the floor ---- */ + this.fieldGeometry = new PlaneGeometry(1, 1, 1, 1).rotateX(-Math.PI / 2); + this.fieldMaterial = createSnareFieldMaterial(); + this.field = new Mesh(this.fieldGeometry, this.fieldMaterial); + this.field.name = 'SnareField'; + this.field.layers.set(LAYER.VFX); + this.field.renderOrder = 7; // over the decals, under the cage + this.field.frustumCulled = false; + this.group.add(this.field); + + /** Re-rolled per cast so no two traps draw the same shape. */ + this._seed = 0; + /** Seconds since the ring started opening. Drives the snap, nothing else. */ + this._openTime = 0; + /** Metres of leash travel already paid out in ground burns. */ + this._burnDistance = 0; + this._filamentCount = 1; + + // Scratch state handed to the materials each frame. One object, reused — + // syncing the trap allocates nothing. + this._state = { + centre: new Vector3(), + hand: new Vector3(), + front: new Vector3(), + radius: 1, + height: 1, + fade: 1, + seed: 0, + counts: { leash: 0, column: 0, tendril: 0, rim: 0 } + }; + this._fieldState = { radius: 1, quadSize: 1, fade: 1, seed: 0 }; + } + + createParticles() { + const particles = this.ctx.particles; + + // Sparks: velocity-stretched streaks under gravity, thrown off the cage. + this.sparks = particles.get('snare.sparks', { + capacity: 4000, + shape: ParticleShape.STREAK, + additive: true, + stretch: true, + softFade: 0.25 + }); + this.sparks.uniforms.uDrag.value = 1.4; + this.sparks.uniforms.uEndSize.value = 0.25; + this.sparks.uniforms.uSizeIn.value = 0.02; + this.sparks.uniforms.uFadeIn.value = 0.03; + this.sparks.uniforms.uFadeOut.value = 0.45; + + // The updraft — this ability's signature system. Motes picked up off the + // whole disc and hauled inward and up the column, which is the read that + // says the trap is pulling on the air rather than sitting in it. + this.updraft = particles.get('snare.updraft', { + capacity: 3000, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.4 + }); + this.updraft.uniforms.uDrag.value = 0.9; + this.updraft.uniforms.uEndSize.value = 0.2; + this.updraft.uniforms.uSizeIn.value = 0.05; + this.updraft.uniforms.uFadeIn.value = 0.1; + this.updraft.uniforms.uFadeOut.value = 0.45; + + // Haze scoured off the burnt floor. Non-additive so it genuinely occludes. + this.smoke = particles.get('snare.smoke', { + capacity: 2000, + shape: ParticleShape.SMOKE, + additive: false, + curl: true, + softFade: 1.1 + }); + this.smoke.uniforms.uDrag.value = 1.8; + this.smoke.uniforms.uEndSize.value = 3.0; + this.smoke.uniforms.uSizeIn.value = 0.12; + this.smoke.uniforms.uFadeIn.value = 0.16; + this.smoke.uniforms.uFadeOut.value = 0.3; + + // Chips torn off the floor inside the ring. + this.debris = particles.get('snare.debris', { + capacity: 1600, + shape: ParticleShape.CHIP, + additive: false, + lit: true, + softFade: 0.25 + }); + this.debris.uniforms.uDrag.value = 0.25; + this.debris.uniforms.uEndSize.value = 0.8; + this.debris.uniforms.uFadeOut.value = 0.7; + + this.sparkEmitter = new RateEmitter(); + this.updraftEmitter = new RateEmitter(); + this.smokeEmitter = new RateEmitter(); + this.debrisEmitter = new RateEmitter(); + this.arcEmitter = new RateEmitter(); + this.pulseEmitter = new RateEmitter(); + this.ringEmitter = new RateEmitter(); + } + + /* ------------------------------------------------------------------ */ + /* Timing */ + /* ------------------------------------------------------------------ */ + + get instanceCount() { + return this._filamentCount * this.meshes.length; + } + + /** The trap snaps open, then stands. */ + get impactDuration() { + return Math.max(0.05, settings.snare.lifetime * settings.global.lifetime); + } + + get fadeDuration() { + return Math.max(0.05, settings.snare.fadeTime); + } + + /** A cage gutters where ice glints — a hard, quantised stutter. */ + lightShimmer() { + const c = settings.snare; + const step = Math.floor(this.age * Math.max(1, c.lightFlickerSpeed)); + const noise = Math.abs(Math.sin(step * 127.1) * 43758.5453) % 1; + return 1 - saturate(c.lightFlicker) * noise; + } + + /* ------------------------------------------------------------------ */ + /* Geometry — every metre resolved from live settings */ + /* ------------------------------------------------------------------ */ + + /** Where the leash leaves the caster, in world space. */ + _handPoint(out) { + const c = settings.snare; + out + .copy(this.origin) + .addScaledVector(this.direction, c.handForward) + .addScaledVector(this.side, c.handSide); + out.y = c.handHeight; + return out; + } + + /** The centre of the trap — the far end of the aimed line. */ + _centrePoint(out) { + return this.pointAt(1, out).setY(0); + } + + /** The leash's travelling tip. Pinned to the centre once it has arrived. */ + _frontPoint(out) { + const c = settings.snare; + const u = this.phase === AbilityPhase.TRAVEL ? this.u : 1; + return this.pointAt(u, out).setY(c.leashCling); + } + + /** + * How far open the ring is, 0..1, and how far up the column has climbed. + * + * Both are pure functions of `_openTime` against the live `snapTime`, so the + * snap re-times itself if the slider moves mid-cast. The ring overshoots its + * radius and pulls back onto it — the same curve the indicator snaps out on, + * which is what makes the trap look like the circle *becoming* real rather + * than a second, unrelated animation. + */ + _openAmount() { + const snap = Math.max(0.01, settings.snare.snapTime); + const t = saturate(this._openTime / snap); + const bump = Math.sin(Math.PI * Math.pow(t, 1.7)); + return Easing.outCubic(t) * (1 + 0.16 * bump); + } + + _climbAmount() { + // The pillar is slower off the mark than the ring: the ground goes first, + // then the air breaks down over it. + const snap = Math.max(0.01, settings.snare.snapTime) * 1.7; + return Easing.outCubic(saturate(this._openTime / snap)); + } + + /** The live footprint, metres. What the indicator measured out. */ + get radius() { + return Math.max(0.05, settings.snare.zoneRadius); + } + + /* ------------------------------------------------------------------ */ + /* Casting */ + /* ------------------------------------------------------------------ */ + + onSpawn() { + this.sparkEmitter.reset(); + this.updraftEmitter.reset(); + this.smokeEmitter.reset(); + this.debrisEmitter.reset(); + this.arcEmitter.reset(); + this.pulseEmitter.reset(); + this.ringEmitter.reset(); + + this._burnDistance = 0; + this._openTime = 0; + // The one thing a cast captures. Everything else is resolved per frame. + this._seed = Math.random() * 100; + this.field.rotation.y = Math.random() * TAU; + + this._sync(1); + this._muzzleFx(); + } + + /* ------------------------------------------------------------------ */ + /* Feedback */ + /* ------------------------------------------------------------------ */ + + /** + * Push the live settings and the current cast state into both cage passes, + * the field and the four particle systems. + * + * @param {number} fade 1 while the trap is lit, ramping to 0 as it collapses + */ + _sync(fade) { + const c = settings.snare; + const g = settings.global; + const state = this._state; + const travelling = this.phase === AbilityPhase.TRAVEL; + + this._centrePoint(state.centre); + this._handPoint(state.hand); + this._frontPoint(state.front); + + const open = travelling ? 0 : this._openAmount(); + state.radius = Math.max(0.05, this.radius * open); + state.height = Math.max(0.05, c.height * (travelling ? 0 : this._climbAmount())); + state.fade = fade; + state.seed = this._seed; + + // Roles are retired by setting their count to zero, which is how the whip + // disappears the instant the ring takes over. + const counts = state.counts; + counts.leash = travelling ? clampCount(c.leashStrands, MAX_LEASH) : 0; + counts.column = travelling ? 0 : clampCount(c.strands, MAX_COLUMN); + counts.tendril = travelling ? 0 : clampCount(c.tendrils, MAX_TENDRIL); + counts.rim = travelling ? 0 : clampCount(c.rimArcs, MAX_RIM); + + this._filamentCount = counts.leash + counts.column + counts.tendril + counts.rim; + this.geometry.instanceCount = Math.max(1, this._filamentCount); + + for (const material of this.cageMaterials) material.userData.sync(state); + + /* --- the field --- */ + const fieldState = this._fieldState; + fieldState.radius = state.radius; + fieldState.quadSize = (this.radius + c.fieldBoundary + 0.6) * 2; + fieldState.fade = travelling ? 0 : fade; + fieldState.seed = this._seed; + this.fieldMaterial.userData.sync(fieldState); + + this.field.visible = !travelling; + this.field.position.set(state.centre.x, c.fieldHeight, state.centre.z); + this.field.scale.set(fieldState.quadSize, 1, fieldState.quadSize); + + /* --- the particle systems, all four of them --- */ + this.sparks.setGradient( + getColor(c.colorSparkA), + getColor(c.colorSparkB), + getColor(c.colorSparkC), + getColor(c.colorSparkD) + ); + this.sparks.uniforms.uGravity.value.set(0, c.sparkGravity, 0); + this.sparks.uniforms.uSizeScale.value = c.sparkSize * g.particleSize * 7; + this.sparks.uniforms.uLifeScale.value = c.sparkLifetime * 0.5 * g.particleLifetime; + this.sparks.uniforms.uSpeedScale.value = g.particleSpeed; + this.sparks.uniforms.uOpacity.value = g.opacity; + this.sparks.uniforms.uGlow.value = c.glow * 0.6 * g.glow; + this.sparks.uniforms.uStretch.value = c.sparkStretch; + this.sparks.uniforms.uTurbulence.value = 0.25 * g.turbulence; + + this.updraft.setGradient( + getColor(c.colorUpdraftA), + getColor(c.colorUpdraftB), + getColor(c.colorUpdraftC), + getColor(c.colorUpdraftD) + ); + // Positive gravity: the column is lifting them, not dropping them. + this.updraft.uniforms.uGravity.value.set(0, c.updraftRise, 0); + this.updraft.uniforms.uSizeScale.value = c.updraftSize * g.particleSize * 7; + this.updraft.uniforms.uLifeScale.value = c.updraftLifetime * 0.5 * g.particleLifetime; + this.updraft.uniforms.uSpeedScale.value = g.particleSpeed; + this.updraft.uniforms.uOpacity.value = g.opacity; + this.updraft.uniforms.uGlow.value = 1.1 * g.glow; + this.updraft.uniforms.uTurbulence.value = c.updraftTurbulence * g.turbulence; + + this.smoke.setGradient( + getColor(c.colorSmokeA), + getColor(c.colorSmokeB), + getColor(c.colorSmokeC), + getColor(c.colorSmokeD) + ); + this.smoke.uniforms.uGravity.value.set(0, c.smokeRise, 0); + this.smoke.uniforms.uSizeScale.value = c.smokeSize * g.particleSize; + this.smoke.uniforms.uLifeScale.value = c.smokeLifetime * 0.5 * g.particleLifetime; + this.smoke.uniforms.uSpeedScale.value = c.smokeSpeed * g.particleSpeed; + this.smoke.uniforms.uOpacity.value = c.smokeOpacity * g.opacity; + this.smoke.uniforms.uTurbulence.value = 0.35 * g.turbulence; + + this.debris.setGradient( + getColor(c.colorDebrisA), + getColor(c.colorDebrisB), + getColor(c.colorDebrisC), + getColor(c.colorDebrisD) + ); + this.debris.uniforms.uGravity.value.set(0, c.debrisGravity, 0); + this.debris.uniforms.uSizeScale.value = c.debrisSize * g.particleSize * 7; + this.debris.uniforms.uLifeScale.value = g.particleLifetime; + this.debris.uniforms.uSpeedScale.value = g.particleSpeed; + this.debris.uniforms.uOpacity.value = g.opacity; + } + + /** The flash at the caster's hand as the leash leaves it. */ + _muzzleFx() { + const c = settings.snare; + const g = settings.global; + + this._handPoint(_pos); + + this.ctx.bursts.spawn(BurstMode.STORM, _pos, { + radius: c.muzzleSize * 0.2, + endRadius: c.muzzleSize * g.explosionIntensity, + life: 0.3, + intensity: c.muzzleIntensity, + opacity: 0.9, + fresnel: 1.5, + displace: 0.5, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + _emit.position = _pos; + _emit.radius = 0.16; + _emit.direction = _dir.copy(this.direction); + _emit.speed = c.sparkSpeed * 1.4; + _emit.speedVariance = 0.8; + _emit.spread = 0.8; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.18; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = frame.uTime.value; + this.sparks.emit(Math.round(34 * g.particleCount), _emit); + + this.ctx.flash.trigger(getColor(c.colorCastFlash), c.castFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 0.7 * g.explosionIntensity; + } + + /** Sparks off the leash and burns under it, while it races to the point. */ + _leashFx(dt) { + const c = settings.snare; + const g = settings.global; + const time = frame.uTime.value; + + const sparkCount = Math.round(this.sparkEmitter.tick(dt, c.sparkRate * 0.5) * g.particleCount); + if (sparkCount > 0) { + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.4).setY(0.6).normalize(); + _emit.speed = c.sparkSpeed * 0.8; + _emit.speedVariance = 0.85; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.14; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime * 0.8; + _emit.lifeVariance = 0.55; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + let remaining = sparkCount; + const per = Math.ceil(sparkCount / Math.min(sparkCount, SPARK_BATCHES)); + while (remaining > 0) { + this.pointAt(randRange(0.1, 1) * this.u, _pos).setY(c.leashCling + 0.05); + _emit.position = _pos; + _emit.radius = 0.22; + this.sparks.emit(Math.min(per, remaining), _emit); + remaining -= per; + } + } + + // Burns paid out per metre of travel, jittered off the line so they do not + // read as a dotted trail. + const step = 1 / Math.max(0.05, c.trailRate); + while (this.front - this._burnDistance >= step) { + this._burnDistance += step; + const s = saturate(this._burnDistance / this.length); + this.pointAt(s, _pos); + _pos.x += this.side.x * randRange(-0.4, 0.4); + _pos.z += this.side.z * randRange(-0.4, 0.4); + + this.ctx.decals.spawn(DecalType.ARC, _pos, { + radius: c.arcRadius * randRange(0.5, 0.85), + life: c.arcLife * 0.7, + width: c.arcBranches, + intensity: c.arcIntensity * 0.8, + colorA: getColor(c.colorEmber), + colorB: getColor(c.colorArc) + }); + } + } + + /** + * Everything the standing trap sheds: sparks off the cage, the updraft, haze, + * chips, burns around the rim, pressure shells and dust rings. + * + * @param {number} scale 0..1 — thinned out as the trap collapses + */ + _fieldFx(dt, scale) { + const c = settings.snare; + const g = settings.global; + const time = frame.uTime.value; + const centre = this._state.centre; + const radius = this._state.radius; + const height = this._state.height; + + /* --- sparks, thrown off the column and off the rim --- */ + let sparkCount = Math.round(this.sparkEmitter.tick(dt, c.sparkRate * scale) * g.particleCount); + if (sparkCount > 0) { + _emit.speed = c.sparkSpeed; + _emit.speedVariance = 0.85; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.16; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.55; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + const per = Math.ceil(sparkCount / Math.min(sparkCount, SPARK_BATCHES)); + while (sparkCount > 0) { + // Two thirds up the pillar, one third off the boundary — the column is + // the loud part, but a rim with no sparks reads as painted on. + if (Math.random() < 0.66) { + _pos.copy(centre).setY(randRange(0.1, 1) * height); + _emit.radius = radius * c.columnSpread * 1.4 + 0.1; + _emit.direction = _dir.set(0, 1, 0); + } else { + const a = Math.random() * TAU; + _pos.set(centre.x + Math.cos(a) * radius, 0.08, centre.z + Math.sin(a) * radius); + _emit.radius = 0.25; + _emit.direction = _dir.set(Math.cos(a) * 0.4, 1, Math.sin(a) * 0.4).normalize(); + } + _emit.position = _pos; + this.sparks.emit(Math.min(per, sparkCount), _emit); + sparkCount -= per; + } + } + + /* --- the updraft: picked up off the disc, hauled in and up --- */ + let updraftCount = Math.round( + this.updraftEmitter.tick(dt, c.updraftRate * scale) * g.particleCount + ); + if (updraftCount > 0) { + _emit.speed = c.updraftSpeed; + _emit.speedVariance = 0.5; + _emit.spread = 0.25; // tight: these are being pulled, not thrown + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.09; + _emit.sizeVariance = 0.6; + _emit.life = c.updraftLifetime; + _emit.lifeVariance = 0.45; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + const per = Math.ceil(updraftCount / Math.min(updraftCount, UPDRAFT_BATCHES)); + while (updraftCount > 0) { + const a = Math.random() * TAU; + const r = radius * (1 - c.updraftInset) * Math.sqrt(Math.random()); + _pos.set(centre.x + Math.cos(a) * r, randRange(0.05, 0.5), centre.z + Math.sin(a) * r); + _emit.position = _pos; + _emit.radius = 0.2; + // Inward and up — the swirl comes from the curl noise, not from a + // tangential velocity, so it stays soft rather than reading as a fan. + _emit.direction = _dir + .set(centre.x - _pos.x, 0, centre.z - _pos.z) + .normalize() + .multiplyScalar(0.8) + .setY(1) + .normalize(); + this.updraft.emit(Math.min(per, updraftCount), _emit); + updraftCount -= per; + } + } + + /* --- haze off the burnt floor --- */ + const smokeCount = Math.round(this.smokeEmitter.tick(dt, c.smokeRate * scale) * g.particleCount); + if (smokeCount > 0) { + const a = Math.random() * TAU; + const r = radius * Math.sqrt(Math.random()); + _pos.set(centre.x + Math.cos(a) * r, 0.15, centre.z + Math.sin(a) * r); + _emit.position = _pos; + _emit.radius = radius * 0.25; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.smokeSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.9; + _emit.size = 0.8; + _emit.sizeVariance = 0.5; + _emit.life = c.smokeLifetime; + _emit.lifeVariance = 0.4; + _emit.spin = 0.4; + _emit.time = time; + this.smoke.emit(smokeCount, _emit); + } + + /* --- chips torn off the floor --- */ + const debrisCount = Math.round( + this.debrisEmitter.tick(dt, c.debrisRate * scale) * g.particleCount + ); + if (debrisCount > 0) { + const a = Math.random() * TAU; + const r = radius * Math.sqrt(Math.random()); + _pos.set(centre.x + Math.cos(a) * r, 0.06, centre.z + Math.sin(a) * r); + _emit.position = _pos; + _emit.radius = 0.3; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.debrisSpeed; + _emit.speedVariance = 0.75; + _emit.spread = 0.7; + _emit.size = 0.1; + _emit.sizeVariance = 0.7; + _emit.life = c.debrisLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 8; + _emit.time = time; + this.debris.emit(debrisCount, _emit); + } + + /* --- burns walking around the boundary --- */ + const arcCount = this.arcEmitter.tick(dt, c.arcRate * scale); + for (let i = 0; i < arcCount; i++) { + const a = Math.random() * TAU; + const r = radius * randRange(0.72, 1.02); + _pos.set(centre.x + Math.cos(a) * r, 0, centre.z + Math.sin(a) * r); + this.ctx.decals.spawn(DecalType.ARC, _pos, { + radius: c.arcRadius * randRange(0.7, 1.25), + life: c.arcLife, + width: c.arcBranches, + intensity: c.arcIntensity, + colorA: getColor(c.colorEmber), + colorB: getColor(c.colorArc) + }); + } + + /* --- pressure shells shed off the foot of the pillar --- */ + // Kept low, small and faint on purpose. A shell this size released halfway + // up the column hangs in the air as a faceted glass dome — it needs the + // floor under it to read as pressure coming off the strike rather than as + // a bubble parked in mid-air. + const pulseCount = this.pulseEmitter.tick(dt, c.pulseRate * scale); + for (let i = 0; i < pulseCount; i++) { + _pos.copy(centre).setY(height * randRange(0.02, 0.14)); + this.ctx.bursts.spawn(BurstMode.STORM, _pos, { + radius: c.pulseSize * 0.25, + endRadius: c.pulseSize * g.explosionIntensity, + life: 0.42, + intensity: c.pulseIntensity, + opacity: 0.3, + fresnel: 2.2, + displace: 0.5, + squash: 0.45, // flattened: pressure spreading over the floor + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + } + + /* --- dust rings pushed out across the floor --- */ + const ringCount = this.ringEmitter.tick(dt, c.ringRate * scale); + for (let i = 0; i < ringCount; i++) { + this.ctx.decals.spawn(DecalType.SHOCKWAVE, centre, { + radius: radius * 1.05, + life: 0.7, + width: 0.06, + intensity: 0.6, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + } + } + + /* ------------------------------------------------------------------ */ + /* Phases */ + /* ------------------------------------------------------------------ */ + + onTravel(dt) { + this._sync(1); + + // The light rides the tip of the leash, a little off the floor. + this._frontPoint(this.position); + this.position.y += 0.3; + + this._leashFx(dt); + this.ctx.shake.rumble(settings.snare.rumble * settings.global.cameraShake, dt); + } + + onImpact() { + const c = settings.snare; + const g = settings.global; + const time = frame.uTime.value; + + this._openTime = 0; + this._centrePoint(_pos); + + /* the shell of ionised air the ring throws off as it opens */ + // Short and squashed: the shell has to be gone before the trap settles, or + // the standing pillar spends its whole life inside a dome. + this.ctx.bursts.spawn(BurstMode.STORM, _pos, { + radius: c.burstSize * 0.2, + endRadius: c.burstSize * g.explosionIntensity, + life: 0.45, + intensity: c.burstIntensity, + opacity: 0.6, + fresnel: 1.6, + displace: 0.6, + squash: 0.55, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + /* the ring that snaps outward across the floor, past the boundary */ + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _pos, { + radius: c.shockRadius * g.explosionIntensity, + life: 0.65, + width: 0.05, + intensity: 1.0, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + /* the dark burn the trap stands on */ + this.ctx.decals.spawn(DecalType.SCORCH, _pos, { + radius: c.scorchRadius, + life: c.scorchLife, + intensity: c.scorchIntensity, + colorA: getColor(c.colorScorch), + colorB: getColor(c.colorEmber), + height: 0.012 + }); + + /* burns struck around the boundary as it grounds out */ + const marks = 6; + for (let i = 0; i < marks; i++) { + const a = (i / marks + Math.random() * 0.1) * TAU; + const r = this.radius * randRange(0.85, 1.0); + this._centrePoint(_pos); + _pos.x += Math.cos(a) * r; + _pos.z += Math.sin(a) * r; + this.ctx.decals.spawn(DecalType.ARC, _pos, { + radius: c.arcRadius * randRange(1.1, 1.7), + life: c.arcLife * 1.5, + width: c.arcBranches, + intensity: c.arcIntensity, + colorA: getColor(c.colorEmber), + colorB: getColor(c.colorArc) + }); + } + + /* sparks and chips blown out of the snap */ + this._centrePoint(_pos); + _emit.position = _pos; + _emit.radius = this.radius * 0.35; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.sparkSpeed * 1.9; + _emit.speedVariance = 0.85; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.2; + _emit.sizeVariance = 0.8; + _emit.life = c.sparkLifetime * 1.4; + _emit.lifeVariance = 0.6; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.sparks.emit(Math.round(c.burstSparks * g.particleCount), _emit); + + _emit.radius = this.radius * 0.6; + _emit.speed = c.debrisSpeed * 1.7; + _emit.spread = 0.8; + _emit.size = 0.13; + _emit.life = c.debrisLifetime * 1.3; + _emit.spin = 10; + this.debris.emit(Math.round(c.burstDebris * g.particleCount), _emit); + + _emit.speed = c.smokeSpeed * 2.0; + _emit.spread = 1.0; + _emit.size = 1.4; + _emit.life = c.smokeLifetime * 1.2; + _emit.spin = 0.5; + this.smoke.emit(Math.round(40 * g.particleCount), _emit); + + this.ctx.shake.add( + c.impactShake * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 24 + ); + this.ctx.flash.trigger(getColor(c.colorFlash), c.impactFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 1.4 * g.explosionIntensity; + } + + onFade(dt, t) { + const c = settings.snare; + this._openTime += dt; + + // `t` runs 0..1 while the trap stands, then 1..2 while it collapses. Cubic + // on the way out so the pillar hangs on and then goes, rather than dimming. + const fade = t <= 1 ? 1 : 1 - Easing.inCubic(saturate(t - 1)); + this._sync(fade); + + // The light climbs into the column and stays there. + this._centrePoint(this.position); + this.position.y = this._state.height * saturate(c.lightHeight); + + this._fieldFx(dt, fade * (t <= 1 ? 1 : 0.4)); + this.ctx.shake.rumble(c.holdShake * fade * settings.global.cameraShake, dt); + } + + onDestroy() { + this._filamentCount = 1; + this.geometry.instanceCount = 1; + this.field.visible = false; + for (const material of this.cageMaterials) material.uniforms.uFade.value = 0; + this.fieldMaterial.uniforms.uFade.value = 0; + } + + dispose() { + this.geometry.dispose(); + this.fieldGeometry.dispose(); + for (const material of this.cageMaterials) material.dispose(); + this.fieldMaterial.dispose(); + super.dispose(); + } +} + +/** Clamp a slider to its role's hard ceiling, and never below one filament. */ +function clampCount(value, max) { + return Math.max(0, Math.min(max, Math.round(value))); +} diff --git a/src/skillshot/abilities/ThunderAbility.js b/src/skillshot/abilities/ThunderAbility.js new file mode 100644 index 0000000..66d90b6 --- /dev/null +++ b/src/skillshot/abilities/ThunderAbility.js @@ -0,0 +1,666 @@ +import { Mesh, Vector3 } from 'three'; +import { Ability, AbilityPhase } from './Ability.js'; +import { createLightningMaterial, BoltPass } from '../materials/LightningMaterial.js'; +import { createBoltRibbonGeometry } from '../ProceduralGeometry.js'; +import { ParticleShape } from '../../particles/ParticleSystem.js'; +import { RateEmitter } from '../../particles/ParticleEngine.js'; +import { DecalType } from '../../effects/GroundDecals.js'; +import { BurstMode } from '../../effects/BurstSphere.js'; +import { LAYER } from '../../core/Layers.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, lerp, Easing, randRange } from '../../utils/math.js'; + +/** Hard ceiling on filaments per bolt. The editor's `strands` slider clamps here. */ +const MAX_STRANDS = 24; +/** + * Samples along one filament. This is the ceiling on how fine a kink can be: + * anything higher-frequency than one kink per two nodes just aliases, so + * `jitterScale` above roughly `nodes / (2 × range)` stops adding detail. + */ +const NODES = 72; +/** + * How many points along the bolt one frame's sparks are split between. Anything + * lower and each batch reads as a starburst pinned to a single spot. + */ +const SPARK_BATCHES = 6; + +const _emit = {}; +const _pos = new Vector3(); +const _dir = new Vector3(); +const _origin = new Vector3(); +const _target = new Vector3(); + +/** + * THUNDER — a bolt thrown from the hand along the aimed line. + * + * One beat, short: a strike front races out from the caster's hand at `speed` + * and a bundle of lightning filaments is drawn behind it; the bundle then holds + * for `lifetime`, guttering and re-striking, and blows out over `fadeTime`. + * Sparks come off it the whole way, the floor underneath takes a branching + * electric burn and a dark scorch, and the far end gets a shell of ionised air. + * + * Everything is generated: the bolt is a strip of camera-facing ribbon whose + * every vertex is placed by the vertex shader, the burns are shaders on quads, + * and the sparks, motes, smoke and debris are GPU particles. Nothing is loaded + * from disk and nothing is a texture. + * + * **The rule that makes the editor work.** `IceAbility` keeps a pool of spike + * records holding nothing but dice rolls; this ability does not even need that, + * because the *entire* path lives in the shader. A cast captures exactly one + * number — `_seed`, so two casts do not draw the identical bolt — and resolves + * every metre, radian and second against `settings.thunder` each frame, on a + * zero-length frame included. Dragging `jitter` re-kinks a bolt that is already + * in the air; dragging `spread` re-fans it; dragging `handHeight` lifts the end + * that leaves the caster. That is the point of pausing with **P** mid-strike. + * + * The only other things a cast captures are timestamps. Those are events. + */ +export class ThunderAbility extends Ability { + constructor(context) { + super('thunder', context); + } + + /* ------------------------------------------------------------------ */ + /* Construction */ + /* ------------------------------------------------------------------ */ + + createShaders() { + this.geometry = createBoltRibbonGeometry(NODES, MAX_STRANDS); + + // Two passes over the same filaments: a wide, soft halo underneath and the + // hot core on top. Drawing the halo as real ribbon rather than faking it + // with bloom is what keeps the glow *attached* to every kink. + this.glowMaterial = createLightningMaterial(BoltPass.GLOW); + this.coreMaterial = createLightningMaterial(BoltPass.CORE); + this.boltMaterials = [this.glowMaterial, this.coreMaterial]; + + this.meshes = []; + for (const [index, material] of this.boltMaterials.entries()) { + const mesh = new Mesh(this.geometry, material); + mesh.frustumCulled = false; + mesh.matrixAutoUpdate = false; + mesh.layers.set(LAYER.VFX); + // Halo first so the core adds on top of it. + mesh.renderOrder = 11 + index * 2; + this.group.add(mesh); + this.meshes.push(mesh); + } + + /** Re-rolled per cast so no two bolts draw the same shape. */ + this._seed = 0; + this._strandCount = 1; + /** Metres of front travel already paid out in ground burns. */ + this._burnDistance = 0; + + // Scratch state handed to both materials each frame. One object, reused — + // syncing the bolt allocates nothing. + this._state = { + origin: new Vector3(), + target: new Vector3(), + side: new Vector3(), + progress: 0, + fade: 1, + seed: 0, + strands: 1 + }; + } + + createParticles() { + const particles = this.ctx.particles; + + // Sparks: velocity-stretched streaks under gravity. The signature of the + // reference frame, and the reason this system asks for `stretch`. + this.sparks = particles.get('thunder.sparks', { + capacity: 4000, + shape: ParticleShape.STREAK, + additive: true, + stretch: true, + softFade: 0.25 + }); + this.sparks.uniforms.uDrag.value = 1.4; + this.sparks.uniforms.uEndSize.value = 0.25; + this.sparks.uniforms.uSizeIn.value = 0.02; + this.sparks.uniforms.uFadeIn.value = 0.03; + this.sparks.uniforms.uFadeOut.value = 0.45; + + // The slow ionised motes that hang in the air around the bolt. + this.motes = particles.get('thunder.motes', { + capacity: 2400, + shape: ParticleShape.SOFT, + additive: true, + curl: true, + softFade: 0.4 + }); + this.motes.uniforms.uDrag.value = 1.6; + this.motes.uniforms.uEndSize.value = 0.15; + this.motes.uniforms.uSizeIn.value = 0.06; + this.motes.uniforms.uFadeIn.value = 0.08; + this.motes.uniforms.uFadeOut.value = 0.4; + + // Haze off the scorched floor. Non-additive so it genuinely occludes. + this.smoke = particles.get('thunder.smoke', { + capacity: 2000, + shape: ParticleShape.SMOKE, + additive: false, + curl: true, + softFade: 1.1 + }); + this.smoke.uniforms.uDrag.value = 1.8; + this.smoke.uniforms.uEndSize.value = 3.0; + this.smoke.uniforms.uSizeIn.value = 0.12; + this.smoke.uniforms.uFadeIn.value = 0.16; + this.smoke.uniforms.uFadeOut.value = 0.3; + + // Chips blown off the floor under the strike. + this.debris = particles.get('thunder.debris', { + capacity: 1600, + shape: ParticleShape.CHIP, + additive: false, + lit: true, + softFade: 0.25 + }); + this.debris.uniforms.uDrag.value = 0.25; + this.debris.uniforms.uEndSize.value = 0.8; + this.debris.uniforms.uFadeOut.value = 0.7; + + this.sparkEmitter = new RateEmitter(); + this.moteEmitter = new RateEmitter(); + this.smokeEmitter = new RateEmitter(); + this.debrisEmitter = new RateEmitter(); + } + + /* ------------------------------------------------------------------ */ + /* Timing */ + /* ------------------------------------------------------------------ */ + + get instanceCount() { + // Two passes over the same filaments. + return this._strandCount * this.meshes.length; + } + + /** The bolt holds after it lands, then blows out. */ + get impactDuration() { + return Math.max(0.05, settings.thunder.lifetime * settings.global.lifetime); + } + + get fadeDuration() { + return Math.max(0.05, settings.thunder.fadeTime); + } + + /** Lightning gutters where ice glints — a hard, quantised stutter. */ + lightShimmer() { + const c = settings.thunder; + const step = Math.floor(this.age * Math.max(1, c.lightFlickerSpeed)); + // Deterministic hash of the step index: the light snaps between levels on + // the same clock the bolt's own flicker runs on. + const noise = Math.abs(Math.sin(step * 127.1) * 43758.5453) % 1; + return 1 - saturate(c.lightFlicker) * noise; + } + + /* ------------------------------------------------------------------ */ + /* Geometry of the bolt — every metre resolved from live settings */ + /* ------------------------------------------------------------------ */ + + /** + * Where the bolt leaves the caster, in world space. + * + * The base class puts `origin` on the floor because that is what the aim + * indicator targets; a bolt comes out of a hand, so the offsets are applied + * here rather than baked into the cast. + */ + _handPoint(out) { + const c = settings.thunder; + out + .copy(this.origin) + .addScaledVector(this.direction, c.handForward) + .addScaledVector(this.side, c.handSide); + out.y = c.handHeight; + return out; + } + + /** Where it lands. */ + _impactPoint(out) { + this.pointAt(1, out); + out.y = settings.thunder.endHeight; + return out; + } + + /** + * A point on the bolt's *axis* at `s` along it, 0..1. + * + * This mirrors the first stage of the vertex shader exactly — the same lerp, + * the same sag — so the sparks and burns the CPU spawns sit on the bolt the + * GPU actually draws instead of near it. + */ + _axisPoint(s, out) { + const c = settings.thunder; + const t = saturate(s); + out + .copy(this.origin) + .addScaledVector(this.direction, lerp(c.handForward, this.length, t)) + .addScaledVector(this.side, c.handSide * (1 - t)); + out.y = lerp(c.handHeight, c.endHeight, t) + c.sag * Math.sin(t * Math.PI); + return out; + } + + /** Half-width of the bundle at `s`, metres — how far sparks may be thrown. */ + _bundleRadius(s) { + const c = settings.thunder; + return lerp(c.spreadNear, c.spread, Math.pow(saturate(s), Math.max(0.01, c.spreadCurve))); + } + + /* ------------------------------------------------------------------ */ + /* Casting */ + /* ------------------------------------------------------------------ */ + + onSpawn() { + this.sparkEmitter.reset(); + this.moteEmitter.reset(); + this.smokeEmitter.reset(); + this.debrisEmitter.reset(); + this._burnDistance = 0; + + // The one thing a cast captures. Everything else is resolved per frame. + this._seed = Math.random() * 100; + + this._syncUniforms(1); + this._muzzleFx(); + } + + /* ------------------------------------------------------------------ */ + /* Feedback */ + /* ------------------------------------------------------------------ */ + + /** + * Push the live settings and the current cast state into both bolt passes. + * @param {number} fade 1 while the bolt is lit, ramping to 0 as it blows out + */ + _syncUniforms(fade) { + const c = settings.thunder; + const g = settings.global; + const state = this._state; + + this._handPoint(state.origin); + this._impactPoint(state.target); + state.side.copy(this.side); + state.progress = this.phase === AbilityPhase.TRAVEL ? this.u : 1; + state.fade = fade; + state.seed = this._seed; + + this._strandCount = Math.max(1, Math.min(MAX_STRANDS, Math.round(c.strands))); + state.strands = this._strandCount; + this.geometry.instanceCount = this._strandCount; + + for (const material of this.boltMaterials) material.userData.sync(state); + + /* --- the particle systems, all four of them --- */ + this.sparks.setGradient( + getColor(c.colorSparkA), + getColor(c.colorSparkB), + getColor(c.colorSparkC), + getColor(c.colorSparkD) + ); + this.sparks.uniforms.uGravity.value.set(0, c.sparkGravity, 0); + this.sparks.uniforms.uSizeScale.value = c.sparkSize * g.particleSize * 7; + this.sparks.uniforms.uLifeScale.value = c.sparkLifetime * 0.5 * g.particleLifetime; + this.sparks.uniforms.uSpeedScale.value = g.particleSpeed; + this.sparks.uniforms.uOpacity.value = g.opacity; + this.sparks.uniforms.uGlow.value = c.glow * 0.6 * g.glow; + this.sparks.uniforms.uStretch.value = c.sparkStretch; + this.sparks.uniforms.uTurbulence.value = 0.25 * g.turbulence; + + this.motes.setGradient( + getColor(c.colorMoteA), + getColor(c.colorMoteB), + getColor(c.colorMoteC), + getColor(c.colorMoteD) + ); + this.motes.uniforms.uGravity.value.set(0, c.moteRise, 0); + this.motes.uniforms.uSizeScale.value = c.moteSize * g.particleSize * 7; + this.motes.uniforms.uLifeScale.value = c.moteLifetime * 0.5 * g.particleLifetime; + this.motes.uniforms.uSpeedScale.value = g.particleSpeed; + this.motes.uniforms.uOpacity.value = g.opacity; + this.motes.uniforms.uGlow.value = 0.9 * g.glow; + this.motes.uniforms.uTurbulence.value = c.moteTurbulence * g.turbulence; + + this.smoke.setGradient( + getColor(c.colorSmokeA), + getColor(c.colorSmokeB), + getColor(c.colorSmokeC), + getColor(c.colorSmokeD) + ); + this.smoke.uniforms.uGravity.value.set(0, c.smokeRise, 0); + this.smoke.uniforms.uSizeScale.value = c.smokeSize * g.particleSize; + this.smoke.uniforms.uLifeScale.value = c.smokeLifetime * 0.5 * g.particleLifetime; + this.smoke.uniforms.uSpeedScale.value = c.smokeSpeed * g.particleSpeed; + this.smoke.uniforms.uOpacity.value = c.smokeOpacity * g.opacity; + this.smoke.uniforms.uTurbulence.value = 0.35 * g.turbulence; + + this.debris.setGradient( + getColor(c.colorDebrisA), + getColor(c.colorDebrisB), + getColor(c.colorDebrisC), + getColor(c.colorDebrisD) + ); + this.debris.uniforms.uGravity.value.set(0, c.debrisGravity, 0); + this.debris.uniforms.uSizeScale.value = c.debrisSize * g.particleSize * 7; + this.debris.uniforms.uLifeScale.value = g.particleLifetime; + this.debris.uniforms.uSpeedScale.value = g.particleSpeed; + this.debris.uniforms.uOpacity.value = g.opacity; + } + + /** The flash at the caster's hand as the bolt leaves it. */ + _muzzleFx() { + const c = settings.thunder; + const g = settings.global; + + this._handPoint(_pos); + + this.ctx.bursts.spawn(BurstMode.STORM, _pos, { + radius: c.muzzleSize * 0.2, + endRadius: c.muzzleSize * g.explosionIntensity, + life: 0.3, + intensity: c.muzzleIntensity, + opacity: 0.9, + fresnel: 1.5, + displace: 0.5, + colorA: getColor(c.colorMuzzleA), + colorB: getColor(c.colorMuzzleB), + colorC: getColor(c.colorMuzzleC) + }); + + _emit.position = _pos; + _emit.radius = 0.18; + _emit.direction = _dir.copy(this.direction); + _emit.speed = c.sparkSpeed * 1.5; + _emit.speedVariance = 0.8; + _emit.spread = 0.85; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.2; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.tint = null; + _emit.time = frame.uTime.value; + this.sparks.emit(Math.round(40 * g.particleCount), _emit); + + this.ctx.flash.trigger(getColor(c.colorCastFlash), c.castFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 0.8 * g.explosionIntensity; + } + + /** + * Sparks, motes, smoke and debris shed along the length of the bolt. + * @param {number} scale 0..1 — thinned out once the bolt is only holding + */ + _boltFx(dt, scale) { + const c = settings.thunder; + const g = settings.global; + const time = frame.uTime.value; + // Only the drawn part of the bolt is allowed to throw anything. + const reach = this.phase === AbilityPhase.TRAVEL ? Math.max(0.02, this.u) : 1; + + let sparkCount = Math.round(this.sparkEmitter.tick(dt, c.sparkRate * scale) * g.particleCount); + if (sparkCount > 0) { + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.45).setY(0.55).normalize(); + _emit.speed = c.sparkSpeed; + _emit.speedVariance = 0.85; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.16; + _emit.sizeVariance = 0.7; + _emit.life = c.sparkLifetime; + _emit.lifeVariance = 0.55; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + + // Spread the frame's sparks over several points along the bolt rather + // than firing them all from one. A beam sheds along its whole length, and + // a single origin makes every batch read as a starburst — which is very + // obviously wrong once you look at it. + const batches = Math.min(sparkCount, SPARK_BATCHES); + const per = Math.ceil(sparkCount / batches); + while (sparkCount > 0) { + const s = randRange(0.05, 1) * reach; + this._axisPoint(s, _pos); + _emit.position = _pos; + _emit.radius = this._bundleRadius(s) * 1.1 + 0.05; + this.sparks.emit(Math.min(per, sparkCount), _emit); + sparkCount -= per; + } + } + + const moteCount = Math.round(this.moteEmitter.tick(dt, c.moteRate * scale) * g.particleCount); + if (moteCount > 0) { + const s = Math.random() * reach; + this._axisPoint(s, _pos); + _emit.position = _pos; + _emit.radius = this._bundleRadius(s) * 1.6 + 0.2; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.moteSpeed; + _emit.speedVariance = 0.8; + _emit.spread = 1.0; + _emit.size = 0.08; + _emit.sizeVariance = 0.6; + _emit.life = c.moteLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 0; + _emit.time = time; + this.motes.emit(moteCount, _emit); + } + + const smokeCount = Math.round(this.smokeEmitter.tick(dt, c.smokeRate * scale) * g.particleCount); + if (smokeCount > 0) { + // Smoke comes off the *floor*, not the bolt — it is the scorch burning. + this.pointAt(Math.random() * reach, _pos).setY(0.15); + _emit.position = _pos; + _emit.radius = c.scorchRadius * 2.5; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.smokeSpeed; + _emit.speedVariance = 0.7; + _emit.spread = 0.9; + _emit.size = 0.8; + _emit.sizeVariance = 0.5; + _emit.life = c.smokeLifetime; + _emit.lifeVariance = 0.4; + _emit.spin = 0.4; + _emit.time = time; + this.smoke.emit(smokeCount, _emit); + } + + const debrisCount = Math.round(this.debrisEmitter.tick(dt, c.debrisRate * scale) * g.particleCount); + if (debrisCount > 0) { + this.pointAt(Math.random() * reach, _pos).setY(0.06); + _emit.position = _pos; + _emit.radius = c.scorchRadius * 1.8; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.3).setY(1).normalize(); + _emit.speed = c.debrisSpeed; + _emit.speedVariance = 0.75; + _emit.spread = 0.8; + _emit.size = 0.1; + _emit.sizeVariance = 0.7; + _emit.life = c.debrisLifetime; + _emit.lifeVariance = 0.5; + _emit.spin = 8; + _emit.time = time; + this.debris.emit(debrisCount, _emit); + } + } + + /** Burns laid on the floor as the strike front passes over it. */ + _groundFx() { + const c = settings.thunder; + const step = 1 / Math.max(0.05, c.arcRate); + + while (this.front - this._burnDistance >= step) { + this._burnDistance += step; + const s = saturate(this._burnDistance / this.length); + this.pointAt(s, _pos); + // Jittered off the axis so the marks do not read as a dotted line. + const wander = this._bundleRadius(s) * 0.8; + _pos.x += this.side.x * randRange(-wander, wander); + _pos.z += this.side.z * randRange(-wander, wander); + + this.ctx.decals.spawn(DecalType.ARC, _pos, { + radius: c.arcRadius * randRange(0.7, 1.2), + life: c.arcLife, + width: c.arcBranches, + intensity: c.arcIntensity, + colorA: getColor(c.colorEmber), + colorB: getColor(c.colorArc) + }); + + this.ctx.decals.spawn(DecalType.SCORCH, _pos, { + radius: c.scorchRadius * randRange(0.7, 1.3), + life: c.scorchLife, + intensity: c.scorchIntensity, + colorA: getColor(c.colorScorch), + colorB: getColor(c.colorEmber), + height: 0.015 + }); + } + } + + /* ------------------------------------------------------------------ */ + /* Phases */ + /* ------------------------------------------------------------------ */ + + onTravel(dt) { + this._syncUniforms(1); + + // The light rides the tip of the bolt, not the floor under it. `advance()` + // has already put `position` on the ground line, so lift it onto the axis. + this._axisPoint(this.u, this.position); + + this._boltFx(dt, 1); + this._groundFx(); + + this.ctx.shake.rumble(settings.thunder.rumble * settings.global.cameraShake, dt); + } + + onImpact() { + const c = settings.thunder; + const g = settings.global; + const time = frame.uTime.value; + + this._impactPoint(_pos); + + /* the shell of ionised air */ + this.ctx.bursts.spawn(BurstMode.STORM, _pos, { + radius: c.burstSize * 0.2, + endRadius: c.burstSize * g.explosionIntensity, + life: 0.7, + intensity: c.burstIntensity, + opacity: 0.9, + fresnel: 1.6, + displace: 0.6, + squash: 0.8, + colorA: getColor(c.colorBurstA), + colorB: getColor(c.colorBurstB), + colorC: getColor(c.colorBurstC) + }); + + /* the ring that snaps outward across the floor */ + this.pointAt(1, _target); + this.ctx.decals.spawn(DecalType.SHOCKWAVE, _target, { + radius: c.shockRadius * g.explosionIntensity, + life: 0.6, + width: 0.05, + intensity: 1.0, + colorA: getColor(c.colorShockA), + colorB: getColor(c.colorShockB) + }); + + /* a wide burn where it grounded out */ + this.ctx.decals.spawn(DecalType.ARC, _target, { + radius: c.arcRadius * 2.4, + life: c.arcLife * 1.6, + width: c.arcBranches, + intensity: c.arcIntensity, + colorA: getColor(c.colorEmber), + colorB: getColor(c.colorArc) + }); + this.ctx.decals.spawn(DecalType.SCORCH, _target, { + radius: c.scorchRadius * 2.6, + life: c.scorchLife * 1.4, + intensity: c.scorchIntensity * 1.3, + colorA: getColor(c.colorScorch), + colorB: getColor(c.colorEmber), + height: 0.015 + }); + + /* sparks and chips blown out of the strike */ + _emit.position = _pos; + _emit.radius = 0.3; + _emit.direction = _dir.copy(this.direction).multiplyScalar(0.5).setY(0.7).normalize(); + _emit.speed = c.sparkSpeed * 2.2; + _emit.speedVariance = 0.85; + _emit.spread = 1.0; + _emit.inherit = null; + _emit.anchor = null; + _emit.size = 0.22; + _emit.sizeVariance = 0.8; + _emit.life = c.sparkLifetime * 1.5; + _emit.lifeVariance = 0.6; + _emit.spin = 0; + _emit.tint = null; + _emit.time = time; + this.sparks.emit(Math.round(c.burstSparks * g.particleCount), _emit); + + _emit.position = _target; + _emit.radius = c.scorchRadius * 2; + _emit.direction = _dir.set(0, 1, 0); + _emit.speed = c.debrisSpeed * 1.8; + _emit.spread = 0.85; + _emit.size = 0.14; + _emit.life = c.debrisLifetime * 1.3; + _emit.spin = 10; + this.debris.emit(Math.round(c.burstDebris * g.particleCount), _emit); + + _emit.speed = c.smokeSpeed * 2.2; + _emit.spread = 1.0; + _emit.size = 1.5; + _emit.life = c.smokeLifetime * 1.3; + _emit.spin = 0.5; + this.smoke.emit(Math.round(45 * g.particleCount), _emit); + + this.ctx.shake.add( + c.impactShake * g.explosionIntensity * g.cameraShake, + 1 / Math.max(0.1, c.shakeDuration), + 26 + ); + this.ctx.flash.trigger(getColor(c.colorFlash), c.impactFlash * g.explosionIntensity); + this.lightBoost = c.lightIntensity * 1.5 * g.explosionIntensity; + } + + onFade(dt, t) { + // `t` runs 0..1 while the bolt holds, then 1..2 while it blows out. The + // blow-out is cubic so the bolt hangs on and then goes, rather than dimming. + const fade = t <= 1 ? 1 : 1 - Easing.inCubic(saturate(t - 1)); + this._syncUniforms(fade); + + // The light stays at the impact point once the front has arrived. + this._axisPoint(1, this.position); + + // Thinned as it dies: a bolt that is guttering out is not still shedding at + // full rate, but it does keep throwing sparks until it lets go. + this._boltFx(dt, fade * (t <= 1 ? 0.6 : 0.35)); + } + + onDestroy() { + this._strandCount = 1; + this.geometry.instanceCount = 1; + for (const material of this.boltMaterials) material.uniforms.uFade.value = 0; + } + + dispose() { + this.geometry.dispose(); + for (const material of this.boltMaterials) material.dispose(); + super.dispose(); + } +} diff --git a/src/skillshot/effects/AimIndicator.js b/src/skillshot/effects/AimIndicator.js new file mode 100644 index 0000000..734ab06 --- /dev/null +++ b/src/skillshot/effects/AimIndicator.js @@ -0,0 +1,340 @@ +import { + Mesh, + PlaneGeometry, + ShaderMaterial, + AdditiveBlending, + Color, + DoubleSide +} from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { LAYER } from '../../core/Layers.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +const AIM_VERTEX = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +/** + * The whole indicator is one signed-distance field evaluated in metres. + * + * `vUv` is remapped so that `p.y` is the distance from the caster along the aim + * line and `p.x` is the lateral offset, both in world metres. Every control in + * `settings.aim` is therefore a real measurement rather than a UV fraction, + * which is what lets the shaft width and the head stay the same physical size + * whether the cast is 3 m or 15 m long. + */ +const AIM_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uQuadLength; // length the quad covers, metres + uniform float uQuadWidth; // width the quad covers, metres + uniform float uQuadBack; // how far behind the caster the quad starts + uniform float uLength; // the cast distance itself, metres + uniform float uStart; // gap between the caster and the tail + uniform float uShaftWidth; + uniform float uHeadLength; + uniform float uHeadWidth; + uniform float uRound; + uniform float uEdge; + uniform float uEdgeGlow; + uniform float uSoftness; + uniform float uFill; + uniform float uFillFalloff; + uniform float uStripes; + uniform float uStripeSharp; + uniform float uStripeDepth; + uniform float uScrollSpeed; + uniform float uPulse; + uniform float uPulseSpeed; + uniform float uNoise; + uniform float uNoiseScale; + uniform float uNoiseSpeed; + uniform float uCrystals; + uniform float uCrystalScale; + uniform float uBaseRing; + uniform float uBaseRingWidth; + uniform float uTipGlyph; + uniform float uTipGlyphSize; + uniform float uTipSpin; + uniform float uRangeArc; + uniform float uReveal; // 0..1 sweep-out when the cast is armed + uniform float uInvalid; // 1 when the target is inside the minimum range + uniform float uOpacity; + uniform vec3 uColorCore; + uniform vec3 uColorEdge; + uniform vec3 uColorInvalid; + uniform float uGlobalGlow; + + varying vec2 vUv; + + ${noiseGLSL} + ${commonGLSL} + + #define TAU 6.28318530718 + + float sdBox(vec2 p, vec2 b) { + vec2 d = abs(p) - b; + return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); + } + + /* iq's exact triangle SDF — needed because the head is a wide, shallow wedge + where a cheap half-plane intersection leaves visible corner artefacts. */ + float sdTriangle(vec2 p, vec2 p0, vec2 p1, vec2 p2) { + vec2 e0 = p1 - p0, e1 = p2 - p1, e2 = p0 - p2; + vec2 v0 = p - p0, v1 = p - p1, v2 = p - p2; + + vec2 pq0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0.0, 1.0); + vec2 pq1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0.0, 1.0); + vec2 pq2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0.0, 1.0); + + float s = sign(e0.x * e2.y - e0.y * e2.x); + vec2 d = min(min(vec2(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)), + vec2(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))), + vec2(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x))); + return -sqrt(d.x) * sign(d.y); + } + + void main() { + /* ---- uv → metres, measured from the caster ---- */ + vec2 p = vec2((vUv.x - 0.5) * uQuadWidth, + (1.0 - vUv.y) * uQuadLength - uQuadBack); + + float length_ = max(uLength, uStart + 0.05); + float headLen = min(uHeadLength, length_ - uStart); + float headBase = length_ - headLen; + + /* ---- silhouette ---- */ + float shaft = sdBox(p - vec2(0.0, (uStart + headBase) * 0.5), + vec2(uShaftWidth, max(0.001, (headBase - uStart) * 0.5))); + float head = sdTriangle(p, + vec2(-uHeadWidth, headBase), + vec2( uHeadWidth, headBase), + vec2( 0.0, length_)); + float d = min(shaft, head) - uRound; + + float aa = fwidth(d) + uSoftness; + float body = 1.0 - smoothstep(-aa, aa, d); + float outline = 1.0 - smoothstep(uEdge, uEdge + aa, abs(d)); + + /* ---- interior wash ---- */ + // Brighter toward the rim: a flat fill reads as a decal, a rim-weighted one + // reads as a volume of light lying on the floor. + float depth = clamp(-d / max(uShaftWidth, 0.05), 0.0, 1.0); + float interior = pow(1.0 - depth, uFillFalloff); + + // Chevrons running toward the tip. Skewing the phase by |x| turns the flat + // bands into arrowheads that all point the same way the cast does. + float phase = (p.y - abs(p.x) * 0.55 - uTime * uScrollSpeed) * uStripes; + float band = 0.5 + 0.5 * cos(phase * TAU); + band = pow(band, mix(1.0, 9.0, uStripeSharp)); + + float frost = fbm3(vec3(p * uNoiseScale, uTime * uNoiseSpeed)) * 0.5 + 0.5; + vec2 cell = voronoi2(p * uCrystalScale + 13.7); + float plates = smoothstep(0.32, 0.0, cell.x); + + float wash = interior; + wash *= mix(1.0, band, uStripeDepth); + wash *= mix(1.0, frost, uNoise); + wash += plates * uCrystals * 0.35 * interior; + wash *= 1.0 + uPulse * sin(uTime * uPulseSpeed * TAU); + + /* ---- furniture ---- */ + float radius = length(p); + float ring = smoothstep(uBaseRingWidth, 0.0, abs(radius - uBaseRing)); + + // A cap arc at maximum reach, clipped to the width of the head so it reads + // as a range marker instead of a full circle. + float arc = smoothstep(0.05, 0.0, abs(radius - length_)) * + smoothstep(uHeadWidth * 2.2, uHeadWidth * 1.1, abs(p.x)) * uRangeArc; + + // A six-fold frost rosette pinned to the impact point. + vec2 q = p - vec2(0.0, length_); + float qr = length(q); + float qa = atan(q.y, q.x) + uTime * uTipSpin * TAU; + float spokes = smoothstep(0.86, 1.0, abs(cos(qa * 3.0))) * + smoothstep(uTipGlyphSize, 0.0, qr); + float glyphRing = smoothstep(0.045, 0.0, abs(qr - uTipGlyphSize * 0.5)); + float glyph = max(spokes, glyphRing) * uTipGlyph; + + /* ---- the sweep-out when the cast is armed ---- */ + float front = uReveal * (length_ + uTipGlyphSize); + float sweep = smoothstep(front + 0.25, front - 0.15, p.y); + float sweepEdge = smoothstep(0.35, 0.0, abs(p.y - front)) * step(uReveal, 0.999); + + /* ---- assemble ---- */ + float fill = body * wash * uFill; + float lines = outline * uEdgeGlow + ring + arc + glyph; + + float alpha = clamp(fill + lines + sweepEdge * 0.6, 0.0, 1.0) * sweep * uOpacity; + if (alpha < 0.004) discard; + + vec3 color = uColorEdge * fill + uColorCore * (lines + sweepEdge); + color = mix(color, uColorInvalid * (fill + lines + sweepEdge), uInvalid); + + gl_FragColor = vec4(color * uGlobalGlow, alpha); + } +`; + +/** + * The ground arrow drawn while a cast is armed. + * + * One quad, one shader, no textures. The quad is re-sized and re-oriented every + * frame from the live `settings.aim` block and the current aim, so dragging any + * slider in the editor reshapes the arrow under the cursor immediately. + */ +export class AimIndicator { + constructor() { + // Unit space: x ∈ [-0.5, 0.5], z ∈ [0, 1], facing up. Local +Z is the aim + // direction, so orienting the arrow is a single yaw. + this.geometry = new PlaneGeometry(1, 1, 1, 1) + .rotateX(-Math.PI / 2) + .translate(0, 0, 0.5); + + this.material = new ShaderMaterial({ + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uQuadLength: { value: 10 }, + uQuadWidth: { value: 6 }, + uQuadBack: { value: 1 }, + uLength: { value: 8 }, + uStart: { value: 0.9 }, + uShaftWidth: { value: 0.42 }, + uHeadLength: { value: 2.6 }, + uHeadWidth: { value: 1.35 }, + uRound: { value: 0.12 }, + uEdge: { value: 0.09 }, + uEdgeGlow: { value: 2.6 }, + uSoftness: { value: 0.06 }, + uFill: { value: 0.3 }, + uFillFalloff: { value: 1.1 }, + uStripes: { value: 0.55 }, + uStripeSharp: { value: 0.62 }, + uStripeDepth: { value: 0.55 }, + uScrollSpeed: { value: 2.4 }, + uPulse: { value: 0.28 }, + uPulseSpeed: { value: 2.2 }, + uNoise: { value: 0.45 }, + uNoiseScale: { value: 1.6 }, + uNoiseSpeed: { value: 0.35 }, + uCrystals: { value: 0.55 }, + uCrystalScale: { value: 2.4 }, + uBaseRing: { value: 0.62 }, + uBaseRingWidth: { value: 0.06 }, + uTipGlyph: { value: 0.9 }, + uTipGlyphSize: { value: 1.15 }, + uTipSpin: { value: 0.45 }, + uRangeArc: { value: 0.55 }, + uReveal: { value: 0 }, + uInvalid: { value: 0 }, + uOpacity: { value: 1 }, + uColorCore: { value: new Color(0.92, 0.98, 1) }, + uColorEdge: { value: new Color(0.24, 0.7, 1) }, + uColorInvalid: { value: new Color(1, 0.41, 0.36) } + }), + vertexShader: AIM_VERTEX, + fragmentShader: AIM_FRAGMENT + }); + + this.mesh = new Mesh(this.geometry, this.material); + this.mesh.name = 'AimIndicator'; + this.mesh.layers.set(LAYER.VFX); + this.mesh.renderOrder = 5; // under the VFX, over the floor + this.mesh.frustumCulled = false; + this.mesh.visible = false; + } + + get object3D() { + return this.mesh; + } + + /** + * Place and re-shape the arrow. + * + * @param {THREE.Vector3} origin the caster's feet + * @param {number} yaw heading of the aim line, radians about +Y + * @param {number} distance cast distance, metres + * @param {number} reveal 0..1 sweep-out + * @param {boolean} valid false tints the arrow to `colorInvalid` + */ + update(origin, yaw, distance, reveal, valid) { + const a = settings.aim; + const u = this.material.uniforms; + + // The quad has to hold the widest thing the shader draws, plus the ring that + // sits *behind* the caster and the rosette that sits *past* the tip. + const back = Math.max(a.baseRing, 0.2) + 0.4; + const forward = distance + Math.max(a.tipGlyphSize, 0.3) + 0.5; + const halfWidth = + Math.max(a.headWidth, a.shaftWidth, a.baseRing, a.tipGlyphSize) + a.edge + a.round + 0.5; + + const quadLength = back + forward; + const quadWidth = halfWidth * 2; + + u.uQuadLength.value = quadLength; + u.uQuadWidth.value = quadWidth; + u.uQuadBack.value = back; + u.uLength.value = distance; + u.uStart.value = a.startOffset; + u.uShaftWidth.value = a.shaftWidth; + u.uHeadLength.value = a.headLength; + u.uHeadWidth.value = a.headWidth; + u.uRound.value = a.round; + u.uEdge.value = a.edge; + u.uEdgeGlow.value = a.edgeGlow; + u.uSoftness.value = a.softness; + u.uFill.value = a.fill; + u.uFillFalloff.value = a.fillFalloff; + u.uStripes.value = a.stripes; + u.uStripeSharp.value = a.stripeSharp; + u.uStripeDepth.value = a.stripeDepth; + u.uScrollSpeed.value = a.scrollSpeed; + u.uPulse.value = a.pulse; + u.uPulseSpeed.value = a.pulseSpeed; + u.uNoise.value = a.noise; + u.uNoiseScale.value = a.noiseScale; + u.uNoiseSpeed.value = a.noiseSpeed; + u.uCrystals.value = a.crystals; + u.uCrystalScale.value = a.crystalScale; + u.uBaseRing.value = a.baseRing; + u.uBaseRingWidth.value = a.baseRingWidth; + u.uTipGlyph.value = a.tipGlyph; + u.uTipGlyphSize.value = a.tipGlyphSize; + u.uTipSpin.value = a.tipSpin; + u.uRangeArc.value = a.rangeArc; + u.uReveal.value = reveal; + u.uInvalid.value = valid ? 0 : 1; + u.uOpacity.value = a.opacity * settings.global.opacity; + u.uColorCore.value.copy(getColor(a.colorCore)); + u.uColorEdge.value.copy(getColor(a.colorEdge)); + u.uColorInvalid.value.copy(getColor(a.colorInvalid)); + + // Slide the quad back so the base ring is not clipped by its own near edge. + this.mesh.position.set( + origin.x - Math.sin(yaw) * back, + a.height, + origin.z - Math.cos(yaw) * back + ); + this.mesh.rotation.set(0, yaw, 0); + this.mesh.scale.set(quadWidth, 1, quadLength); + } + + setVisible(visible) { + this.mesh.visible = visible; + } + + dispose() { + this.geometry.dispose(); + this.material.dispose(); + } +} diff --git a/src/skillshot/effects/GroundFissures.js b/src/skillshot/effects/GroundFissures.js new file mode 100644 index 0000000..0eada0b --- /dev/null +++ b/src/skillshot/effects/GroundFissures.js @@ -0,0 +1,588 @@ +import { + BufferGeometry, + BufferAttribute, + Mesh, + MeshStandardMaterial, + InstancedMesh, + Object3D, + Group, + ShaderMaterial, + AdditiveBlending, + Color, + DynamicDrawUsage, + Sphere, + Vector3, + DoubleSide +} from 'three'; +import { createAsteroidGeometry } from '../ProceduralGeometry.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { LAYER } from '../../core/Layers.js'; +import { ObjectPool } from '../../utils/ObjectPool.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { clamp, saturate, lerp, randRange, Easing } from '../../utils/math.js'; + +/** + * Molten fissures — the ground tearing open under an impact. + * + * A network of cracks races outward from the point of impact: several main arms + * that meander rather than run straight, each shedding lightning-style side + * branches, drawn as ribbons that hug the floor. Every ribbon is rendered twice — + * a narrow white-hot **core** and a wide additive **underglow** that paints + * radiant orange onto the stone around it — and lumps of basalt are heaved up + * along the lips so the crack has physical relief instead of being a glowing + * decal. + * + * Ported from the molten-fissures paint mode in the geometry-painter sandbox and + * re-aimed at a crater: there the crack followed a stroke over a mesh surface, + * here it radiates across the floor plane, which makes the frame maths trivial + * (the normal is always +Y) and lets the whole network live in **unit space**. + * + * That last part is the important one. The paths are baked into a disc of radius + * 1 and the vertex shader multiplies them by `uRadius`, so the reach of the + * crater is a live slider that re-scales a network already on the ground rather + * than needing a rebuild. Width, branch density, branch length, heat and pulse + * are uniforms for the same reason. + * + * Buffers are allocated once. A spawn rewrites them; it never grows them. + */ + +/** Ribbon nodes across the whole network. The generator stops when it runs out. */ +const MAX_NODES = 900; +/** Basalt chunks heaved up along the lips. */ +const MAX_LIPS = 44; +/** Branches are generated at this count; the density slider culls them. */ +const MAX_BRANCHES = 8; +const TAU = Math.PI * 2; +/** Centreline resample step, in unit space. */ +const STEP = 0.045; + +const _dummy = new Object3D(); + +const FISSURE_VERTEX = /* glsl */ ` + uniform float uRadius; + uniform float uWidth; + uniform float uBranchFrac; + uniform float uLenFrac; + + attribute vec3 aSide; + attribute float aAcross; + attribute float aDist; + attribute float aJit; + attribute float aWalk; + attribute float aMaxWalk; + attribute float aRank; + + varying float vAcross; + varying float vDist; + varying float vTaper; + varying float vSel; + + void main() { + // A branch survives while its rank sits under the density fraction; the main + // arms are rank 0, so they can never be culled. + float sel = step(aRank, uBranchFrac); + // ... and is pinched to a point wherever the length slider currently ends. + float taper = pow(clamp(1.0 - aWalk / (aMaxWalk * uLenFrac + 1e-4), 0.0, 1.0), 0.7); + + vAcross = aAcross; + vDist = aDist; + vTaper = taper; + vSel = sel; + + // The network is baked in a unit disc; the reach is a live slider. + vec3 centre = vec3(position.x * uRadius, position.y, position.z * uRadius); + vec3 pos = centre + aSide * (uWidth * 0.5 * aAcross * aJit * taper * sel); + + gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0); + } +`; + +const FISSURE_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uGrown; + uniform float uHeat; + uniform float uPulse; + uniform float uFade; + uniform float uGlobalGlow; + uniform vec3 uColorSeam; + uniform vec3 uColorRed; + uniform vec3 uColorOrange; + uniform vec3 uColorWhite; + + varying float vAcross; + varying float vDist; + varying float vTaper; + varying float vSel; + + void main() { + // The crack is only open behind the racing front. + float openness = smoothstep(0.0, 0.08, uGrown - vDist); + if (openness <= 0.001 || vSel < 0.5) discard; + + #if FISSURE_PASS == 0 /* CORE */ + float centre = 1.0 - smoothstep(0.12, 1.0, abs(vAcross)); + float pulse = sin(vDist * 22.0 - uTime * uPulse * 2.6) * 0.28 + 0.72; + float flicker = sin(uTime * 9.0 + vDist * 120.0) * 0.08 + 0.94; + // A white flash rides the propagating crack front. + float flash = (1.0 - smoothstep(0.0, 0.14, abs(uGrown - vDist))) * 1.6; + + float heat = centre * pulse * flicker * uHeat * (vTaper * 0.35 + 0.65) + flash; + + vec3 color = mix(uColorSeam, uColorRed, smoothstep(0.0, 0.55, heat)); + color = mix(color, uColorOrange, smoothstep(0.55, 1.15, heat)); + color = mix(color, uColorWhite, smoothstep(1.15, 2.1, heat)); + + float edge = 1.0 - smoothstep(0.82, 1.0, abs(vAcross)); + float alpha = openness * edge * uFade; + + #else /* UNDERGLOW */ + float falloff = pow(max(1.0 - abs(vAcross), 0.0), 1.6); + float pulse = sin(vDist * 22.0 - uTime * uPulse * 2.6) * 0.22 + 0.78; + float strength = falloff * pulse * uHeat * (vTaper * 0.5 + 0.5) * 0.34; + vec3 color = uColorOrange * strength; + float alpha = openness * uFade * clamp(strength * 2.2, 0.0, 1.0); + #endif + + if (alpha < 0.004) discard; + gl_FragColor = vec4(color * uGlobalGlow, alpha); + } +`; + +/** Which of the two passes over the same ribbon geometry a material draws. */ +const FissurePass = Object.freeze({ CORE: 0, GLOW: 1 }); + +/* ---------------------------------------------------------------------- */ +/* One crater's worth of cracks */ +/* ---------------------------------------------------------------------- */ + +class Fissure { + constructor() { + this.group = new Group(); + this.group.name = 'Fissure'; + + const vertices = MAX_NODES * 2; + this.positions = new Float32Array(vertices * 3); + this.sides = new Float32Array(vertices * 3); + this.across = new Float32Array(vertices); + this.dists = new Float32Array(vertices); + this.jitters = new Float32Array(vertices); + this.walks = new Float32Array(vertices); + this.maxWalks = new Float32Array(vertices); + this.ranks = new Float32Array(vertices); + // Two triangles per node pair; the draw range is what actually limits it. + this.indices = new Uint16Array(MAX_NODES * 6); + + for (let i = 0; i < vertices; i++) this.across[i] = i % 2 === 0 ? -1 : 1; + + this.geometry = new BufferGeometry(); + const attribute = (array, size) => + new BufferAttribute(array, size).setUsage(DynamicDrawUsage); + this.geometry.setAttribute('position', attribute(this.positions, 3)); + this.geometry.setAttribute('aSide', attribute(this.sides, 3)); + this.geometry.setAttribute('aAcross', attribute(this.across, 1)); + this.geometry.setAttribute('aDist', attribute(this.dists, 1)); + this.geometry.setAttribute('aJit', attribute(this.jitters, 1)); + this.geometry.setAttribute('aWalk', attribute(this.walks, 1)); + this.geometry.setAttribute('aMaxWalk', attribute(this.maxWalks, 1)); + this.geometry.setAttribute('aRank', attribute(this.ranks, 1)); + this.geometry.setIndex(new BufferAttribute(this.indices, 1)); + this.geometry.setDrawRange(0, 0); + this.geometry.boundingSphere = new Sphere(new Vector3(), 1e4); + + this.materials = []; + this.meshes = []; + // Underglow first so the core adds on top of it. + for (const pass of [FissurePass.GLOW, FissurePass.CORE]) { + const material = new ShaderMaterial({ + defines: { FISSURE_PASS: pass }, + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uRadius: { value: 3 }, + uWidth: { value: 0.12 }, + uBranchFrac: { value: 0.7 }, + uLenFrac: { value: 0.8 }, + uGrown: { value: 0 }, + uHeat: { value: 1.4 }, + uPulse: { value: 1 }, + uFade: { value: 1 }, + uColorSeam: { value: new Color(0.02, 0.004, 0.002) }, + uColorRed: { value: new Color(1.1, 0.1, 0.01) }, + uColorOrange: { value: new Color(2.6, 0.85, 0.1) }, + uColorWhite: { value: new Color(4.6, 3.6, 2.4) } + }), + vertexShader: FISSURE_VERTEX, + fragmentShader: FISSURE_FRAGMENT + }); + + const mesh = new Mesh(this.geometry, material); + mesh.frustumCulled = false; + mesh.layers.set(LAYER.VFX); + mesh.renderOrder = pass === FissurePass.GLOW ? 7 : 9; + this.group.add(mesh); + this.materials.push(material); + this.meshes.push(mesh); + } + + /** + * Basalt heaved up along the lips. + * + * Records hold dice only — a unit-space anchor, which lip it sits on, and a + * few jitters. Their metres come from the settings every frame, so the + * crater's reach and the chunk size stay live sliders. + */ + // Few cuts: at this subdivision a heavily sliced rock flattens into a pebble, + // and these want to read as blocks of crust shouldered out of the ground. + this.lipGeometry = createAsteroidGeometry({ + seed: 4.2, + detail: 1, + lumpiness: 0.34, + cuts: 3, + cutDepth: 0.16, + craters: 2 + }); + this.lipMaterial = new MeshStandardMaterial({ + color: 0x565056, + roughness: 0.95, + metalness: 0.02, + flatShading: true + }); + this.lips = new InstancedMesh(this.lipGeometry, this.lipMaterial, MAX_LIPS); + this.lips.castShadow = true; + this.lips.receiveShadow = true; + this.lips.frustumCulled = false; + this.lips.count = 0; + this.lips.layers.set(LAYER.WORLD); + this.group.add(this.lips); + + this.lipRecords = []; + for (let i = 0; i < MAX_LIPS; i++) { + this.lipRecords.push({ + x: 0, // unit-space anchor + z: 0, + sideX: 0, // unit lateral, signed: which lip + sideZ: 0, + yaw: 0, + dist: 0, // where along the crack it sits, 0..1 + size: 0, // 0..1 jitter + flat: 0, // 0..1 squash + offset: 0, // 0..1 how far outside the crack edge + rank: 0 // culled with its branch + }); + } + + this.age = 0; + this.life = 1; + this.radius = 3; + this.nodeCount = 0; + this.lipCount = 0; + } + + /* ------------------------------------------------------------------ */ + + /** + * Generate a fresh crack network in unit space and upload it. + * + * The walk is deliberately *not* radial: an arm veers as it advances and its + * branches veer harder, which is the whole difference between a crack and the + * star of spokes you get from drawing lines out of a centre. + * + * @param {number} arms main cracks radiating from the impact + * @param {number} wander how hard an arm veers, radians per unit walked + */ + _generate(arms, wander) { + let node = 0; + let quad = 0; + + const pushNode = (x, z, sx, sz, dist, jitter, walk, maxWalk, rank) => { + if (node >= MAX_NODES) return false; + const v = node * 2; + for (let k = 0; k < 2; k++) { + const i = v + k; + this.positions[i * 3 + 0] = x; + this.positions[i * 3 + 1] = 0; + this.positions[i * 3 + 2] = z; + this.sides[i * 3 + 0] = sx; + this.sides[i * 3 + 1] = 0; + this.sides[i * 3 + 2] = sz; + this.dists[i] = dist; + this.jitters[i] = jitter; + this.walks[i] = walk; + this.maxWalks[i] = maxWalk; + this.ranks[i] = rank; + } + node++; + return true; + }; + + /** + * Walk one crack across the floor. + * @returns {Array} the nodes it laid down, for branches to hang off + */ + const walk = (startX, startZ, heading, length, rank, originDist, widthScale) => { + const laid = []; + let x = startX; + let z = startZ; + let angle = heading; + let jitter = 1; + const curvature = randRange(-wander, wander); + const first = node; + + for (let travelled = 0; travelled <= length; travelled += STEP) { + // Smoothed random walk → organic width variation baked per node. + jitter = clamp(jitter + randRange(-0.18, 0.18), 0.6, 1.45); + const dist = saturate(originDist + travelled); + // A crack terminates in a needle, not a rounded cap. + const tip = Math.pow(saturate((length - travelled) / 0.22), 0.65); + const width = jitter * widthScale * (rank > 0 ? 0.62 : tip); + + // The lateral is perpendicular to the heading, in the floor plane. + if (!pushNode(x, z, Math.cos(angle + Math.PI / 2), Math.sin(angle + Math.PI / 2), + dist, width, rank > 0 ? travelled : 0, rank > 0 ? length : 1, rank)) break; + laid.push({ x, z, angle, dist }); + + x += Math.cos(angle) * STEP; + z += Math.sin(angle) * STEP; + // Veer, plus a little high-frequency stagger so the line is never smooth. + angle += curvature * STEP + randRange(-0.35, 0.35) * STEP; + // ... but pull it back toward its launch bearing, or a steady veer curls + // an arm into a circle instead of driving it away from the impact. + angle = lerp(angle, heading, 0.06); + } + + // Stitch this run into the index buffer. Separate runs are never joined. + for (let i = first; i < node - 1; i++) { + const a = i * 2; + this.indices[quad * 6 + 0] = a; + this.indices[quad * 6 + 1] = a + 1; + this.indices[quad * 6 + 2] = a + 2; + this.indices[quad * 6 + 3] = a + 1; + this.indices[quad * 6 + 4] = a + 3; + this.indices[quad * 6 + 5] = a + 2; + quad++; + } + return laid; + }; + + const spin = Math.random() * TAU; + const mains = []; + for (let i = 0; i < arms; i++) { + // Uneven spacing: equal angles read as a star however much they meander. + const heading = spin + (i / arms) * TAU + randRange(-0.45, 0.45); + const length = randRange(0.6, 1.0); + // Start just outside the crater mouth rather than at a single point. + const r0 = randRange(0.04, 0.12); + mains.push(walk(Math.cos(heading) * r0, Math.sin(heading) * r0, heading, length, 0, r0, 1)); + } + + /* --- branches, hung off the arms --- */ + for (let b = 0; b < MAX_BRANCHES; b++) { + const arm = mains[Math.floor(Math.random() * mains.length)]; + if (!arm || arm.length < 4) continue; + const at = arm[Math.floor(randRange(1, arm.length - 1))]; + const side = Math.random() < 0.5 ? 1 : -1; + // 32°–72° off the parent, like a lightning fork. + const heading = at.angle + side * randRange(0.55, 1.25); + walk(at.x, at.z, heading, randRange(0.18, 0.42), (b + 1) / MAX_BRANCHES, at.dist, 0.8); + } + + this.nodeCount = node; + this.geometry.setDrawRange(0, quad * 6); + for (const name of ['position', 'aSide', 'aAcross', 'aDist', 'aJit', 'aWalk', 'aMaxWalk', 'aRank']) { + this.geometry.attributes[name].needsUpdate = true; + } + this.geometry.index.needsUpdate = true; + + /* --- basalt along the lips --- */ + let lip = 0; + for (const arm of mains) { + for (let i = 2; i < arm.length && lip < MAX_LIPS; i += 3) { + const point = arm[i]; + const record = this.lipRecords[lip++]; + record.x = point.x; + record.z = point.z; + record.sideX = Math.cos(point.angle + Math.PI / 2) * (i % 6 === 2 ? 1 : -1); + record.sideZ = Math.sin(point.angle + Math.PI / 2) * (i % 6 === 2 ? 1 : -1); + record.yaw = point.angle + randRange(-0.5, 0.5); + record.dist = point.dist; + record.size = Math.random(); + record.flat = Math.random(); + record.offset = Math.random(); + record.rank = 0; + } + } + this.lipCount = lip; + } + + /** Pose the lip chunks from the live settings. */ + _poseLips(grown, sink) { + const c = settings.meteor; + let used = 0; + + for (let i = 0; i < this.lipCount; i++) { + const record = this.lipRecords[i]; + // Chunks are heaved up as the crack front passes them. + const emerge = saturate((grown - record.dist) / 0.18); + const size = c.fissureRockSize * lerp(0.55, 1.35, record.size); + + if (emerge <= 0) { + _dummy.position.set(0, -999, 0); + _dummy.scale.setScalar(0.0001); + _dummy.quaternion.identity(); + } else { + const out = c.fissureWidth * 0.55 + record.offset * c.fissureWidth * 0.6 + size * 0.15; + _dummy.position.set( + record.x * this.radius + record.sideX * out, + // Sunk well in, so only the top ridge breaks through: broken crust, + // not pebbles scattered on a floor. + -size * lerp(0.3, 0.6, record.flat) - sink * (size * 2 + 0.4), + record.z * this.radius + record.sideZ * out + ); + _dummy.rotation.set(0, record.yaw, 0); + const grow = Easing.outBack(emerge); + _dummy.scale.set(size * grow, size * lerp(0.45, 0.85, record.flat) * grow, size * 0.8 * grow); + } + + _dummy.updateMatrix(); + this.lips.setMatrixAt(i, _dummy.matrix); + used = i + 1; + } + + this.lips.count = used; + this.lips.instanceMatrix.needsUpdate = true; + } + + /* ------------------------------------------------------------------ */ + + spawn(position, options) { + const c = settings.meteor; + this.age = 0; + this.life = Math.max(0.2, options.life ?? c.fissureLife); + this.radius = options.radius ?? c.fissureRadius; + + this.group.position.set(position.x, options.height ?? 0.02, position.z); + this.group.rotation.y = Math.random() * TAU; + + this._generate(Math.max(2, Math.round(c.fissureArms)), c.fissureWander); + this._poseLips(0, 0); + this.sync(0); + } + + /** Push the live settings into both passes. */ + sync(grown) { + const c = settings.meteor; + const g = settings.global; + const t = saturate(this.age / this.life); + + // The melt cools long before the crack closes, then the whole thing fades. + const heat = c.fissureHeat * (1 - Easing.inQuad(saturate((t - 0.25) / 0.75))) * g.shaderIntensity; + const fade = 1 - Easing.inQuad(saturate((t - 0.7) / 0.3)); + + for (const material of this.materials) { + const u = material.uniforms; + u.uRadius.value = this.radius; + u.uWidth.value = + material.defines.FISSURE_PASS === FissurePass.CORE + ? c.fissureWidth + : c.fissureWidth * 3.4 + 0.16; + u.uBranchFrac.value = saturate(c.fissureBranches); + u.uLenFrac.value = saturate(c.fissureBranchLength); + u.uGrown.value = grown; + u.uHeat.value = heat; + u.uPulse.value = c.fissurePulse; + u.uFade.value = fade; + u.uColorSeam.value.copy(getColor(c.colorScorch)); + u.uColorRed.value.copy(getColor(c.colorFlameEdge)); + u.uColorOrange.value.copy(getColor(c.colorCrack)); + u.uColorWhite.value.copy(getColor(c.colorHot)); + } + + // Charred, not grey: these sit *in* the melt, and a mid-grey chunk lit by the + // fire light reads as a pale pebble scattered on the floor. + this.lipMaterial.color.copy(getColor(c.colorChar)); + } + + update(dt) { + const c = settings.meteor; + this.age += dt; + const t = saturate(this.age / this.life); + + // The front races out from the impact, then the lips sink back as it dies. + const grown = saturate((this.age * c.fissureGrowth) / Math.max(0.05, this.radius)); + const sink = Easing.inCubic(saturate((t - 0.82) / 0.18)); + + this.sync(grown); + this._poseLips(grown, sink); + + return t < 1; + } + + dispose() { + this.geometry.dispose(); + this.lipGeometry.dispose(); + this.lipMaterial.dispose(); + this.lips.dispose(); + for (const material of this.materials) material.dispose(); + } +} + +/* ---------------------------------------------------------------------- */ + +/** + * Pooled crack networks. One instance is reused per impact, so casting twenty + * meteors builds at most a handful of ribbon buffers. + */ +export class FissureSystem { + constructor(scene) { + this.group = new Group(); + this.group.name = 'Fissures'; + scene.add(this.group); + + this.active = []; + this.pool = new ObjectPool( + () => new Fissure(), + (fissure) => { + fissure.group.visible = false; + this.group.remove(fissure.group); + } + ); + } + + /** + * Tear the ground open. + * @param {THREE.Vector3} position where it was hit + * @param {object} [options] { radius, life, height } + */ + spawn(position, options = {}) { + const fissure = this.pool.acquire(); + fissure.spawn(position, options); + fissure.group.visible = true; + this.group.add(fissure.group); + this.active.push(fissure); + return fissure; + } + + update(dt) { + for (let i = this.active.length - 1; i >= 0; i--) { + if (!this.active[i].update(dt)) { + this.pool.release(this.active.splice(i, 1)[0]); + } + } + } + + clear() { + for (const fissure of this.active) this.pool.release(fissure); + this.active.length = 0; + } + + dispose() { + this.clear(); + this.pool.dispose((fissure) => fissure.dispose()); + this.group.parent?.remove(this.group); + } +} diff --git a/src/skillshot/effects/ZoneIndicator.js b/src/skillshot/effects/ZoneIndicator.js new file mode 100644 index 0000000..cd12de0 --- /dev/null +++ b/src/skillshot/effects/ZoneIndicator.js @@ -0,0 +1,484 @@ +import { + Group, + Mesh, + PlaneGeometry, + ShaderMaterial, + AdditiveBlending, + Color, + DoubleSide, + Vector3 +} from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { createBoltRibbonGeometry } from '../ProceduralGeometry.js'; +import { LAYER } from '../../core/Layers.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; +import { saturate, Easing } from '../../utils/math.js'; + +const ZONE_VERTEX = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +/** + * The footprint: one signed-distance ring evaluated in **metres**. + * + * `vUv` is remapped so `p` is the offset from the target point in world metres, + * with `p.y` running along the aim heading — the quad carries the caster's yaw, + * so the long reticle arm points downrange. Every control in `settings.zone` is + * therefore a real measurement: the boundary stays 0.34 m thick whether the + * footprint is 2 m or 8 m across, which is the entire point of drawing it. + * + * The band is split about the nominal radius by `uBias` rather than centred on + * it, because the number the player is judging is *where the edge of the effect + * is* — growing the band inward keeps its outer lip honest. + */ +const ZONE_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uQuadSize; // metres the quad covers, edge to edge + uniform float uRadius; // footprint radius, metres (already snapped) + uniform float uBoundary; // thickness of the band + uniform float uBias; // how much of that thickness sits outside uRadius + uniform float uBoundaryGlow; + uniform float uLiner; + uniform float uSoftness; + uniform float uFill; + uniform float uFillFalloff; + uniform float uRings; + uniform float uRingWidth; + uniform float uRingSpeed; + uniform float uCrawl; + uniform float uCrawlScale; + uniform float uCrawlSpeed; + uniform float uNoise; + uniform float uNoiseScale; + uniform float uTicks; + uniform float uTickLength; + uniform float uTickWidth; + uniform float uTickSpin; + uniform float uSweep; + uniform float uSweepSpeed; + uniform float uCore; + uniform float uCoreSize; + uniform float uCrosshair; + uniform float uCrosshairLength; + uniform float uPulse; + uniform float uPulseSpeed; + uniform float uReveal; // 0..1 snap-out + uniform float uInvalid; // 1 when the target is inside the minimum range + uniform float uOpacity; + uniform vec3 uColorCore; + uniform vec3 uColorEdge; + uniform vec3 uColorInvalid; + uniform float uGlobalGlow; + + varying vec2 vUv; + + ${noiseGLSL} + ${commonGLSL} + + #define TAU 6.28318530718 + + void main() { + /* ---- uv → metres, measured from the target; +y is downrange ---- */ + vec2 p = vec2(vUv.x - 0.5, 0.5 - vUv.y) * uQuadSize; + float d = length(p); + + float outer = uRadius + uBoundary * uBias; + float inner = max(0.01, uRadius - uBoundary * (1.0 - uBias)); + + float aa = fwidth(d) + uSoftness; + if (d > outer + aa * 3.0) discard; + + /* ---- the band that *is* the footprint ---- */ + float band = smoothstep(outer + aa, outer - aa, d) * smoothstep(inner - aa, inner + aa, d); + // A hard liner on the inside lip: the band alone reads soft at a distance, + // and the inside lip is the line the player is actually measuring against. + float liner = 1.0 - smoothstep(uLiner, uLiner + aa, abs(d - inner)); + + float interior = smoothstep(inner + aa, inner - aa, d); + float radial = clamp(d / inner, 0.0, 1.0); + + /* ---- the wash inside it ---- */ + // Weighted to the rim: a flat disc reads as a decal lying on the floor, a + // rim-weighted one reads as a volume standing inside the boundary. + float wash = pow(radial, uFillFalloff); + float n = fbm3(vec3(p * uNoiseScale, uTime * 0.2)) * 0.5 + 0.5; + wash *= mix(1.0, n, uNoise); + + // Contour rings travelling outward — the read that says "this is a field + // with a size", not a puddle. + float ringPhase = radial * uRings - uTime * uRingSpeed; + float ring = smoothstep(1.0 - uRingWidth, 1.0, 0.5 + 0.5 * cos(ringPhase * TAU)); + + // Filaments crawling over the interior, sampled in the *plane* and domain + // warped. Sampling on atan() would hand every radius along a bearing the + // same value and draw dead-straight spokes — a firework, not a field. + float warp = fbm3(vec3(p * 0.4, uTime * 0.15 + 3.1)) * 0.6; + float fil = ridged(vec3(p * uCrawlScale + warp, uTime * uCrawlSpeed), 4); + float veins = smoothstep(0.68, 0.95, fil); + + wash += ring * 0.4; + wash += veins * uCrawl * (0.3 + 0.7 * radial); + + /* ---- furniture ---- */ + float ang = atan(p.y, p.x) / TAU + 0.5; + + // Ticks stepping around the boundary. Deliberate radial marks, so this is + // the one place an angular function is the right tool. + float tickPhase = fract(ang * uTicks + uTime * uTickSpin * uTicks); + float tick = 1.0 - smoothstep(uTickWidth, uTickWidth + 0.06, tickPhase); + tick *= smoothstep(inner - uTickLength, inner, d) * smoothstep(outer, inner, d); + + // A slow sweep, trailing rather than symmetric, so it reads as rotating. + // The second term feathers the wrap point: without it the tail meets the + // head at full brightness and the sweep reads as a hard wedge. + float sweepPhase = fract(ang - uTime * uSweepSpeed); + float sweep = pow(1.0 - sweepPhase, 6.0) * smoothstep(0.0, 0.05, sweepPhase) * uSweep * interior; + + float core = smoothstep(uCoreSize, 0.0, d) * uCore; + float coreRing = (1.0 - smoothstep(0.02, 0.045, abs(d - uCoreSize * 0.8))) * uCore * 0.8; + + // Four arms out of the core, the downrange one longer: with the quad yawed + // onto the aim, that arm is the heading. + float armLength = uCrosshairLength * mix(1.0, 1.8, step(0.0, p.y) * step(abs(p.x), abs(p.y))); + float arms = max(1.0 - smoothstep(0.02, 0.05, abs(p.x)), 1.0 - smoothstep(0.02, 0.05, abs(p.y))); + arms *= smoothstep(armLength, armLength * 0.25, d) * smoothstep(uCoreSize * 0.5, uCoreSize, d); + arms *= uCrosshair; + + /* ---- assemble ---- */ + float breathe = 1.0 + uPulse * sin(uTime * uPulseSpeed * TAU); + float fill = interior * wash * uFill * breathe; + float lines = (liner * 1.3 + tick + core + coreRing + arms + sweep) * breathe; + float edge = band * uBoundaryGlow * breathe; + + float alpha = clamp(fill + lines + edge, 0.0, 1.0) * uOpacity * uReveal; + if (alpha < 0.004) discard; + + // The band is drawn halfway between the two colours rather than in the core + // white the rest of the furniture uses. At the glow it needs to read as the + // thickest thing on screen it would otherwise clip flat, and the ability + // loses the one cue that says which cast this circle belongs to. + vec3 color = uColorEdge * fill + uColorCore * lines + mix(uColorEdge, uColorCore, 0.5) * edge; + color = mix(color, uColorInvalid * (fill + lines + edge), uInvalid); + + gl_FragColor = vec4(color * uGlobalGlow, alpha); + } +`; + +/** + * The reach ring, drawn on the bolt's ribbon strip bent into a circle. + * + * A quad big enough to hold a 20 m range would be 40 m across and shade a + * screenful of discarded fragments for one thin line; a strip costs the ring + * itself and nothing else. `(t, side)` arrives as "how far around" and "which + * lip", and comes out as a world position — same contract as every other + * parametric mesh in the project. + */ +const REACH_VERTEX = /* glsl */ ` + #define TAU 6.283185307179586 + + uniform float uTime; + uniform vec3 uCentre; + uniform float uRadius; + uniform float uWidth; + uniform float uSpin; + + varying float vAngle; + varying float vSide; + + void main() { + float t = position.x; + float side = position.y; + vAngle = t; + vSide = side; + + float a = (t + uTime * uSpin) * TAU; + vec3 dir = vec3(sin(a), 0.0, cos(a)); + vec3 world = uCentre + dir * (uRadius + side * uWidth); + + gl_Position = projectionMatrix * viewMatrix * vec4(world, 1.0); + } +`; + +const REACH_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uDashes; + uniform float uDashGap; + uniform float uSpin; + uniform float uLead; // bearing of the cursor, as a 0..1 fraction + uniform float uLeadStrength; + uniform float uIntensity; + uniform float uReveal; + uniform float uInvalid; + uniform float uOpacity; + uniform vec3 uColorCore; + uniform vec3 uColorEdge; + uniform vec3 uColorInvalid; + uniform float uGlobalGlow; + + varying float vAngle; + varying float vSide; + + void main() { + float profile = 1.0 - clamp(abs(vSide), 0.0, 1.0); + profile = pow(profile, 1.6); + + // The dash pattern is welded to the strip's own parameter, and the vertex + // shader is what rotates the strip — so the dashes creep with the ring + // rather than sitting still in the world while it turns under them. + float dash = 1.0; + if (uDashes > 0.5) { + float phase = fract(vAngle * uDashes); + dash = 1.0 - smoothstep(1.0 - uDashGap, 1.0 - uDashGap + 0.12, phase); + } + + // The lead marker is pinned to a *world* bearing, so it has to undo the + // spin the vertex shader applied. Shortest way round, 0..0.5. + float world = fract(vAngle + uTime * uSpin); + float delta = abs(fract(world - uLead + 0.5) - 0.5); + float lead = smoothstep(0.12, 0.0, delta) * uLeadStrength; + + float alpha = profile * (dash * uIntensity + lead) * uReveal * uOpacity; + if (alpha < 0.004) discard; + + vec3 color = mix(uColorEdge, uColorCore, clamp(lead + profile * 0.4, 0.0, 1.0)); + color = mix(color, uColorInvalid, uInvalid); + + gl_FragColor = vec4(color * uGlobalGlow, clamp(alpha, 0.0, 1.0)); + } +`; + +/** + * The ground circle drawn while a **far cast** is armed. + * + * Two parametric meshes and no textures: the footprint at the cursor, and the + * reach ring at the caster's feet. Both are rebuilt from the live + * `settings.zone` block every frame, so dragging any slider reshapes the circle + * under the cursor immediately — and dragging the ability's own `zoneRadius` + * resizes the promise the indicator is making. + * + * The footprint snaps out past its radius and settles back when the cast is + * armed. That overshoot is the whole personality of the indicator: a circle + * that grows linearly reads as a UI element, one that slams out reads as + * something the caster did. + */ +export class ZoneIndicator { + constructor() { + this.group = new Group(); + this.group.name = 'ZoneIndicator'; + this.group.matrixAutoUpdate = false; + + /* ---- the footprint ---- */ + // Unit square in the ground plane, centred: local +Z is the aim heading, so + // placing it is a yaw and a scale. + this.discGeometry = new PlaneGeometry(1, 1, 1, 1).rotateX(-Math.PI / 2); + this.discMaterial = new ShaderMaterial({ + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uQuadSize: { value: 12 }, + uRadius: { value: 4.4 }, + uBoundary: { value: 0.34 }, + uBias: { value: 0.35 }, + uBoundaryGlow: { value: 2.4 }, + uLiner: { value: 0.05 }, + uSoftness: { value: 0.05 }, + uFill: { value: 0.18 }, + uFillFalloff: { value: 1.5 }, + uRings: { value: 2 }, + uRingWidth: { value: 0.05 }, + uRingSpeed: { value: 0.35 }, + uCrawl: { value: 0.6 }, + uCrawlScale: { value: 1.3 }, + uCrawlSpeed: { value: 0.45 }, + uNoise: { value: 0.4 }, + uNoiseScale: { value: 1.2 }, + uTicks: { value: 24 }, + uTickLength: { value: 0.42 }, + uTickWidth: { value: 0.2 }, + uTickSpin: { value: 0.06 }, + uSweep: { value: 0.55 }, + uSweepSpeed: { value: 0.4 }, + uCore: { value: 0.85 }, + uCoreSize: { value: 0.4 }, + uCrosshair: { value: 0.5 }, + uCrosshairLength: { value: 1.1 }, + uPulse: { value: 0.22 }, + uPulseSpeed: { value: 2 }, + uReveal: { value: 0 }, + uInvalid: { value: 0 }, + uOpacity: { value: 1 }, + uColorCore: { value: new Color(0.92, 0.97, 1) }, + uColorEdge: { value: new Color(0.49, 0.42, 1) }, + uColorInvalid: { value: new Color(1, 0.41, 0.36) } + }), + vertexShader: ZONE_VERTEX, + fragmentShader: ZONE_FRAGMENT + }); + + this.disc = new Mesh(this.discGeometry, this.discMaterial); + this.disc.name = 'ZoneFootprint'; + this.disc.layers.set(LAYER.VFX); + this.disc.renderOrder = 5; // under the VFX, over the floor + this.disc.frustumCulled = false; + + /* ---- the reach ring ---- */ + this.reachGeometry = createBoltRibbonGeometry(settings.zone.reachSegments + 1, 1); + this.reachMaterial = new ShaderMaterial({ + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uCentre: { value: new Vector3() }, + uRadius: { value: 20 }, + uWidth: { value: 0.05 }, + uSpin: { value: 0.03 }, + uDashes: { value: 64 }, + uDashGap: { value: 0.42 }, + uLead: { value: 0 }, + uLeadStrength: { value: 0.9 }, + uIntensity: { value: 0.7 }, + uReveal: { value: 0 }, + uInvalid: { value: 0 }, + uOpacity: { value: 1 }, + uColorCore: { value: new Color(0.92, 0.97, 1) }, + uColorEdge: { value: new Color(0.49, 0.42, 1) }, + uColorInvalid: { value: new Color(1, 0.41, 0.36) } + }), + vertexShader: REACH_VERTEX, + fragmentShader: REACH_FRAGMENT + }); + + this.reach = new Mesh(this.reachGeometry, this.reachMaterial); + this.reach.name = 'ZoneReachRing'; + this.reach.layers.set(LAYER.VFX); + this.reach.renderOrder = 5; + this.reach.frustumCulled = false; + + this.group.add(this.disc, this.reach); + this.group.visible = false; + } + + get object3D() { + return this.group; + } + + /** + * Place and re-shape the circle. + * + * @param {THREE.Vector3} origin the caster's feet + * @param {number} yaw heading of the aim, radians about +Y + * @param {number} distance how far out the target point is, metres + * @param {number} radius the ability's footprint, metres + * @param {number} range the ability's maximum reach, metres + * @param {number} reveal 0..1 snap-out + * @param {boolean} valid false tints the circle to `colorInvalid` + */ + update(origin, yaw, distance, radius, range, reveal, valid) { + const z = settings.zone; + const opacity = z.opacity * settings.global.opacity; + const invalid = valid ? 0 : 1; + + /* ---- the snap-out ---- */ + // Two curves multiplied: a grow that settles at 1, and a bump that peaks + // late and dies at exactly 1 so the circle overshoots its own radius on the + // way out and pulls back onto it. A linear grow reads as a UI element. + const t = saturate(reveal); + const bump = Math.sin(Math.PI * Math.pow(t, 1.7)); + const snapped = radius * Easing.outCubic(t) * (1 + (z.snap - 1) * bump); + + /* ---- the footprint ---- */ + const quadSize = (radius * Math.max(1, z.snap) + z.boundary + 0.6) * 2; + const u = this.discMaterial.uniforms; + + u.uQuadSize.value = quadSize; + u.uRadius.value = Math.max(0.05, snapped); + u.uBoundary.value = z.boundary; + u.uBias.value = z.boundaryBias; + u.uBoundaryGlow.value = z.boundaryGlow; + u.uLiner.value = z.liner; + u.uSoftness.value = z.softness; + u.uFill.value = z.fill; + u.uFillFalloff.value = z.fillFalloff; + u.uRings.value = z.rings; + u.uRingWidth.value = z.ringWidth; + u.uRingSpeed.value = z.ringSpeed; + u.uCrawl.value = z.crawl; + u.uCrawlScale.value = z.crawlScale; + u.uCrawlSpeed.value = z.crawlSpeed; + u.uNoise.value = z.noise; + u.uNoiseScale.value = z.noiseScale; + u.uTicks.value = Math.max(0, Math.round(z.ticks)); + u.uTickLength.value = z.tickLength; + u.uTickWidth.value = z.tickWidth; + u.uTickSpin.value = z.tickSpin; + u.uSweep.value = z.sweep; + u.uSweepSpeed.value = z.sweepSpeed; + u.uCore.value = z.core; + u.uCoreSize.value = z.coreSize; + u.uCrosshair.value = z.crosshair; + u.uCrosshairLength.value = z.crosshairLength; + u.uPulse.value = z.pulse; + u.uPulseSpeed.value = z.pulseSpeed; + u.uReveal.value = t; + u.uInvalid.value = invalid; + u.uOpacity.value = opacity; + u.uColorCore.value.copy(getColor(z.colorCore)); + u.uColorEdge.value.copy(getColor(z.colorEdge)); + u.uColorInvalid.value.copy(getColor(z.colorInvalid)); + + this.disc.position.set( + origin.x + Math.sin(yaw) * distance, + z.height, + origin.z + Math.cos(yaw) * distance + ); + this.disc.rotation.set(0, yaw, 0); + this.disc.scale.set(quadSize, 1, quadSize); + + /* ---- the reach ring ---- */ + const r = this.reachMaterial.uniforms; + r.uCentre.value.set(origin.x, z.height, origin.z); + r.uRadius.value = Math.max(0.2, range); + r.uWidth.value = z.reachWidth; + r.uSpin.value = z.reachSpin; + r.uDashes.value = Math.max(0, Math.round(z.reachDashes)); + r.uDashGap.value = z.reachDashGap; + // The strip's `t` runs from +Z anticlockwise, which is how the vertex + // shader lays it out — so the cursor's bearing is just its yaw over a turn. + r.uLead.value = ((yaw / (Math.PI * 2)) % 1 + 1) % 1; + r.uLeadStrength.value = z.reachLead; + r.uIntensity.value = z.reach; + r.uReveal.value = t; + r.uInvalid.value = invalid; + r.uOpacity.value = opacity; + r.uColorCore.value.copy(getColor(z.colorCore)); + r.uColorEdge.value.copy(getColor(z.colorEdge)); + r.uColorInvalid.value.copy(getColor(z.colorInvalid)); + + this.reach.visible = z.reach > 0.001; + } + + setVisible(visible) { + this.group.visible = visible; + } + + dispose() { + this.discGeometry.dispose(); + this.discMaterial.dispose(); + this.reachGeometry.dispose(); + this.reachMaterial.dispose(); + } +} diff --git a/src/skillshot/linearSettings.js b/src/skillshot/linearSettings.js new file mode 100644 index 0000000..4e3f443 --- /dev/null +++ b/src/skillshot/linearSettings.js @@ -0,0 +1,1903 @@ +/** + * settings.js — the single source of truth for every tweakable value in the sandbox. + * + * Nothing in the renderer owns state that lives here: shaders, particle systems, + * lights and post processing all *read* these objects every frame. That is what + * makes the real-time editor work without rebuilding anything — mutating a field + * is immediately visible on screen, including on an ice field that is already + * standing, and including while the clock is paused (`P`), which is when the + * shapes are actually worth tuning. + * + * The one rule that keeps that promise: a system may only ever *sample* these + * values. It must never copy one into a record at spawn time and read it back + * later — see `IceAbility`, whose spike records hold nothing but unitless dice + * rolls, and resolve every metre, radian and second against this file each frame. + * + * Conventions + * - Colours are stored as `#rrggbb` strings so lil-gui can bind them directly. + * Use `utils/color.js#getColor()` to read them as a cached THREE.Color. + * - `global` holds multipliers that scale everything at once (1 = neutral). + * - The per-ability blocks (`ice`, `thunder`, `meteor`, `beam`) hold absolute values. + * + * Every ability block is keyed by its id in `ELEMENTS`, and the shared systems + * that need to know about "the ability the player is currently holding" — the + * aim controller, the cooldown, the HUD — look it up as `settings[element]`. + * The four fields they rely on being present are `range`, `minRange`, `speed` + * and `cooldown`; everything else in a block is that ability's own business. + * A **far cast** (`CastShape.ZONE`, declared in `ELEMENT_META`) adds a fifth: + * `zoneRadius`, the footprint the circle indicator measures out. + */ + +/** + * The cast animations shipped alongside the rig, in `public/models/.fbx`. + * + * Every ability block carries a `castAnim` naming one of these, so each spell + * can throw the body differently; `CharacterController` loads all of them once + * at boot and keeps only their clips, and the editor turns this array straight + * into the per-ability dropdown. + */ +export const CAST_ANIMATIONS = ['cast1', 'cast2', 'cast3']; + +export const settings = { + /* ------------------------------------------------------------------ */ + /* Global multipliers */ + /* ------------------------------------------------------------------ */ + global: { + timeScale: 1.0, // slow-mo / fast forward for the whole simulation + speed: 1.0, // eruption travel speed multiplier + lifetime: 1.0, // ability lifetime multiplier + glow: 1.0, // emissive multiplier fed into bloom + shaderIntensity: 1.0, // master strength of every procedural shader effect + noiseStrength: 1.0, + noiseFrequency: 1.0, + noiseSpeed: 1.0, + turbulence: 1.0, + randomness: 1.0, // per-instance / per-particle jitter multiplier + particleCount: 1.0, + particleLifetime: 1.0, + particleSpeed: 1.0, + particleSize: 1.0, + emissionRate: 1.0, + lightIntensity: 1.0, + lightRadius: 1.0, + distortion: 1.0, + fresnel: 1.0, + opacity: 1.0, + animationSpeed: 1.0, // character animation playback rate + cameraShake: 1.0, + explosionIntensity: 1.0 + }, + + /* ------------------------------------------------------------------ */ + /* The aim indicator — the ground arrow drawn while the cast is armed */ + /* ------------------------------------------------------------------ */ + /** + * A League-style skillshot indicator: one ground quad with a signed-distance + * arrow in its fragment shader, so every dimension below is in *metres* and + * nothing is a texture. The quad is rebuilt from these numbers each frame, + * which is why dragging `range` while aiming stretches the arrow live. + */ + aim: { + /* --- silhouette (metres) --- */ + shaftWidth: 0.42, // half-width of the shaft + headLength: 2.6, // length of the arrowhead + headWidth: 1.35, // half-width at the base of the head + round: 0.12, // corner rounding of the whole silhouette + startOffset: 0.9, // gap between the caster and the tail of the arrow + + /* --- rendering --- */ + edge: 0.09, // outline thickness, metres + edgeGlow: 2.6, // how hard the outline blooms + softness: 0.06, // feather on the outer edge + fill: 0.3, // opacity of the interior wash + fillFalloff: 1.1, // how fast the wash fades from the axis to the edge + opacity: 1.0, + + /* --- energy running up the shaft --- */ + stripes: 0.55, // chevrons per metre + stripeSharp: 0.62, // 0 = soft gradient, 1 = hard bars + stripeDepth: 0.55, // how much they modulate the fill + scrollSpeed: 2.4, // metres/second they travel toward the tip + pulse: 0.28, // brightness breathing + pulseSpeed: 2.2, + + /* --- frost break-up --- */ + noise: 0.45, // how much noise eats into the fill + noiseScale: 1.6, // features per metre + noiseSpeed: 0.35, + crystals: 0.55, // voronoi frost plates over the interior + crystalScale: 2.4, + + /* --- furniture --- */ + baseRing: 0.62, // radius of the ring at the caster's feet, metres + baseRingWidth: 0.06, + tipGlyph: 0.9, // strength of the crystal rosette at the impact point + tipGlyphSize: 1.15, // radius of that rosette, metres + tipSpin: 0.45, // revolutions/second + rangeArc: 0.55, // brightness of the max-range cap + reveal: 0.055, // seconds for the arrow to sweep out when armed + + /* --- colour --- */ + colorCore: '#ecfbff', + colorEdge: '#3fb4ff', + colorInvalid: '#ff6a5c', // shown when the target is inside `minRange` + + height: 0.035 // hover distance above the floor, metres + }, + + /* ------------------------------------------------------------------ */ + /* The far-cast indicator — the circle drawn at the target point */ + /* ------------------------------------------------------------------ */ + /** + * The other half of the targeting vocabulary. Where `aim` draws an arrow + * along a line, this draws the **footprint**: a disc dropped at the cursor + * with a deliberately thick boundary, because the one thing a ground-targeted + * AoE has to answer before you click is *how much space is this going to + * take*. The band is the answer, and the ability's own field is built to land + * exactly on it. + * + * Two meshes, both parametric: + * - the **footprint**, a quad whose fragment shader is a signed-distance + * ring evaluated in metres from the target; + * - the **reach ring**, a ribbon strip bent into a circle at the caster's + * feet at `range` — a far cast needs to show where its arm ends. + * + * Shared by every far cast, so a new one inherits the whole indicator and + * only brings its own `zoneRadius`. + */ + zone: { + /* --- the boundary (metres) --- */ + boundary: 0.34, // thickness of the band that *is* the footprint edge + // Held under 2: the band is already the widest mark on the circle, and + // pushing the gain past this clips it to flat white and throws away the + // hue that says which ability you are holding. + boundaryGlow: 1.8, // how hard it blooms + boundaryBias: 0.35, // <0.5 grows the band inward, >0.5 outward + liner: 0.05, // thin bright liner riding the inside of the band + softness: 0.05, // feather on both lips + + /* --- the interior --- */ + fill: 0.22, // opacity of the wash inside the circle + fillFalloff: 1.5, // >1 keeps the middle clear and crowds it to the rim + rings: 2.0, // concentric contour rings across the radius + ringWidth: 0.05, + ringSpeed: 0.35, // how fast they travel outward, radii/second + crawl: 0.75, // filaments crawling over the interior + crawlScale: 1.3, // filaments per metre + crawlSpeed: 0.45, + noise: 0.4, // break-up eating into the wash + noiseScale: 1.2, + + /* --- furniture --- */ + ticks: 24, // marks stepping around the boundary + tickLength: 0.42, // how far they reach in, metres + tickWidth: 0.2, // duty cycle, 0..1 + tickSpin: 0.06, // revolutions/second + sweep: 0.55, // radar sweep brightness + sweepSpeed: 0.4, // revolutions/second + core: 0.85, // the mark at the exact target point + coreSize: 0.4, // its radius, metres + crosshair: 0.5, // four arms pointing out of the core + crosshairLength: 1.1, + pulse: 0.22, // brightness breathing + pulseSpeed: 2.0, + + /* --- the reach ring at the caster --- */ + reach: 0.7, // brightness of the max-range circle, 0 hides it + reachWidth: 0.05, // its half-width, metres + reachDashes: 64, // dashes around it (0 = solid) + reachDashGap: 0.42, // fraction of each dash that is gap + reachSpin: 0.03, // revolutions/second the dashes creep + reachLead: 0.9, // how much brighter the arc nearest the cursor is + reachSegments: 192, // tessellation of that circle + + /* --- rendering --- */ + opacity: 1.0, + reveal: 0.07, // seconds the circle takes to snap out when armed + snap: 1.18, // how far past its radius it overshoots on the way out + height: 0.035, // hover distance above the floor, metres + + /* --- colour --- */ + colorCore: '#eaf7ff', + colorEdge: '#7c6bff', + colorInvalid: '#ff6a5c' // shown when the target is inside `minRange` + }, + + /* ------------------------------------------------------------------ */ + /* Character */ + /* ------------------------------------------------------------------ */ + character: { + /* --- blending the cast clip over the idle --- */ + // The idle loops forever; a cast clip is a one-shot laid over the top of it, + // so these are the two edges of that overlap. In fast, out soft: the throw + // has to land on the frame you clicked, the recovery does not. + castBlendIn: 0.12, // seconds to cross-fade from the idle into the cast + castBlendOut: 0.3, // seconds to fall back to the idle once it finishes + + /* --- how the body sells the cast --- */ + turnToAim: true, // face the arrow while aiming + turnRate: 0.0002, // fraction of the heading gap left after 1s (lower = snappier) + castLean: 0.34, // radians the torso pitches forward on release + castRecoil: 0.16, // metres the body is shoved back + castSettle: 2.6 // seconds⁻¹ the lunge decays at + }, + + /* ================================================================== */ + /* ICE — ability one */ + /* ================================================================== */ + /** + * A glacial eruption: a fracture front races out along the aimed line and a + * field of crystal spikes tears up out of the floor behind it, small and dense + * at the caster, tall and violent at the far end. + * + * Everything is generated — the crystals are procedural geometry + * (`assets/ProceduralGeometry.js`), their shading is a patched standard + * material (`materials/IceMaterial.js`), the frost is a shader on a quad and + * the mist, shards and glitter are GPU particles. There are no textures and no + * meshes on disk. + */ + ice: { + /* --- the cast itself --- */ + range: 15.0, // maximum cast distance, metres + minRange: 2.5, // closer than this and the cast is refused + speed: 26.0, // how fast the fracture front travels, metres/second + lifetime: 3.6, // seconds the field stands before it withdraws + cooldown: 0.4, // seconds before the ability can be armed again + castAnim: 'cast3', // which clip in `CAST_ANIMATIONS` the body throws + + /* --- the footprint the spikes fill --- */ + widthNear: 0.55, // half-width of the band at the caster, metres + width: 2.5, // half-width at the far end, metres + widthCurve: 0.75, // <1 flares early, >1 stays narrow then opens out + spikeCount: 190, // instances spent on one cast (capped at 288) + density: 1.0, // multiplier on that count + clumping: 1.35, // >1 pulls spikes toward the centre line + scatter: 0.55, // extra lateral jitter, fraction of the local half-width + frontBias: 0.85, // <1 crowds spikes toward the impact point + + /* --- silhouette of the field --- */ + heightNear: 0.5, // spike height at the caster, metres + height: 3.1, // spike height at the far end, metres + heightCurve: 1.7, // how late the ramp climbs + heightJitter: 0.55, + crown: 0.55, // how much shorter the flank blades are than the spine, 0..1 + peak: 1.45, // extra height multiplier at the impact point + peakWidth: 0.28, // how much of the line that swell covers, 0..1 + rubble: 0.42, // fraction of the spikes demoted to ankle-height shards + rubbleScale: 0.3, + + /* --- an individual crystal --- */ + radius: 0.41, // base radius, metres + radiusJitter: 0.93, + taper: 0.69, // tip radius as a fraction of the base + facets: 7, // sides of the prism (5–8 read best) + roughness: 0.09, // how far the facets are pushed off a clean prism + bend: 0.66, // sideways curve from base to tip + lean: 0.42, // radians the spikes lean away from the caster + leanJitter: 1.5, + twist: 1.0, // random yaw, 0..1 of a full turn + + /* --- the eruption --- */ + riseTime: 0.17, // seconds from buried to full height + riseOvershoot: 0.26, // how far past full height the punch carries + riseStagger: 0.09, // seconds of random delay between neighbours + settle: 0.55, // seconds the overshoot takes to damp out + shatterDelay: 0.6, // seconds after `lifetime` before they start to go + sinkTime: 1.0, // seconds to withdraw into the floor + + /* --- the ice material --- */ + colorDeep: '#3e737a', // the colour thick ice accumulates toward + colorIce: '#8adaff', // body + colorRim: '#f2feff', // fresnel edge + colorCore: '#638797', // the light trapped inside a fresh crystal + opacity: 0.92, + depthTint: 1.15, // how fast the deep tint builds with thickness + fresnel: 2.3, + fresnelPower: 2.4, + translucency: 1.5, // light bleeding through from behind + envIntensity: 0.9, // how much of the HDR probe the facets catch + facetSharp: 0.68, // crispness of the internal facet shading + fracture: 0.62, // internal crack planes + fractureScale: 6.5, // cracks per metre + veins: 0.45, // milky feather-frost inside the crystal + veinScale: 3.2, + // Named `glint*` rather than `sparkle*` on purpose: these are the pinpoint + // highlights on the crystal *surface*, and the `sparkle*` family further + // down drives the glitter *particles*. Two different effects. + glint: 1.1, + glintScale: 34.0, + glintSpeed: 0.7, + frostLine: 0.5, // rime banding climbing the crystal + glow: 0.85, // overall emissive gain + edgeGlow: 1.1, // brightness of the silhouette rim + birthGlow: 1.6, // extra glow on a crystal that has just erupted + birthFade: 0.45, // seconds that birth flash lasts + + /* --- what the ground does --- */ + frostSpread: 1.35, // frost patch radius, × the local half-width + frostRate: 3.6, // patches laid per metre of front travel + frostLife: 7.0, // seconds a patch lingers + frostIntensity: 0.85, + frostCrystals: 1.5, // grain of the packed snow + colorFrost: '#f0f9ff', // the lit face of the snow + colorFrostEdge: '#79b6dd', // what it goes in its own shadow + shockRadius: 5.5, // impact shockwave ring, metres + colorShockA: '#5fd0ff', // body of the shockwave ring + colorShockB: '#f2feff', // its crest + + /* --- mist, shards and glitter --- */ + /** + * Every particle system is coloured by a four-stop gradient sampled over the + * particle's own lifetime: `A` the instant it is born, `D` as it dies. They + * are spelled out rather than derived from the crystal palette so the fog can + * be warmed, or the glitter recoloured, without touching the ice itself. + */ + mistRate: 260, // rolling ground fog, particles/second + mistSize: 1.15, + mistSpeed: 1.3, + mistLifetime: 2.8, + mistOpacity: 0.05, + mistRise: 0.35, // how fast the fog lifts, metres/second + colorMistA: '#f2feff', + colorMistB: '#cdefff', + colorMistC: '#a9e4ff', + colorMistD: '#09304c', + shardRate: 150, // ice chips thrown off the eruption + shardSize: 0.075, + shardSpeed: 7.0, + shardLifetime: 1.7, + shardGravity: -14.0, + colorShardA: '#f2feff', + colorShardB: '#a9e4ff', + colorShardC: '#a9e4ff', + colorShardD: '#12496f', + sparkleRate: 130, // the rising glitter plume + sparkleSize: 0.055, + sparkleSpeed: 3.4, + sparkleLifetime: 2.6, + sparkleRise: 1.6, // upward drift, metres/second + sparkleTurbulence: 0.55, + colorSparkleA: '#f2feff', + colorSparkleB: '#57c9ff', + colorSparkleC: '#a9e4ff', + colorSparkleD: '#041e32', + + /* --- dynamic light --- */ + lightIntensity: 9, + lightRadius: 13, + lightColor: '#7fd4ff', + + /* --- the impact at the far end --- */ + burstSize: 3.6, + burstIntensity: 0.75, + burstShards: 90, // extra chips thrown at the impact + impactShake: 0.7, + impactFlash: 0.12, + shakeDuration: 0.9, + rumble: 0.06, // continuous shake while the front travels + // The frost shell mixes A→B across its billowing noise and lays C over the + // crystallised plates and the fresnel rim, so C is the one that reads hot. + colorBurstA: '#a9e4ff', + colorBurstB: '#cdefff', + colorBurstC: '#f2feff', + colorFlash: '#f2feff' // the full-screen flash on impact + }, + + /* ================================================================== */ + /* THUNDER — ability two */ + /* ================================================================== */ + /** + * A bolt thrown from the caster's hand along the aimed line: a bundle of + * lightning filaments that snap into existence, hold while they gutter, and + * blow out. Reference for the look: `thundercast.jpg`. + * + * The bolt is **one mesh**. Every filament is an instance of the same ribbon + * strip, and its entire shape — the sag of the axis, the fan of the bundle, + * the kinks in an individual strand, the camera-facing width — is evaluated in + * the vertex shader from the numbers below. Nothing about the path exists on + * the CPU, which is why `strands`, `jitter` and `spread` reshape a bolt that + * is already in the air, and do it with the clock paused. + * + * The one thing a cast *does* capture is `uSeed`, a single random number + * rolled at spawn so two casts do not draw the identical bolt. That is an + * event, not a dimension — the same rule `IceAbility` follows. + */ + thunder: { + /* --- the cast — electric mastery look (hot core + branch alt texture) --- */ + range: 26.0, // maximum cast distance, metres + minRange: 1.5, // closer than this and the cast is refused + speed: 118.0, // how fast the strike front travels, metres/second + lifetime: 0.52, // seconds the bolt holds after it lands + fadeTime: 0.42, // seconds it takes to blow out + cooldown: 0.45, + castAnim: 'cast2', // which clip in `CAST_ANIMATIONS` the body throws + /** Effect variant hooks (effectVariants.js) — size/speed/angle without new meshes */ + variantId: 'arc_bolt', + textureKey: 'electric_core', + textureAlt: 'electric_branch', + + /* --- where the bolt leaves the caster --- */ + // The beam starts at the hand, not at the feet, so these are measured from + // the caster's origin in the cast's own frame. + handHeight: 1.32, // metres above the floor + handForward: 0.58, // metres in front of the caster + handSide: 0.18, // metres to the side (+ follows `Ability#side`) + endHeight: 0.42, // height of the bolt where it lands, metres + sag: 0.18, // metres the mid-span bows upward (negative droops) + + /* --- the bundle of filaments — denser electric look --- */ + strands: 12, // separate filaments (capped at 24) + spread: 0.92, // metres the bundle fans out at the far end + spreadNear: 0.04, // ... and at the hand + spreadCurve: 1.45, // >1 keeps the bundle tight then opens it late + twist: 0.55, // turns the bundle makes around the axis over its length + twistSpeed: 1.15, // turns/second it rolls on top of that + branchDim: 0.68, // how much dimmer an outer filament is than the spine + + /* --- the shape of one filament --- */ + jitter: 0.42, // metres of kink at the coarsest octave + jitterScale: 1.05, // kinks per metre + octaves: 5, // 1–5; each one halves the amplitude and doubles the rate + jitterFalloff: 0.58, // amplitude kept per octave + crawl: 4.0, // how fast the kinks slide along the bolt + pinch: 0.12, // fraction of the span the ends are pulled straight over + converge: 0.88, // how hard the far end is pulled onto the target, 0..1 + + /* --- the ribbon — brighter core + wider halo for "electric" read --- */ + width: 0.032, // half-width of a filament at the hand, metres + widthTip: 0.5, // that width at the impact point, as a fraction + widthCurve: 1.05, // how early the taper happens + coreWidth: 1.45, // multiplier on the central spine + coreSharp: 5.2, // how hard the hot core falls off across the ribbon + glowWidth: 6.8, // the halo, × the core width + glowFalloff: 2.15, // how fast the halo fades across its ribbon + glowOpacity: 0.62, + softFade: 0.85, // metres of soft fade where the bolt meets geometry + + /* --- flicker & restrike --- */ + restrike: 28, // times/second the filaments re-roll their shape + flicker: 0.38, // depth of the whole-bolt brightness stutter + flickerSpeed: 34, // stutters/second + strandFlash: 0.5, // how much individual filaments blink out + tipGlow: 2.0, // extra heat on the leading edge while it travels + tipLength: 0.08, // length of that leading edge, fraction of the span + + /* --- colour — hot white core, cyan branch, deep electric halo --- */ + colorCore: '#f4fcff', // the centre of a filament + colorInner: '#b8ecff', + colorOuter: '#4eb8ff', // the outside of a filament + colorHalo: '#1a4cff', // the wide glow around the bundle + glow: 2.85, // overall emissive gain + opacity: 1.0, + + /* --- what the ground does — AOE electric footprint --- */ + arcRate: 1.15, // electric burns laid per metre of front travel + arcRadius: 1.85, // radius of one burn, metres + arcLife: 0.75, // seconds a burn lingers + arcIntensity: 1.2, + arcBranches: 0.78, // how finely the burn splits into filaments + scorchRadius: 0.65, // dark burn mark under the bolt, metres + scorchLife: 7.0, + scorchIntensity: 0.5, + colorArc: '#a8ecff', + colorScorch: '#080b11', + colorEmber: '#6ec8ff', + shockRadius: 7.5, // impact shockwave ring, metres + colorShockA: '#d0f4ff', // body of the shockwave ring + colorShockB: '#ffffff', // its crest + + /* --- sparks, motes, smoke and debris --- */ + /** + * As in `ice`: each system is coloured by a four-stop gradient sampled over + * the particle's own lifetime, `A` at birth through `D` as it dies. Spelled + * out rather than derived from the bolt palette, so the sparks can be made + * to cool to orange while the filaments stay blue. + */ + sparkRate: 240, // sparks thrown off the bolt, particles/second + sparkSize: 0.16, + sparkSpeed: 9.0, + sparkLifetime: 0.5, + sparkGravity: -12.0, + sparkStretch: 0.18, // how far a spark smears along its velocity + colorSparkA: '#ffffff', + colorSparkB: '#ffffff', + colorSparkC: '#c9ecff', + colorSparkD: '#1e5b95', + moteRate: 90, // the slow ionised motes drifting off the bolt + moteSize: 0.05, + moteSpeed: 1.5, + moteLifetime: 1.6, + moteRise: 1.0, // upward drift, metres/second + moteTurbulence: 0.7, + colorMoteA: '#ffffff', + colorMoteB: '#c9ecff', + colorMoteC: '#3aa0ff', + colorMoteD: '#02195f', + smokeRate: 50, // thin haze off the scorched floor + smokeSize: 1.0, + smokeSpeed: 1.1, + smokeLifetime: 2.2, + smokeOpacity: 0.06, + smokeRise: 0.55, + colorSmokeA: '#3d546e', + colorSmokeB: '#33475e', + colorSmokeC: '#33475e', + colorSmokeD: '#1c2938', + debrisRate: 24, // chips kicked off the floor under the bolt + debrisSize: 0.055, + debrisSpeed: 5.0, + debrisLifetime: 1.3, + debrisGravity: -17.0, + colorDebrisA: '#252c36', + colorDebrisB: '#1c222a', + colorDebrisC: '#1c222a', + colorDebrisD: '#1c222a', + + /* --- dynamic light --- */ + lightIntensity: 26, + lightRadius: 17, + lightColor: '#63b8ff', + lightFlicker: 0.4, // depth of the light's gutter, 0 = steady + lightFlickerSpeed: 26, + + /* --- the muzzle and the impact --- */ + // Both shells are the same shader: A→B is mixed across the billowing noise + // and stays nearly empty, and C is what the racing filaments and the fresnel + // rim are drawn in — so C is the one carrying the read. + muzzleSize: 0.55, // the flash at the hand, metres + muzzleIntensity: 1.9, + castFlash: 0.1, // screen flash on release + colorMuzzleA: '#3aa0ff', + colorMuzzleB: '#c9ecff', + colorMuzzleC: '#ffffff', + colorCastFlash: '#c9ecff', + burstSize: 3.0, // the shell at the impact point, metres + burstIntensity: 1.4, + burstSparks: 170, // extra sparks thrown at the impact + burstDebris: 45, + impactShake: 0.8, + shakeDuration: 0.55, + impactFlash: 0.28, + rumble: 0.03, // continuous shake while the front travels + colorBurstA: '#3aa0ff', + colorBurstB: '#c9ecff', + colorBurstC: '#ffffff', + colorFlash: '#c9ecff' // the full-screen flash on impact + }, + + /* ================================================================== */ + /* METEOR — ability three */ + /* ================================================================== */ + /** + * A burning rock lobbed along the aimed line, which detonates on arrival. + * + * The rock is real geometry — a cratered, faceted asteroid generated by + * `assets/ProceduralGeometry.js` — shaded by a patched standard material so it + * casts and receives the stage's shadows. Its signature is the **lava seams**: + * the zero crossing of an fbm field sampled in the rock's own local space, so + * the cracks are welded to it and tumble with it. `chargeCurve` decides how + * fast they prise open on the way in. + * + * Behind it hangs the **fire trail**: a black-body volume raymarched inside a + * camera-facing proxy hull laid along the arc. See the `trail*` block. + * + * As in `ice` and `thunder`, a cast captures nothing but dice and timestamps: + * one seed, one tumble axis and a few unitless rolls per debris chunk. The + * trajectory, the size of the rock, the width of its seams and the whole + * ballistic flight of every chunk are resolved against this block each frame — + * which is why dragging `arc` re-lofts a meteor already in the air, and + * dragging `chunkSpeed` re-throws debris that has already landed. + */ + meteor: { + /* --- the cast — AOE impact showcase --- */ + range: 22.0, // maximum cast distance, metres + minRange: 3.0, // closer than this and the cast is refused + speed: 23.0, // how fast the rock travels downrange, metres/second + lifetime: 2.4, // seconds the crater burns after the impact + fadeTime: 1.5, // seconds everything takes to clear + cooldown: 0.85, + castAnim: 'cast1', // which clip in `CAST_ANIMATIONS` the body throws + textureKey: 'ember_core', + textureAlt: 'scorch_rim', + variantId: 'aoe_meteor', + + /* --- the flight path --- */ + // The rock is thrown from a hand, so these are measured from the caster's + // origin in the cast's own frame. + handHeight: 1.35, // metres above the floor + handForward: 0.6, // metres in front of the caster + handSide: 0.2, // metres to the side (+ follows `Ability#side`) + endHeight: 0.75, // height of the rock where it lands, metres + arc: 2.8, // metres the mid-span lobs upward + arcCurve: 0.85, // <1 flattens the top of the arc, >1 peaks it + + /* --- the rock --- */ + radius: 0.95, // metres — readable AOE body + facets: 3, // icosphere subdivisions, 0–3 (3 = 1280 triangles) + lumpiness: 0.26, // low-frequency deformation, × the radius + lumpScale: 1.5, // lumps per unit radius + surfaceRoughness: 0.16, // high-frequency chipping + cuts: 9, // planar fracture faces sliced off it + cutDepth: 0.28, // how far in those planes bite, × the radius + craters: 5, // impact bowls punched into it + craterDepth: 0.18, // how deep those bowls go, × the radius + craterSize: 0.5, // their angular radius, radians + spin: 3.4, // tumble rate, radians/second + + /* --- the lava seams --- */ + chargeCurve: 1.6, // how late the rock heats up on its way in + crackScale: 0.95, // seams per unit radius + crackWidth: 0.045, // how wide a seam opens (doubled at full charge) + crackBranches: 0.5, // strength of the finer seams splitting off + crackGlow: 2.2, + crackFlow: 0.7, // how much the magma brightness crawls + crackFlowSpeed: 0.9, + rockScale: 3.4, // mottling of the rock between the seams + facetTint: 0.5, // per-facet value break-up — what makes it read as stone + cavity: 0.25, // darkening down in the craters and the cut faces + soot: 0.6, // charring either side of a seam + rimHeat: 0.7, // heat sheath around the silhouette + leadGlow: 0.9, // compression heat on the leading facets + leadSharp: 2.6, // how tightly that hugs the nose + glow: 0.75, // overall emissive gain + envIntensity: 1.25, // how much of the HDR probe the rock catches + colorRock: '#6e675f', + colorChar: '#17130f', + colorCrack: '#ff6a12', + colorHot: '#fff3d0', + + /* --- the fire trail --- */ + /** + * The burning wake, **raymarched as a black-body volume** — the firebending + * stream from the freehand sandbox, re-aimed at the meteor's arc. The mesh + * drawn is only a camera-facing proxy hull; the flame itself is integrated + * inside it by `materials/VolumetricFireMaterial.js`, which is where the four + * layers these controls drive (silhouette → vortex roll-up → turbulence → + * shred) are explained. + * + * As with everything else here it is not a recorded history: the hull's + * centre line is sampled straight off the trajectory, so these reshape fire + * that is already in the air. + * + * The volume borrows the rock's palette — `colorHot`, `colorFlameMid`, + * `colorFlameEdge`, `colorFlameSmoke` — but only reaches for it in + * proportion to `trailPalette`; at 0 it is a pure Planckian radiator and the + * colour comes out of `trailTempCore` / `trailTempEdge` instead. + */ + trailSpan: 7.0, // metres of arc the fire covers behind the rock + trailWidth: 0.66, // tube radius, metres + trailHeadSize: 1.8, // fireball radius at the rock, × trailWidth + trailPlume: 1.1, // upward stretch of the volume (buoyant elongation) + trailWakeSpread: 0.22, // how far the spent gas behind the head has ballooned + trailRise: 0.35, // how far the far end of the wake has floated upward, metres + // Metre-scale lobes in the silhouette. Without these the outline stays a + // capsule no matter how much fine turbulence is piled on top of it, and the + // trail reads as a shaded tube. + trailBulge: 0.18, // how far those lobes swell and pinch the local radius + trailBulgeScale: 0.34, // lobes per metre — lower = bigger, slower shapes + // Ring vortices shed off the head and travelling back down the wake. This is + // what folds the field into curling, mushrooming billows; fbm alone can only + // make clouds. + trailVortex: 0.0, // roll-up strength + trailRingFrequency: 0.0, // vortices per metre of stream + trailRingSpeed: 4.0, // how fast they travel backwards + // Kept low on purpose: rolling the noise frame hard around the axis wraps + // the filaments circumferentially and the flame reads as concentric contour + // lines rather than as tongues running along the flow. + trailCurl: 0.0, // swirl of the density field around the axis + trailTurbulence: 2.94, // noise amplitude eating into the volume + trailWarp: 0.45, // domain warp — folds the noise into curling sheets + trailTongue: 0.94, // < 1 stretches structures upward into licking tongues + trailStreamStretch: 1.13, // < 1 draws them out along the flow + // Radial shear: how far the fringe is dragged up and back relative to the + // axis. This is what makes the edge structures read as licking tongues + // rather than as blobs of the same shape at every radius. + trailLick: 3.1, + trailWisps: 0.81, // ridged filaments shredding the fringe into strands + trailShred: 1.57, // how violently the fringe tears compared to the core + trailOctaves: 5, // turbulence octaves (quality ↔ cost) + trailSpeed: 4.62, // how fast the field streams backwards along the path + trailBuoyancy: 3.5, // how fast it climbs inside the volume + trailDetachment: 0.9, // how hard the tail tears into separate puffs + trailNoiseStrength: 0.78, + trailNoiseFrequency: 3.23, + trailSoftness: 0.42, // 0 = hard tongues, 1 = a soft glow + trailFlicker: 0.74, + trailDensity: 2.09, + trailSoot: 1.42, // absorption — how much the cool gas occludes + trailCoreClarity: 0.54, // extinction left in the hottest gas (low = white blob) + trailSteps: 35, // raymarch samples per pixel (quality ↔ cost) + trailGlow: 3.06, + trailOpacity: 0.96, + trailTailFade: 0.71, // fraction of the trail that has already burnt out + trailBurnout: 1.2, // seconds the trail takes to die after the impact + // Temperature & radiance. The flame is shaded as a Planckian radiator: these + // are the two ends of its temperature range in kelvin, and the exponent the + // emitted power follows. 4 would be Stefan-Boltzmann; a little gentler keeps + // the mid-tones off the floor at this exposure. + trailTempCore: 1920, + trailTempEdge: 1590, + trailEmissionCurve: 4.79, + trailHeatFocus: 1.54, // how fast the gas reaches full heat inside the surface + trailHeatFalloff: 2.46, // how sharply it cools toward that surface + // How far the turbulence is allowed to drag the temperature profile around. + // Radiated power goes as a high power of T, so this number is amplified + // several-fold on screen — past ~0.5 the noise's own contour lines start + // showing through as agate banding. + trailHeatFollow: 0.26, + trailTailHeat: 0.36, // temperature of the spent gas at the far end of the wake + trailPalette: 0.62, // 0 = pure black-body physics, 1 = the colour stops below + trailScatter: 1.99, // firelight bouncing inside the sooty fringe + trailScatterFalloff: 4.4, // how fast that bath dies away from the core + colorFlameMid: '#ffb02e', + colorFlameEdge: '#ff3d10', + colorFlameSmoke: '#181616', + + /* --- the debris the rock breaks into --- */ + chunkCount: 18, // chunks thrown at the impact (capped at 28) + chunkScale: 0.28, // their radius, × the meteor's + chunkSpeed: 7.5, // metres/second they leave the crater at + chunkForward: 0.55, // how far the spray is biased downrange + chunkLoft: 1.0, // how steeply they are thrown + chunkGravity: -17.0, + chunkSpin: 6.0, // tumble rate, radians/second + chunkCool: 2.6, // seconds a chunk's seams take to go out + chunkLinger: 0.5, // seconds they lie there before sinking + chunkSink: 1.0, // seconds to withdraw into the floor + + /* --- embers, sparks, smoke and grit --- */ + /** + * As in `ice` and `thunder`: each system is coloured by a four-stop gradient + * sampled over the particle's own lifetime, `A` at birth through `D` as it + * dies. Spelled out rather than derived from the flame palette, so the + * trail can be cooled to red while the rock itself stays white-hot. + */ + emberRate: 180, // embers streaming off the rock, particles/second + emberSize: 0.1, + emberSpeed: 2.4, + emberLifetime: 1.5, + emberRise: 1.5, // buoyancy, metres/second + emberGlow: 1.2, + emberTurbulence: 0.5, + colorEmberA: '#fff3d0', + colorEmberB: '#ff9a2e', + colorEmberC: '#ff3b0d', + colorEmberD: '#2b0d05', + sparkRate: 110, // sparks flung off it + sparkSize: 0.14, + sparkSpeed: 6.5, + sparkLifetime: 0.8, + sparkGravity: -11.0, + sparkStretch: 0.16, // how far a spark smears along its velocity + colorSparkA: '#fffdf2', + colorSparkB: '#ffd27a', + colorSparkC: '#ff6a12', + colorSparkD: '#3d1103', + smokeRate: 70, // the trail and the column off the crater + smokeSize: 1.1, + smokeSpeed: 1.2, + smokeLifetime: 3.0, + smokeOpacity: 0.12, + smokeRise: 0.9, + colorSmokeA: '#6b503f', + colorSmokeB: '#3b2c25', + colorSmokeC: '#241b17', + colorSmokeD: '#141010', + debrisSize: 0.06, // grit kicked off the floor + debrisSpeed: 6.0, + debrisLifetime: 1.5, + debrisGravity: -18.0, + colorDebrisA: '#3a322c', + colorDebrisB: '#2a231e', + colorDebrisC: '#1c1714', + colorDebrisD: '#151110', + + /* --- the molten cracks torn through the floor --- */ + /** + * Real geometry, not a decal: arms of crack that meander outward from the + * impact, shed branches, glow from a white-hot core through a wide orange + * underglow, and heave basalt up along their lips. See + * `effects/GroundFissures.js` — the network is baked in a unit disc, so + * `fissureRadius` re-scales cracks that are already on the ground. + */ + fissureRadius: 5.2, // how far the cracks reach, metres + fissureLife: 6.5, // seconds before they close up + fissureArms: 6, // main cracks radiating from the impact + fissureWander: 1.6, // how hard an arm veers, radians per unit walked + fissureBranches: 0.75, // fraction of the generated branches kept, 0..1 + fissureBranchLength: 0.85, // how far along a branch runs before its point, 0..1 + fissureWidth: 0.14, // width of the open seam, metres + fissureHeat: 1.5, // core temperature + fissurePulse: 1.0, // speed of the heat waves travelling along them + fissureGrowth: 9.0, // how fast the cracks race outward, metres/second + fissureRockSize: 0.3, // basalt heaved up along the lips, metres + + /* --- what else the ground does --- */ + scorchRadius: 2.8, // burnt patch under it, metres + scorchLife: 8.0, + scorchIntensity: 0.95, + shockRadius: 6.0, // impact shockwave ring, metres + colorScorch: '#0d0907', + colorShockA: '#ff9a2e', // body of the shockwave ring + colorShockB: '#fff3d0', // its crest + + /* --- dynamic light --- */ + lightIntensity: 16, + lightRadius: 14, + lightColor: '#ff8a3c', + lightFlicker: 0.25, // depth of the light's gutter, 0 = steady + lightFlickerSpeed: 13, + + /* --- the launch and the detonation --- */ + muzzleSize: 0.0, // the flare at the hand as the rock leaves it — 0 = none + muzzleIntensity: 1.6, + castFlash: 0.08, // screen flash on release + colorCastFlash: '#ff9a2e', + burstSize: 3.6, // the fireball at the impact point, metres + burstIntensity: 1.0, + burstTurbulence: 2.0, // how hard the noise eats into the fireball's shell + burstEmbers: 260, // extra embers thrown at the impact + burstSparks: 180, + burstDebris: 90, + burstSmoke: 70, + impactShake: 1.0, + shakeDuration: 1.1, + impactFlash: 0.3, + rumble: 0.04, // continuous shake while the rock is in the air + colorFlash: '#ff9a2e' // the full-screen flash on impact + }, + + /* ================================================================== */ + /* BEAM — ability four */ + /* ================================================================== */ + /** + * A sustained super beam: the caster winds up a ball of light in both hands, + * then lets a column of it out along the aimed line, where it *stays* — + * burning into the floor for `lifetime` before it collapses back to a thread + * and blinks out. Reference for the look: `superbeam.jpg`. + * + * This is the ability with a **fourth beat**. Ice, thunder and meteor all run + * travel → impact → fade; the beam puts a `charge` in front of that, so the + * shot is something you watch arrive *and* something that lands and holds. + * Nothing in the base class needed changing for it — `BeamAbility` simply + * refuses to let the front leave the hand until the orb is up to power. + * + * The column is **one tube** — see `assets/ProceduralGeometry.js` — drawn + * three times at three radii by `materials/BeamMaterial.js`: a wide halo, a + * hollow rim-weighted sheath and, inside it, a core weighted the *opposite* + * way, brightest where the view ray runs down the barrel. That inversion is + * what makes the middle read as a solid rod of light instead of as a lit + * pipe. The coils spiralling around it and the shock discs racing down it are + * two more instanced passes placed against the same radius profile, so all + * five stay welded together when the shape is dragged. + * + * Deliberately *not* electric: no kinks anywhere. The bolt's noise is + * piecewise-linear so it keeps its corners; every noise term here is smooth + * and stretched hard along the flow, because a beam that kinks is a bolt. + * + * As in every other block, a cast captures nothing but one seed and a few + * timestamps. The barrel, the flare, the coil pitch and the disc train are all + * resolved against these numbers each frame — which is why dragging `radius` + * re-bores a beam that is already burning, with the clock stopped. + */ + beam: { + /* --- the cast --- */ + range: 26.0, // maximum cast distance, metres + minRange: 3.0, // closer than this and the cast is refused + charge: 0.42, // seconds the orb winds up before the beam is let out + speed: 150.0, // how fast the leading edge races downrange, metres/second + lifetime: 1.15, // seconds it burns once it lands + fadeTime: 0.4, // seconds it takes to collapse + cooldown: 1.6, + castAnim: 'cast1', // which clip in `CAST_ANIMATIONS` the body throws + + /* --- where it leaves the caster --- */ + // Both hands, so this one sits on the centre line rather than off a + // shoulder like the bolt and the rock. + handHeight: 1.3, // metres above the floor + handForward: 0.72, // metres in front of the caster + handSide: 0.0, // metres to the side (+ follows `Ability#side`) + endHeight: 1.0, // height of the beam where it lands, metres + + /* --- the column --- */ + // A narrow throat that stays tight (`radiusCurve` above 1) and then opens + // hard over the last tenth of the span: the beam reads as a jet with a bell + // on the end rather than as a cone, which is what puts the weight at the + // impact instead of spreading it down the whole line. + radiusNear: 0.16, // half-width at the muzzle, metres + radius: 0.77, // half-width at the target + radiusCurve: 1.27, // <1 opens out early, >1 stays tight then flares late + flare: 1.74, // extra swell where it lands + flareWidth: 0.09, // how much of the span that swell covers, 0..1 + // Both wobbles ship at zero. The column reads cleaner with a hard, still + // silhouette — the coils already give the eye something moving to follow — + // but the rates below are tuned, so raising either one is a single drag. + throb: 0.0, // pressure waves travelling out along it + throbScale: 4.8, // waves over the length + throbSpeed: 2.6, // waves/second + wander: 0.0, // metres the axis drifts, pinned at both ends + wanderScale: 0.9, // drift features per unit length + wanderSpeed: 0.7, + + /* --- the three tube passes --- */ + // The core is deliberately narrow and not fully opaque. Widen it or push + // `coreFill` up and the three layers stack into one white rod: the cyan + // sheath and the gold coils are only readable because the middle leaves + // them room. + coreWidth: 0.2, // the hot rod, × the column radius + coreSharp: 1.55, // how tightly the core hugs the axis + coreFill: 0.6, // how solid it reads + shellWidth: 1.0, // the sheath + shellRim: 1.15, // brightness of its silhouette edges + shellFill: 0.18, // how much body it has between them + shellOpacity: 0.95, + // Wide and faint: the halo is atmosphere, not a second beam. Pushing its + // opacity up fogs the sheath's silhouette edges, which are the read. + haloWidth: 2.75, // the outer bloom + haloRim: 4.3, // how tightly that hugs the silhouette + haloOpacity: 0.14, + edgePower: 2.2, // rim exponent shared by the sheath + + /* --- the surface --- */ + ripple: 0.2, // how far the noise pushes the barrel off round + rippleBands: 2.2, // ripple features around the barrel + rippleScale: 4.25, // ... and along it + rippleSpeed: 2.0, // how fast they crawl downrange + streak: 1.1, // filaments streaming along the flow + streakSharp: 0.45, // 0 = a wash, 1 = hard threads + streakScale: 4.2, // threads per unit length + streakBands: 1.8, // ... and around the barrel + // Kept low: the threads carry heat into the *sheath*, and pushing this up + // whitens it out until the beam is one colour from axis to rim. + streakGlow: 0.55, // how hot a thread burns in the sheath + flowSpeed: 7.0, // how fast the whole field streams downrange + mouthGlow: 1.6, // heat where the column leaves the orb + mouthLength: 0.1, // how far that reaches, fraction of the span + // Kept below the muzzle's: the flare and the impact shell already carry the + // far end, and stacking a hot cap on top of them blows it out to a disc. + tipGlow: 0.6, // heat on the leading edge / the burning end + tipLength: 0.09, // length of that edge, fraction of the span + softFade: 0.62, // metres of soft fade where it meets geometry + + /* --- colour --- */ + colorCore: '#ffffff', // the axis + colorInner: '#d3f4ff', + colorOuter: '#3ec6ff', // the outside of the sheath + colorHalo: '#0d3ce0', // the wide bloom around it + // The column is deliberately held *back*. Three additive tube passes at full + // strength clip to white and the beam becomes a flat plank; dropping the + // gain and the opacity keeps it glassy and hands the read to the coils. + glow: 0.74, // overall emissive gain + opacity: 0.29, + + /* --- the coils --- */ + /** + * Ribbons spiralling around the column, on the same strip the bolt is drawn + * on. Warm on purpose: the reference frames a white-hot beam with gold + * coils, and the colour split is what stops them dissolving into the sheath. + */ + coils: 4, // ribbons (capped at 8) + coilTurns: 1.45, // turns each one makes over the length + // Negative, so the ribbons roll *against* the direction the charge pulse + // runs. The two motions reading differently is what keeps a held beam from + // looking like a single rotating screw. + coilSpeed: -0.69, // turns/second they roll on top of that + coilRadius: 1.88, // how far out they ride, × the column radius + coilFlare: 0.57, // extra opening at the far end + coilWidth: 0.1, // half-width at the muzzle, metres + coilWidthTip: 1.9, // that width at the target, as a multiple + coilSharp: 2.2, // how hard the ribbon falls off across its width + coilPulse: 0.65, // depth of the charge running along it + coilPulseFreq: 3.0, // pulses over the length + coilPulseSpeed: 1.6, // pulses/second + // Driven hard on purpose. With the column dialled back above, the ribbons + // are what the eye actually follows down the beam. + coilGlow: 8.0, + coilOpacity: 2.0, + colorCoil: '#ffdc8c', + colorCoilEdge: '#ff6a12', + + /* --- the shock discs --- */ + rings: 10, // discs in flight (capped at 12) + ringSpeed: 1.31, // trips down the beam per second + // Both lips well clear of the sheath, and close together: the discs read as + // thin hoops orbiting the column rather than as plates growing out of it. + ringInner: 2.42, // inner lip, × the local column radius + ringOuter: 2.73, // outer lip + ringSwell: 0.55, // how much they open out as they travel + ringFade: 0.18, // how much is left of one by the time it lands + ringSharp: 1.6, // how thin the band reads + ringGlow: 2.4, + ringOpacity: 0.7, + colorRing: '#9ceeff', + + /* --- the charge orb --- */ + orbSize: 0.39, // radius once it is up to power, metres + orbThrob: 0.11, // how hard it pulses + orbThrobSpeed: 6.9, + orbTurbulence: 0.24, // how far the noise eats into its surface + orbScale: 2.2, // features over the surface + orbFlow: 0.9, // how fast they crawl + orbBands: 5.0, // filament frequency + orbRim: 1.8, // rim exponent + orbGlow: 2.8, + orbOpacity: 1.0, + + /* --- what the ground does --- */ + scorchRate: 1.1, // burns laid per metre of front travel + scorchRadius: 0.7, // radius of one, metres + scorchLife: 7.0, // seconds it lingers + scorchIntensity: 0.55, + colorScorch: '#0a0d14', + colorEmber: '#4ad6ff', + dustRate: 7.0, // dust rings thrown off the burning end, per second + dustRadius: 2.4, // radius of one, metres + dustLife: 0.9, + colorDustA: '#3d5c74', + colorDustB: '#9ceeff', + shockRate: 3.5, // pressure rings snapped across the floor, per second + shockRadius: 7.0, // radius of the one at the impact, metres + colorShockA: '#3ec6ff', // body of the shockwave ring + colorShockB: '#ffffff', // its crest + + /* --- sparks, motes, smoke and debris --- */ + /** + * As in `ice`, `thunder` and `meteor`: each system is coloured by a four-stop + * gradient sampled over the particle's own lifetime, `A` at birth through + * `D` as it dies. The motes do double duty — they are the intake spiralling + * *into* the orb while it charges, and the drift shed off the column once it + * is firing. + */ + sparkRate: 300, // sparks shed off the column, particles/second + sparkSize: 0.15, + sparkSpeed: 8.0, + sparkLifetime: 0.55, + sparkGravity: -9.0, + sparkStretch: 0.22, // how far a spark smears along its velocity + sparkForward: 0.9, // how hard the spray is dragged downrange + colorSparkA: '#ffffff', + colorSparkB: '#d3f4ff', + colorSparkC: '#3ec6ff', + colorSparkD: '#0b2f7a', + moteRate: 120, // the drift hanging around the column + moteSize: 0.06, + moteSpeed: 1.6, + moteLifetime: 1.5, + moteRise: 0.9, // upward drift, metres/second + moteTurbulence: 0.8, + colorMoteA: '#ffffff', + colorMoteB: '#9ceeff', + colorMoteC: '#3ec6ff', + colorMoteD: '#06205e', + intakeRate: 260, // motes pulled into the orb while it charges + intakeRadius: 2.6, // how far out they are drawn from, metres + intakeSpeed: 7.5, // how fast they fall in + smokeRate: 90, // steam scoured off the floor under the beam + smokeSize: 1.1, + smokeSpeed: 1.4, + smokeLifetime: 2.4, + smokeOpacity: 0.07, + smokeRise: 0.7, + colorSmokeA: '#41566d', + colorSmokeB: '#35485e', + colorSmokeC: '#2a3949', + colorSmokeD: '#1a2430', + debrisRate: 34, // chips torn off the floor along the burn line + debrisSize: 0.06, + debrisSpeed: 6.0, + debrisLifetime: 1.4, + debrisGravity: -18.0, + colorDebrisA: '#2b323c', + colorDebrisB: '#1f252d', + colorDebrisC: '#1a1f26', + colorDebrisD: '#1a1f26', + + /* --- dynamic light --- */ + // Two lights: one rides the beam, one sits in the caster's hands so the + // charge actually lights the body that is holding it. + lightIntensity: 30, + lightRadius: 20, + lightColor: '#7fdcff', + lightPulse: 0.18, // depth of the hum, 0 = steady + lightPulseSpeed: 5.0, // pulses/second + muzzleLightIntensity: 16, + muzzleLightRadius: 9, + + /* --- the wind-up, the release and the burn --- */ + chargeShake: 0.045, // rumble while the orb spools up + castFlash: 0.22, // screen flash as it is released + muzzleSize: 1.1, // the pressure shell thrown off the hands, metres + muzzleIntensity: 2.0, + colorCastFlash: '#d3f4ff', + burstSize: 4.2, // the shell at the impact point, metres + burstIntensity: 1.6, + burstSparks: 220, // extra sparks thrown when it lands + burstDebris: 70, + pulseRate: 2.6, // pressure shells off the burning end, per second + pulseSize: 2.2, // radius of one, metres + pulseIntensity: 1.1, + splashRate: 260, // sparks kicked back up the beam while it burns + impactShake: 0.9, + shakeDuration: 0.7, + burnShake: 0.09, // continuous rumble while the beam is standing + impactFlash: 0.3, + rumble: 0.05, // rumble while the leading edge travels + colorBurstA: '#3ec6ff', + colorBurstB: '#d3f4ff', + colorBurstC: '#ffffff', + colorFlash: '#d3f4ff' // the full-screen flash on impact + }, + + /* ================================================================== */ + /* SNARE — ability five, and the first **far cast** */ + /* ================================================================== */ + /** + * A trap planted at a point rather than a shot fired along a line: the caster + * whips a leash of current out across the floor, and where it lands the ring + * snaps open — a column of lightning tears up out of the middle, tendrils + * crawl outward to the boundary and arcs run around the rim, all of it + * holding, re-striking and dragging the air upward for `lifetime` before it + * collapses. Reference for the look: `electricalboost.jpg`. + * + * This is the block that defines what a far cast *is* in this project. The + * targeting is a circle (see the `zone` block) and `zoneRadius` is the promise + * that circle makes: the boundary the indicator draws is the boundary the + * field burns, the tendrils reach and the rim arcs run along, so dragging that + * one number re-scales the indicator and a snare that is already standing + * together. + * + * The whole cage is **one instanced strip** — see `materials/SnareMaterial.js`. + * Every filament is the same ribbon, and a *role* decided from its instance + * index (leash → column → tendril → rim) picks which parametric path the + * vertex shader threads it along. Two draw calls for all four, however many + * filaments are in the air. + * + * As in every other block, a cast captures nothing but a seed and a few + * timestamps. Every metre, radian and second is resolved against these numbers + * each frame — including a zero-length one, which is why the trap reshapes + * under the sliders with the clock stopped. + */ + snare: { + /* --- the cast --- */ + range: 20.0, // maximum cast distance, metres + minRange: 0.0, // a trap can legitimately be dropped on your own feet + zoneRadius: 5.2, // AOE footprint (showcase) — circle indicator + speed: 62.0, // how fast the leash races to the point, metres/second + snapTime: 0.16, // seconds the ring takes to slam open once it lands + lifetime: 2.6, // seconds the snare stands + fadeTime: 0.75, // seconds it takes to collapse + cooldown: 1.4, + castAnim: 'cast2', // which clip in `CAST_ANIMATIONS` the body throws + + /* --- the leash that plants it --- */ + // Thrown from a hand, so these are measured from the caster's origin in the + // cast's own frame, exactly as the bolt and the rock are. + handHeight: 1.24, // metres above the floor + handForward: 0.58, // metres in front of the caster + handSide: 0.18, // metres to the side (+ follows `Ability#side`) + leashStrands: 3, // filaments in the whip + leashSag: -0.35, // metres the mid-span bows (negative drops it to the floor) + leashSpread: 0.22, // how far the filaments separate, metres + leashKink: 0.3, // kink amplitude on the whip, metres + leashWidth: 1.0, // × the shared filament width + leashCling: 0.12, // how far above the floor the tip runs, metres + + /* --- the column --- */ + strands: 15, // filaments in the pillar + height: 9.2, // how high it reaches, metres + heightCurve: 1.45, // <1 gets it up fast, >1 makes it climb late + throat: 0.16, // radius where it leaves the floor, × zoneRadius + columnSpread: 0.25, // radius at the top, × zoneRadius + columnCurve: 2.88, // >1 keeps the throat tight then opens it late + columnFlare: 0.585, // extra opening over the last quarter, × zoneRadius + columnTwist: 0.22, // turns a filament makes over the climb + columnSpin: 1.26, // turns/second the whole pillar rolls + columnKink: 0.27, // kink amplitude, metres + columnWidth: 1.86, // × the shared filament width + columnTaper: 1.09, // how much thinner the top is than the base + + /* --- the tendrils crawling out to the boundary --- */ + tendrils: 20, // separate ground filaments (capped with the rest at 56) + tendrilInner: 0.0, // where they leave the column, × zoneRadius + tendrilReach: 1.07, // where they end, × zoneRadius (1 = exactly on the band) + tendrilCurve: 1.18, // <1 throws them outward early + tendrilWander: 1.41, // radians a tendril veers over its run + tendrilArch: 1.16, // metres it hops off the floor mid-span + tendrilHug: 0.005, // how far above the floor it runs, metres + tendrilSpin: -0.225, // turns/second the whole fan rotates + tendrilKink: 0.72, // kink amplitude, metres + tendrilWidth: 0.75, // × the shared filament width + tendrilDim: 0.8, // how much dimmer than the column + + /* --- the arcs running around the rim --- */ + rimArcs: 14, // arcs on the boundary at once + rimSpan: 0.335, // fraction of the circle one arc covers + rimSpeed: -1.84, // revolutions/second they travel + // High enough to clear the burnt band underneath them: an arc that hops + // 0.3 m over a band this bright is simply invisible. + rimHeight: 0.98, // metres they hop at mid-span + rimJitter: 0.23, // radial wobble, × zoneRadius + rimKink: 0.15, // kink amplitude, metres + rimWidth: 0.85, // × the shared filament width + rimDim: 1.0, + + /* --- the shape every filament shares --- */ + // The same piecewise-linear value noise the bolt uses — linear on purpose, + // because smoothstep rounds the corners off and the corners are the entire + // reason it reads as lightning. + jitter: 1.0, // master multiplier on the four per-role kink amplitudes + jitterScale: 1.4, // kinks per metre + octaves: 4, // 1–5; each halves the amplitude and doubles the rate + jitterFalloff: 0.55, // amplitude kept per octave + crawl: 2.4, // how fast the kinks slide along a filament + pinch: 0.16, // fraction of the span the ends are pulled straight over + restrike: 21, // times/second every filament re-rolls its shape + flicker: 0.26, // depth of the whole-cage brightness stutter + flickerSpeed: 30, + strandFlash: 0.45, // how much individual filaments blink out + + /* --- the ribbon --- */ + width: 0.032, // half-width of a filament, metres + coreSharp: 4.4, // how hard the hot core falls off across the ribbon + glowWidth: 6.2, // the halo, × the core width + glowFalloff: 2.3, // how fast the halo fades across its ribbon + glowOpacity: 0.44, + softFade: 0.7, // metres of soft fade where a filament meets geometry + + /* --- colour --- */ + // Violet rather than the Storm Lance's blue: two electric abilities on the + // bar need to be told apart at a glance, and the hue split does it before + // the silhouette does. + colorCore: '#ffffff', // the centre of a filament + colorInner: '#dcd0ff', + colorOuter: '#8f6bff', // the outside of a filament + colorHalo: '#2a0e8c', // the wide glow around the cage + glow: 2.2, // overall emissive gain + opacity: 1.0, + + /* --- the field burnt into the floor --- */ + /** + * The indicator's promise, made real: the same circle, the same thick + * boundary, now a live shader instead of a targeting aid. It is an + * ability-owned mesh rather than a decal precisely because a decal captures + * its radius when it spawns — this one has to re-scale under `zoneRadius` + * while it is standing. + */ + fieldBoundary: 0.02, // thickness of the burnt band, metres + fieldBoundaryGlow: 2.9, + fieldFill: 0.65, // the wash inside it + fieldFalloff: 3.6, // how hard that wash crowds to the rim + fieldVeins: 2.98, // filaments burnt across the disc + fieldVeinScale: 2.0, // veins per metre + fieldVeinSharp: 0.72, // 0 = a wash, 1 = hard threads + fieldWarp: 0.55, // domain warp — what stops the veins reading as spokes + fieldCrawl: 0.5, // how fast they writhe + fieldRings: 2.4, // pressure rings travelling out from the middle + fieldRingSpeed: 0.8, // rings/second + fieldSpokes: 20, // ticks stepping around the boundary + fieldSpokeLength: 0.5, // how far they reach in, metres + fieldSpin: 0.05, // revolutions/second the ticks step around + fieldCore: 1.3, // brightness of the pool the column stands in + fieldCoreSize: 0.22, // its radius, × zoneRadius + fieldPulse: 0.0, // brightness breathing + fieldPulseSpeed: 3.95, + fieldOpacity: 1.0, + fieldHeight: 0.03, // hover distance above the floor, metres + colorField: '#8f6bff', // the wash and the veins + colorFieldEdge: '#ffffff', // the boundary band and the core pool + + /* --- what else the ground does --- */ + arcRate: 5.0, // branching burns laid around the rim, per second + arcRadius: 1.2, // radius of one, metres + arcLife: 0.75, + arcIntensity: 0.9, + arcBranches: 0.7, // how finely a burn splits into filaments + trailRate: 1.1, // burns laid per metre while the leash races out + scorchRadius: 1.6, // dark burn under the column, metres + scorchLife: 7.5, + scorchIntensity: 0.5, + colorArc: '#c3b0ff', + colorEmber: '#8f6bff', + colorScorch: '#0b0813', + shockRadius: 7.0, // the ring that snaps out when the trap opens, metres + colorShockA: '#8f6bff', // body of the shockwave ring + colorShockB: '#ffffff', // its crest + + /* --- sparks, updraft, smoke and debris --- */ + /** + * As in every other block: a four-stop gradient sampled over the particle's + * own lifetime, `A` at birth through `D` as it dies. The **updraft** is this + * ability's signature system — motes drawn off the whole disc and hauled + * inward and up into the column, which is the read that says the trap is + * pulling on the air rather than just sitting in it. + */ + sparkRate: 320, // sparks thrown off the cage, particles/second + sparkSize: 0.15, + sparkSpeed: 8.5, + sparkLifetime: 0.55, + sparkGravity: -13.0, + sparkStretch: 0.2, // how far a spark smears along its velocity + colorSparkA: '#ffffff', + colorSparkB: '#dcd0ff', + colorSparkC: '#8f6bff', + colorSparkD: '#2a0e8c', + updraftRate: 210, // motes hauled up the column, particles/second + updraftSize: 0.07, + updraftSpeed: 6.0, // how fast they are pulled in + updraftLifetime: 1.4, + updraftRise: 5.5, // upward acceleration once they are inside, m/s² + updraftInset: 0.15, // how far inside the boundary they are picked up + updraftTurbulence: 0.9, + colorUpdraftA: '#8f6bff', + colorUpdraftB: '#dcd0ff', + colorUpdraftC: '#ffffff', + colorUpdraftD: '#1b0a5e', + smokeRate: 70, // haze scoured off the burnt floor + smokeSize: 1.05, + smokeSpeed: 1.2, + smokeLifetime: 2.4, + smokeOpacity: 0.06, + smokeRise: 0.6, + colorSmokeA: '#4a4368', + colorSmokeB: '#3a3554', + colorSmokeC: '#2b2740', + colorSmokeD: '#191728', + debrisRate: 30, // chips torn off the floor inside the ring + debrisSize: 0.055, + debrisSpeed: 5.5, + debrisLifetime: 1.3, + debrisGravity: -17.0, + colorDebrisA: '#2a2733', + colorDebrisB: '#201e28', + colorDebrisC: '#1a1822', + colorDebrisD: '#1a1822', + + /* --- dynamic light --- */ + lightIntensity: 24, + lightRadius: 18, + lightHeight: 0.38, // how far up the column the light sits, 0..1 + lightColor: '#a98bff', + lightFlicker: 0.38, // depth of the light's gutter, 0 = steady + lightFlickerSpeed: 24, + + /* --- the throw, the snap and the hold --- */ + muzzleSize: 0.5, // the flash at the hand as the leash leaves it + muzzleIntensity: 1.7, + castFlash: 0.09, // screen flash on release + colorCastFlash: '#c3b0ff', + burstSize: 2.8, // the shell thrown off when the ring opens, metres + burstIntensity: 1.5, + burstSparks: 200, // extra sparks at the snap + burstDebris: 60, + pulseRate: 1.5, // pressure shells shed off the column while it holds, /s + pulseSize: 1.2, // radius of one, metres + pulseIntensity: 0.5, + ringRate: 1.4, // dust rings pushed across the floor while it holds, /s + impactShake: 0.85, + shakeDuration: 0.6, + holdShake: 0.07, // continuous rumble while the snare stands + impactFlash: 0.26, + rumble: 0.025, // rumble while the leash races out + colorBurstA: '#8f6bff', + colorBurstB: '#dcd0ff', + colorBurstC: '#ffffff', + colorFlash: '#c3b0ff' // the full-screen flash when it snaps open + }, + + /* ================================================================== */ + /* GLACIER — ability six, and the far cast that comes out of the floor */ + /* ================================================================== */ + /** + * A cold front races along the floor to the aimed point, the disc freezes out + * to the boundary the circle drew, and a wall of crystal tears up out of the + * ground around it: a ring of blades leaning outward with a skirt of wreckage + * banked against their feet. It stands, glints, breathes cold off its rim — + * and then breaks into plates and sinks back into the floor. Reference for the + * look: `Hud7Xfg3LH.jpg`. + * + * The **middle stays open**: every shard is seated in a band about + * `zoneRadius` and nothing is planted in the centre, because the read is a + * wall you are looking into and filling the disc stops it being a ring. What + * lives inside it is air and frozen ground. + * + * The second **far cast**, and the counterpart to the Voltaic Snare: same + * circle, same promise, opposite answer. The snare fills the footprint with + * current standing in the air; this one fills it with geometry standing on the + * ground, so `zoneRadius` is again the one number that matters — it is where + * the ring of blades is seated, where the sheet's boundary band burns, where + * the curtain of cold air stands and where the rime creeps. + * + * Three things carry it, and each has its own group below: + * + * - **the sweep.** The ring does not appear; it *closes*. The blade nearest + * the caster goes up first and the wave runs around both sides to meet + * behind the crown (`sweepTime`), with the skirt banking up behind the wave + * (`skirtDelay`, `skirtWave`). + * - **the freeze front.** Every shard crystallises upward along its own axis + * while it rises (`frontRough`, `frontWidth`, `frontGlow` — see + * `materials/GlacierMaterial.js`), so the ice *forms* rather than sliding + * out of a hole. + * - **the shatter.** It leaves the same way it arrived, in pieces: a + * per-shard ramp against a chunk id made of voronoi cells and flat facets, + * so plates and wedges come away one at a time (`shatterScale`, + * `shatterEdge`, `shatterGlow`). + * + * As in every other block, a cast captures nothing but a seed and a handful of + * timestamps. Every metre, radian and second is resolved against these numbers + * each frame — including a zero-length one, which is why the crown reshapes + * under the sliders with the clock stopped. + */ + glacier: { + /* --- the cast --- */ + range: 18.0, // maximum cast distance, metres + minRange: 0.0, // a wall of ice around your own feet is a legitimate play + zoneRadius: 5.4, // AOE frost crown footprint (showcase) + speed: 44.0, // how fast the front races to the point, metres/second + snapTime: 0.22, // seconds the sheet takes to freeze out to the boundary + lifetime: 4.2, // seconds the crown stands + shatterDelay: 0.5, // seconds after `lifetime` before the ice starts to break + shatterStagger: 0.45, // seconds of random delay between neighbours + sinkTime: 1.15, // seconds one shard takes to crumble and withdraw + cooldown: 1.6, + castAnim: 'cast3', // which clip in `CAST_ANIMATIONS` the body throws + + /* --- where the front leaves the caster --- */ + // Thrown from a hand, so these are measured from the caster's origin in the + // cast's own frame, exactly as the bolt, the rock and the leash are. + handHeight: 1.22, // metres above the floor + handForward: 0.6, // metres in front of the caster + handSide: 0.18, // metres to the side (+ follows `Ability#side`) + + /* --- how the footprint is filled --- */ + /** + * Everything is seated in a band about `zoneRadius`; the middle of the + * circle is left empty on purpose, because the read of the ability is a wall + * you are looking *into* and filling the disc stops it being a ring. The + * spire in the middle is kept as a control and ships at zero. + */ + spikeCount: 220, // instances spent on one cast (capped at 320) + density: 1.0, // multiplier on that count + ringShare: 0.6, // fraction of them spent on the wall at the boundary + coreShare: 0.0, // ... on the spire in the middle (0 = the middle stays open) + lateShare: 0.12, // ... held back to push up during the hold + ringSeat: 0.94, // where the wall stands, × zoneRadius + ringScatter: 0.16, // radial jitter of the wall, × zoneRadius + skirtSeat: 0.74, // inner lip of the wreckage banked against it, × zoneRadius + skirtBand: 0.42, // how wide that band is, × zoneRadius + skirtBias: 0.9, // <1 pushes the skirt outward, >1 crowds it inward + coreSpread: 0.16, // radius of the cluster in the middle, × zoneRadius + + /* --- the silhouette --- */ + /** + * The reference is a *starburst*, not a fence: long needles thrown outward + * from the rim at a steep angle, fanned off the radius so they cross, with + * wildly uneven lengths. `ringLean` is the single control that decides + * whether this reads as a crown or a picket line — at 0 it is a fence, and + * the higher it goes the further the blades are thrown out over the floor. + */ + ringHeight: 1.4, // length of a blade on the wall, metres + ringWave: 0.61, // how uneven the crest of that wall is, 0..1 + skirtHeight: 1.7, // length of a shard in the skirt, metres + coreHeight: 5.2, // length of the spire, metres + heightJitter: 0.65, + ringLean: 0.33, // radians the wall is thrown outward (≈19°) + skirtLean: 0.3, // ... and the skirt + coreLean: 0.2, // the spire stands nearly upright + leanJitter: 1.3, + fan: 1.16, // radians a blade is splayed off its own radius, ± — the crossing + twist: 1.0, // random yaw, 0..1 of a full turn + rubble: 0.53, // fraction of the skirt demoted to ankle-height wreckage + rubbleScale: 0.34, + + /* --- an individual crystal --- */ + // Blunt wedges rather than needles: a thick base that only narrows to about + // a third at the tip, so each facet stays wide enough to catch a flash. + radius: 0.375, // base radius, metres + radiusJitter: 0.94, + taper: 0.36, // tip radius as a fraction of the base + facets: 7, // sides of the prism — fewer, so each facet is a broad flash + roughness: 0.0, // how far the facets are pushed off a clean prism + bend: 0.0, // sideways curve from base to tip — nearly straight + + /* --- the bloom: when each shard goes up --- */ + riseTime: 0.2, // seconds from buried to full height + riseOvershoot: 0.3, // how far past full height the punch carries + settle: 0.5, // seconds the overshoot takes to damp out + sweepTime: 0.42, // seconds the wave takes to run around the ring + skirtDelay: 0.1, // seconds before the skirt starts + skirtWave: 0.26, // ... and how long it takes to cross the band + coreDelay: 0.2, // seconds before the spire comes up + stagger: 0.07, // seconds of random delay on top of all of it + bloomSpread: 0.7, // fraction of the hold the late shards are scattered over + + /* --- the ice: prismatic glass, not the Lance's quarried crystal --- */ + /** + * Deliberately the *opposite* treatment to `ice`. Two frost abilities on one + * bar have to be told apart before the silhouette does it, and a recolour is + * not enough — so where the Frost Lance is milky, diffuse and tinted deeper + * the thicker it gets, these blades are near-empty glass carried entirely by + * their edges: a chromatically split fresnel (`dispersion`), light piped up + * the body to an incandescent point (`pipe`, `tipBias`, `tipGlow`), flow + * lines instead of feather frost (`stria`) and one real reflection of the + * stage off every facet (`envIntensity`, `specular`). + * See `materials/GlacierMaterial.js`. + */ + colorGlass: '#0e4a66', // the little body it has + colorEdge: '#ffffff', // the silhouette, the flow lines and the glint + colorPrismA: '#57f0ff', // one end of the dispersion split + colorPrismB: '#8f9bff', // ... and the other + colorCore: '#a8f4ff', // the light piped up the blade + colorTip: '#ffffff', // the incandescent point + body: 1.37, // how much of a body it has at all, 0 = pure edges + edgePower: 1.14, // how tightly the silhouette hugs the rim + edgeGain: 0.81, // how hard it burns + dispersion: 0.73, // how far the red, green and blue fresnels come apart + pipe: 1.09, // light piped along the blade + tipBias: 1.6, // how hard that light crowds toward the point + bands: 1.4, // slow waves travelling up it + pulseSpeed: 0.6, + tipStart: 0.6, // where the incandescent tip begins, 0..1 up the blade + tipGlow: 1.5, + stria: 0.75, // flow lines running the blade's length + striaScale: 6.0, + envIntensity: 0.6, // how much of the HDR probe the facets catch + specular: 2.0, // the tight sun lobe off them + glow: 1.0, // overall emissive gain + opacity: 1.0, + birthGlow: 2.2, // extra glow on a shard that has just erupted + birthFade: 0.5, // seconds that birth flash lasts + + /* --- the freeze front and the shatter --- */ + /** + * The two things that make this ability's ice *arrive* and *leave* rather + * than fade in and out. Both are per-instance ramps the ability drives; what + * lives here is only their look. + */ + frontRough: 0.35, // how ragged the crystallising edge is + frontWidth: 0.12, // how much of the shard is lit behind that edge + frontGlow: 2.4, // how hard it burns + shatterScale: 7.0, // break-up cells per unit of the crystal + shatterEdge: 0.08, // width of the lit rim on a fresh break + shatterGlow: 3.0, + + /* --- the sheet of ice on the floor --- */ + /** + * The indicator's promise, made real: the same circle and the same thick + * boundary, now a frozen sheet instead of a targeting aid. An ability-owned + * mesh rather than a decal precisely because a decal captures its radius + * when it spawns — this one has to re-scale under `zoneRadius` while the + * crown is standing, and to run its own front outward and back. + */ + fieldBoundary: 0.4, // thickness of the band at the edge, metres + fieldBoundaryGlow: 2.4, + fieldFill: 0.26, // the wash inside it + fieldFalloff: 1.4, // how hard that wash crowds to the rim + fieldPlates: 1.0, // tonal break-up between plates + fieldPlateScale: 2.2, // plates per metre + fieldSeam: 0.8, // rime piled in the seams between them + fieldFingers: 0.9, // frost fingers crawling over the sheet + fieldFingerScale: 1.6, // fingers per metre + fieldWarp: 0.5, // domain warp — what stops them reading as spokes + fieldCrawl: 0.12, // how fast they writhe + fieldRings: 2.6, // pressure rings travelling in toward the spire + fieldRingSpeed: -0.5, // rings/second (negative travels inward) + fieldSweep: 0.4, // slow cold sweep around the disc + fieldSweepSpeed: 0.12, // revolutions/second + fieldCore: 1.0, // brightness of the pool the spire stands in + fieldCoreSize: 0.2, // its radius, × zoneRadius + fieldPulse: 0.18, // brightness breathing + fieldPulseSpeed: 1.6, + fieldOpacity: 1.0, + fieldHeight: 0.03, // hover distance above the floor, metres + colorField: '#a7e6ff', // the wash, the plates and the fingers + colorFieldEdge: '#ffffff', // the boundary band, the seams and the pool + + /* --- the curtain of cold air standing on the ring --- */ + /** + * An open cylinder seated on the boundary, eroded by ridged noise stretched + * hard vertically and scrolled downward. This is the piece that frames the + * crown from the outside: without it the wall of blades ends at its own + * silhouette, and a wall of ice that is not shedding cold reads as glass. + * Set `veil` to 0 to take it off. + */ + veil: 0.5, // master opacity of the curtain, 0 hides it + veilHeight: 1.9, // how high it stands, metres + veilRadius: 1.02, // where it stands, × zoneRadius + veilFlare: 0.32, // how far it leans outward at the top + veilBillow: 0.22, // metre-scale lobes pushing its silhouette off round + veilScale: 1.4, // noise features per metre + veilStretch: 0.5, // <1 draws the structures out into vertical falls + veilFlow: 0.4, // how fast they pour downward + veilErode: 0.55, // how much harder the top is eaten away than the base + veilFalloff: 1.8, // how fast it thins with height + veilSpin: 0.02, // revolutions/second the whole curtain turns + veilSoftFade: 0.8, // metres of soft fade where it meets geometry + colorVeil: '#8cd2ff', + colorVeilCrest: '#ffffff', + + /* --- what the ground does --- */ + trailFrostRate: 2.2, // rime patches laid per metre of front travel + trailFrostRadius: 1.0, // radius of one, metres + frostSpread: 1.5, // the rime sheet under the crown, × zoneRadius + frostLife: 7.5, // seconds a rime patch lingers + frostIntensity: 0.85, + frostCrystals: 1.5, // grain of the packed snow + frostCollar: 2.6, // rime around the foot of a blade, × its own radius + rimeRate: 3.0, // rime patches creeping around the boundary, per second + rimeRadius: 1.0, // radius of one, metres + colorFrost: '#f0f9ff', // the lit face of the snow + colorFrostEdge: '#79b6dd', // what it goes in its own shadow + shockRadius: 7.5, // the ring that snaps out when the crown blooms, metres + ringRate: 0.9, // pressure rings pushed out while it stands, per second + colorShockA: '#8ee8ff', // body of the shockwave ring + colorShockB: '#ffffff', // its crest + + /* --- mist, chips, glitter and snow --- */ + /** + * As in every other block: a four-stop gradient sampled over the particle's + * own lifetime, `A` at birth through `D` as it dies. The **snow** is this + * ability's signature system — ice dust spawned *above* the crown and left + * to fall back down through it. Everything else in the project is thrown + * upward, and a slow fall inside the ring is what says the air over it is + * freezing rather than burning. + */ + mistRate: 240, // cold air pouring off the rim, particles/second + mistSize: 1.1, + mistSpeed: 1.6, + mistLifetime: 3.0, + mistOpacity: 0.055, + mistRise: -0.12, // negative: cold air is heavy, it falls and spreads + mistTurbulence: 0.4, + colorMistA: '#f2feff', + colorMistB: '#cdefff', + colorMistC: '#8ec9e8', + colorMistD: '#0a2c42', + shardSize: 0.07, // ice chips + shardSpeed: 6.5, + shardLifetime: 1.6, + shardGravity: -15.0, + breachShards: 3, // chips thrown as one shard breaks the surface + shatterShards: 5, // ... and as it comes apart + colorShardA: '#ffffff', + colorShardB: '#cdefff', + colorShardC: '#8ee8ff', + colorShardD: '#0a3c55', + glitterRate: 150, // the sparkle lifting off the sheet + glitterSize: 0.05, + glitterSpeed: 2.6, + glitterLifetime: 2.4, + glitterRise: 1.3, // upward drift, metres/second + glitterTurbulence: 0.6, + glitterGlow: 1.0, + colorGlitterA: '#ffffff', + colorGlitterB: '#6fe0ff', + colorGlitterC: '#bdeeff', + colorGlitterD: '#062434', + snowRate: 110, // ice dust falling back through the crown + snowSize: 0.045, + snowSpeed: 0.9, // how hard it is pushed downward to start with + snowLifetime: 3.2, + snowFall: -1.1, // gravity on it, metres/second² + snowTurbulence: 0.85, // what turns the fall into a drift + snowGlow: 0.9, + snowInset: 0.85, // how far inside the boundary it falls, × zoneRadius + snowHeight: 1.35, // where it starts, × the height of the wall + colorSnowA: '#ffffff', + colorSnowB: '#e4f9ff', + colorSnowC: '#a7e6ff', + colorSnowD: '#0c3348', + + /* --- dynamic light --- */ + lightIntensity: 14, + lightRadius: 16, + lightHeight: 0.45, // how far up the crown the light sits, 0..1 + lightColor: '#8ee8ff', + + /* --- the throw, the bloom and the hold --- */ + muzzleSize: 0.55, // the puff at the hand as the front leaves it + muzzleIntensity: 1.5, + castFlash: 0.08, // screen flash on release + colorCastFlash: '#cdefff', + burstSize: 4.0, // the vapour shell thrown off at the bloom, metres + burstIntensity: 1.1, + burstShards: 120, // extra chips at the bloom + burstMist: 70, + burstGlitter: 140, + vapourRate: 1.6, // vapour shells shed off the wall while it stands, /s + vapourSize: 1.4, // radius of one, metres + vapourIntensity: 0.7, + impactShake: 0.85, + shakeDuration: 0.85, + holdShake: 0.05, // continuous rumble while the crown stands + impactFlash: 0.2, + rumble: 0.045, // rumble while the front races out + colorBurstA: '#a7e6ff', + colorBurstB: '#cdefff', + colorBurstC: '#ffffff', + colorFlash: '#cdefff' // the full-screen flash when it blooms + }, + + /* ------------------------------------------------------------------ */ + /* Camera rig */ + /* ------------------------------------------------------------------ */ + camera: { + distance: 11.5, + minDistance: 3.5, + maxDistance: 30, + zoomSpeed: 1.0, + zoomDamping: 0.002, + minPolar: 0.35, + maxPolar: 1.32, + fov: 46, + targetHeight: 1.35, + damping: 0.06, + autoFrame: 0.35 // how strongly the rig drifts toward an active cast + }, + + /* ------------------------------------------------------------------ */ + /* Environment & lighting */ + /* ------------------------------------------------------------------ */ + environment: { + // A dark cinematic stage: one cool key, a colder rim from behind, and very + // little fill, so the ice is the brightest thing on screen and the fog can + // swallow the floor into the backdrop. + sunIntensity: 2.6, + sunColor: '#e8f3ff', + sunAzimuth: 2.95, + sunElevation: 0.6, + ambientIntensity: 0.14, + ambientColor: '#8ea8d8', + hemiIntensity: 0.36, + hemiSkyColor: '#bdd7ff', + hemiGroundColor: '#3a4552', + rimIntensity: 1.1, + rimColor: '#9ec2ff', + rimAzimuth: 5.45, + rimElevation: 0.35, + envIntensity: 0.32, + backgroundColor: '#121820', + // Fog is pulled well back so it only dissolves the far edge of the floor into + // the backdrop rather than sitting on top of the action. Toggle and range are + // both live in the editor (Environment → Backdrop, fog & dust). + fogEnabled: true, + fogColor: '#121820', + fogNear: 26, + fogFar: 135, + shadowBias: -0.0008, + shadowRadius: 2.2, + floorColor: '#191f27', + floorTint: '#232b35', + floorRoughness: 0.88, + floorSheen: 0.34, + floorPool: 0.8, + // The stone tiling that dresses the floor: ambientCG Rock030 (CC0), a rough + // natural rock, living in public/textures/cathedral. `floorTextureScale` is metres of floor + // one tile covers; `floorTexTint` grades the grey stone toward `floorTint` so + // it sits inside the cool stage palette instead of fighting it. + floorTexture: false, + floorTextureScale: 12.0, + floorNormalScale: 0.85, + floorTexTint: 0.4, + dustAmount: 0.85, + contactShadow: 0.55 + }, + + /* ------------------------------------------------------------------ */ + /* Post processing */ + /* ------------------------------------------------------------------ */ + post: { + enabled: true, + exposure: 1.05, + // Threshold sits above the ice body's lit value on purpose: only the rim, + // the glints and the impact should bloom, not the whole crystal field. + // Strength is deliberately near zero — the crystal silhouette carries the + // read, and bloom was the thing eating it. Push it up if you want the halo. + bloomStrength: 0.03, + bloomRadius: 0.6, + bloomThreshold: 0.88, + vignette: 0.52, + chromaticAberration: 0.4, + contrast: 1.12, + saturation: 1.08, + temperature: -0.03, // + warm / - cool + lift: -0.008, + gain: 1.0, + grain: 0.045, + // Master gain on the screen-space warp written by LAYER.DISTORTION — the + // last link in the heat-haze chain. Screen widths, so it stays put when the + // window resizes. + distortion: 0.045, + flashStrength: 1.0 + } +}; + +/** + * How an ability is aimed. + * + * `LINE` is the skillshot the sandbox started with: an arrow swung about the + * caster, cast along its length. `ZONE` is the **far cast** — a circle with a + * thick boundary dropped at the cursor, which answers the only question a + * ground-targeted AoE has to answer before you commit: how much space is this + * going to take. Both resolve to the same `cast(origin, direction, distance)` + * event, so an ability never has to care which one aimed it; a zone ability + * simply reads its target as `pointAt(1)` and works outward from there. + */ +export const CastShape = Object.freeze({ + LINE: 'line', + ZONE: 'zone' +}); + +/** + * Ability ids, in slot order. + * + * `AbilityManager`, the HUD, the aim controller and the editor all key off this + * array, and the index is the slot the keyboard binds to — adding a third + * ability is a new file, an entry here and a settings block above. + */ +export const ELEMENTS = ['ice', 'thunder', 'meteor', 'beam', 'snare', 'glacier']; + +/** + * Registry metadata: how an ability is presented, and how it is aimed. + * + * `key` must match `InputManager`. `cast` is read by `AimController` to pick + * between the arrow and the circle; omit it and the ability is a line cast. + */ +export const ELEMENT_META = { + ice: { label: 'Frost Lance', accent: '#5fd0ff', key: 'Q', hint: 'Frost Lance' }, + thunder: { label: 'Storm Lance', accent: '#7fb4ff', key: 'E', hint: 'Storm Lance' }, + meteor: { label: 'Cinder Fall', accent: '#ff8a3c', key: 'R', hint: 'Cinder Fall' }, + beam: { label: 'Nova Beam', accent: '#7ff0ff', key: 'F', hint: 'Nova Beam' }, + snare: { + label: 'Voltaic Snare', + accent: '#a98bff', + key: 'V', + hint: 'Voltaic Snare', + cast: CastShape.ZONE + }, + glacier: { + label: 'Glacial Crown', + accent: '#8ee8ff', + key: 'X', + hint: 'Glacial Crown', + cast: CastShape.ZONE + } +}; + +/** How the given ability is aimed. Line unless its metadata says otherwise. */ +export function castShapeOf(element) { + return ELEMENT_META[element]?.cast ?? CastShape.LINE; +} + +/** The footprint a far cast will cover, metres. 0 for a line cast. */ +export function zoneRadiusOf(element) { + return castShapeOf(element) === CastShape.ZONE ? (settings[element]?.zoneRadius ?? 0) : 0; +} + +/** Immutable snapshot used by "Reset to defaults" and the preset system. */ +export const DEFAULT_SETTINGS = structuredClone(settings); + +/** + * Deep-merge a plain object into `settings` in place. + * Existing object identity is preserved so every live binding keeps working. + */ +export function applySettings(patch, target = settings) { + for (const key of Object.keys(patch)) { + const value = patch[key]; + if (value && typeof value === 'object' && !Array.isArray(value)) { + if (target[key] && typeof target[key] === 'object') applySettings(value, target[key]); + } else if (key in target) { + target[key] = value; + } + } + return target; +} + +/** Restore every value to the shipped defaults (in place). */ +export function resetSettings() { + applySettings(structuredClone(DEFAULT_SETTINGS)); +} + +/** Serialisable clone of the current state. */ +export function snapshotSettings() { + return structuredClone(settings); +} diff --git a/src/skillshot/materials/BeamMaterial.js b/src/skillshot/materials/BeamMaterial.js new file mode 100644 index 0000000..d8ecef4 --- /dev/null +++ b/src/skillshot/materials/BeamMaterial.js @@ -0,0 +1,669 @@ +import { ShaderMaterial, AdditiveBlending, Color, DoubleSide, Vector3 } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/** + * The six passes a beam is drawn in. + * + * The first three are the same tube at three radii — that stack is the whole + * trick behind the look, and it is worth being explicit about why: + * + * HALO — widest, nothing but a rim term. The atmosphere the beam is pushing. + * SHELL — the sheath: rim-weighted, so it reads as *hollow* and its silhouette + * edges are the brightest part of it. + * CORE — narrow, and weighted the opposite way: brightest where the view ray + * runs down the barrel and the path through the tube is longest. That + * is what makes the middle read as a solid white rod rather than as a + * cylinder with a lit outline. + * + * Rim-weighted outside, axis-weighted inside, both faces adding: that is a + * volume integral, cheaply. Then COIL and RING put structure *on* it, and ORB + * is the charge sitting in the caster's hands. + */ +export const BeamPass = Object.freeze({ + CORE: 0, // the white-hot rod + SHELL: 1, // the sheath around it + HALO: 2, // the wide outer bloom + COIL: 3, // ribbons spiralling around the column + RING: 4, // shock discs racing downrange + ORB: 5 // the charge in the caster's hands +}); + +const BEAM_UNIFORMS = /* glsl */ ` + #define PI 3.141592653589793 + #define TAU 6.283185307179586 + + uniform float uTime; + uniform vec3 uOrigin; + uniform vec3 uTarget; + uniform vec3 uSide; + uniform float uSeed; + uniform float uProgress; + uniform float uFade; + uniform float uWidthFade; + uniform float uCharge; + + uniform float uRadius; + uniform float uRadiusNear; + uniform float uRadiusCurve; + uniform float uRadiusScale; + uniform float uFlare; + uniform float uFlareWidth; + uniform float uThrob; + uniform float uThrobScale; + uniform float uThrobSpeed; + uniform float uWander; + uniform float uWanderScale; + uniform float uWanderSpeed; + + uniform float uRipple; + uniform float uRippleBands; + uniform float uRippleScale; + uniform float uRippleSpeed; + + uniform float uStreak; + uniform float uStreakSharp; + uniform float uStreakScale; + uniform float uStreakBands; + uniform float uStreakGlow; + uniform float uFlowSpeed; + + uniform float uCoreSharp; + uniform float uCoreFill; + uniform float uEdgePower; + uniform float uShellRim; + uniform float uShellFill; + uniform float uHaloRim; + uniform float uPassOpacity; + + uniform float uMouthGlow; + uniform float uMouthLength; + uniform float uTipGlow; + uniform float uTipLength; + + uniform float uCoils; + uniform float uCoilTurns; + uniform float uCoilSpeed; + uniform float uCoilRadius; + uniform float uCoilFlare; + uniform float uCoilWidth; + uniform float uCoilWidthTip; + uniform float uCoilSharp; + uniform float uCoilPulse; + uniform float uCoilPulseFreq; + uniform float uCoilPulseSpeed; + + uniform float uRingCount; + uniform float uRingSpeed; + uniform float uRingInner; + uniform float uRingOuter; + uniform float uRingSwell; + uniform float uRingFade; + uniform float uRingSharp; + + uniform float uOrbTurbulence; + uniform float uOrbScale; + uniform float uOrbFlow; + uniform float uOrbBands; + uniform float uOrbRim; + + uniform float uOpacity; + uniform float uGlow; + uniform float uSoftFade; + uniform vec3 uColorCore; + uniform vec3 uColorInner; + uniform vec3 uColorOuter; + uniform vec3 uColorHalo; + uniform vec3 uColorCoil; + uniform vec3 uColorCoilEdge; + uniform vec3 uColorRing; +`; + +/** + * Varyings, guarded per pass. + * + * Both stages share this block, so a pass only ever declares the interpolants it + * actually uses. Five is the ceiling here, which keeps the whole family well + * inside the varying budget on the low end. + */ +const BEAM_VARYINGS = /* glsl */ ` + varying float vT; + varying float vViewZ; + + #if BEAM_PASS <= 2 + varying float vA; + varying float vFacing; + #elif BEAM_PASS == 3 + varying float vSide; + #elif BEAM_PASS == 4 + varying float vBand; + varying float vPhase; + #else + varying vec3 vNormalW; + varying vec3 vViewDir; + varying float vDisp; + #endif +`; + +/** + * The shape of the beam, shared by every pass that lives on the column. + * + * `beamRadius` and `beamAxis` are the whole geometry: the tube, the coils and + * the shock discs all place themselves against these two functions, which is why + * they stay welded to each other when `radius` or `flare` is dragged. Nothing + * caches them — they are evaluated per vertex, per frame. + */ +const BEAM_SHAPE = /* glsl */ ` + /** Half-width of the column at t, metres. */ + float beamRadius(float t) { + float u = clamp(t, 0.0, 1.0); + float r = mix(uRadiusNear, uRadius, pow(u, max(uRadiusCurve, 0.01))); + // Pressure waves travelling out along it. Small — this is the beam + // breathing, not a sausage. + r *= 1.0 + uThrob * sin((u * uThrobScale - uTime * uThrobSpeed) * TAU); + // ... and the cone opening out where it lands. + r *= 1.0 + uFlare * smoothstep(1.0 - max(uFlareWidth, 1e-3), 1.0, u); + return max(r * uRadiusScale * uWidthFade, 1e-4); + } + + /** The column's local frame. n1/n2 span the plane across it. */ + void beamFrame(out vec3 dir, out vec3 n1, out vec3 n2) { + vec3 delta = uTarget - uOrigin; + float span = max(length(delta), 0.01); + dir = delta / span; + // Gram-Schmidt: the axis tilts down from the hands, so uSide is only + // approximately perpendicular to it. + vec3 lateral = uSide - dir * dot(uSide, dir); + n1 = length(lateral) > 1e-4 ? normalize(lateral) : normalize(cross(dir, vec3(0.0, 1.0, 0.0))); + n2 = normalize(cross(dir, n1)); + } + + /** + * A point on the column's axis at t. + * + * Smooth low-frequency drift, pinned at both ends. Deliberately *smooth*: the + * bolt's charm is that its noise is piecewise-linear and keeps its corners, + * and this is the opposite effect — a beam that kinks is a bolt. + */ + vec3 beamAxis(float t, vec3 n1, vec3 n2) { + vec3 p = mix(uOrigin, uTarget, t); + float ends = sin(clamp(t, 0.0, 1.0) * PI); + float dx = snoise(vec3(t * uWanderScale, uTime * uWanderSpeed, uSeed)); + float dy = snoise(vec3(t * uWanderScale + 31.7, uTime * uWanderSpeed, uSeed + 7.3)); + return p + (n1 * dx + n2 * dy) * uWander * ends; + } +`; + +const BEAM_VERTEX = /* glsl */ ` + ${BEAM_UNIFORMS} + ${BEAM_VARYINGS} + + #if BEAM_PASS == 3 + attribute float aStrand; + #endif + #if BEAM_PASS == 4 + attribute float aRing; + #endif + + ${noiseGLSL} + ${BEAM_SHAPE} + + #if BEAM_PASS == 3 + /** A point on one spiralling ribbon. */ + vec3 coilPoint(float t, float phase, vec3 n1, vec3 n2) { + float angle = (t * uCoilTurns + uTime * uCoilSpeed + phase) * TAU; + float r = beamRadius(t) * uCoilRadius * (1.0 + uCoilFlare * pow(clamp(t, 0.0, 1.0), 3.0)); + return beamAxis(t, n1, n2) + (n1 * cos(angle) + n2 * sin(angle)) * r; + } + #endif + + void main() { + vec3 dir, n1, n2; + + #if BEAM_PASS != 5 + beamFrame(dir, n1, n2); + #endif + + #if BEAM_PASS <= 2 /* the column */ + float t = position.x; + float a = position.y; + float angle = a * TAU; + vec3 nrm = n1 * cos(angle) + n2 * sin(angle); + + // Break the barrel up so it is a gas column and not a machined pipe. The + // noise crawls downrange with the flow rather than sitting still on it. + float rip = snoise(vec3( + cos(angle) * uRippleBands, + sin(angle) * uRippleBands, + t * uRippleScale - uTime * uRippleSpeed + uSeed + )); + float r = beamRadius(t) * (1.0 + uRipple * rip); + + vec3 here = beamAxis(t, n1, n2) + nrm * r; + vT = t; + vA = a; + // 1 looking down the barrel, 0 at the silhouette. Both tube weightings + // are built out of this one number. + vFacing = abs(dot(normalize(cameraPosition - here), nrm)); + + #elif BEAM_PASS == 3 /* the coils */ + float t = position.x; + vSide = position.y; + vT = t; + + float phase = uCoils <= 1.0 ? 0.0 : aStrand / uCoils; + phase += hash11(aStrand * 5.31 + uSeed) * 0.12; + + vec3 here = coilPoint(t, phase, n1, n2); + + // Tangent by finite difference, mirrored at the far end so the last node + // still has a neighbour to look at. + float step_ = 0.015; + float ahead = t + step_; + float flip = 1.0; + if (ahead > 1.0) { ahead = t - step_; flip = -1.0; } + vec3 tangent = (coilPoint(ahead, phase, n1, n2) - here) * flip; + tangent = length(tangent) > 1e-5 ? normalize(tangent) : dir; + + vec3 binormal = cross(tangent, normalize(cameraPosition - here)); + float bl = length(binormal); + binormal = bl > 1e-4 ? binormal / bl : n1; + + float halfWidth = uCoilWidth * mix(1.0, uCoilWidthTip, clamp(t, 0.0, 1.0)); + // Drawn to a point at the muzzle so the ribbons grow out of the orb. + halfWidth *= smoothstep(0.0, 0.05, t) * uWidthFade * uFade; + here += binormal * position.y * halfWidth; + + #elif BEAM_PASS == 4 /* the shock discs */ + // Evenly spaced along the column and slid downrange by the clock, so the + // train is a pure function of time — there is no queue on the CPU. + float phase = fract(aRing / max(uRingCount, 1.0) + uTime * uRingSpeed + uSeed * 0.37); + float t = phase; + float angle = position.y * TAU; + float r = beamRadius(t) * mix(uRingInner, uRingOuter, position.x) * (1.0 + uRingSwell * phase); + + vec3 here = beamAxis(t, n1, n2) + (n1 * cos(angle) + n2 * sin(angle)) * r; + vT = t; + vBand = position.x; + vPhase = phase; + + #else /* the charge orb */ + // The one pass that goes through the model matrix: the ability places and + // scales the orb, so it needs no world-space plumbing of its own. + vec3 np = normal * uOrbScale + vec3(uSeed * 3.1) - vec3(0.0, uTime * uOrbFlow, 0.0); + float n = fbm4(np) * 0.6 + ridged(np * 1.4, 4) * 0.4; + vDisp = n; + + vec4 world = modelMatrix * vec4(position + normal * n * uOrbTurbulence, 1.0); + vec3 here = world.xyz; + vNormalW = normalize(mat3(modelMatrix) * normal); + vViewDir = cameraPosition - here; + vT = 0.0; + #endif + + vec4 mv = viewMatrix * vec4(here, 1.0); + vViewZ = mv.z; + gl_Position = projectionMatrix * mv; + } +`; + +const BEAM_FRAGMENT = /* glsl */ ` + ${BEAM_UNIFORMS} + ${BEAM_VARYINGS} + + uniform float uGlobalGlow; + uniform vec2 uResolution; + uniform sampler2D uSceneDepth; + uniform float uCameraNear; + uniform float uCameraFar; + + ${noiseGLSL} + ${commonGLSL} + + void main() { + vec3 color = vec3(0.0); + float alpha = 0.0; + + #if BEAM_PASS == 5 /* the charge orb */ + float facing = abs(dot(normalize(vViewDir), normalize(vNormalW))); + float rim = pow(1.0 - facing, max(uOrbRim, 0.05)); + float heat = clamp(vDisp * 0.5 + 0.5, 0.0, 1.0); + // Filaments crawling over the surface, thresholded hard: a wide band here + // fills the orb in and it stops reading as contained energy. + float fil = smoothstep(0.74, 0.98, + ridged(vNormalW * uOrbBands + vec3(0.0, uTime * uOrbFlow * 2.0, 0.0) + uSeed, 4)); + + color = mix(uColorOuter, uColorInner, heat); + color = mix(color, uColorCore, clamp(fil + rim * 0.35, 0.0, 1.0)); + color += uColorCore * fil * 1.6; + alpha = (0.2 + rim * 0.9 + fil * 0.75) * uCharge; + + #else + // Ahead of the leading edge there is no beam yet. The column is built + // whole and clipped here rather than scaled, so its *shape* never changes + // as the front travels — only how much of it exists. + float tip = max(uTipLength, 1e-3); + float drawn = smoothstep(uProgress, uProgress - tip, vT); + if (drawn <= 0.002) discard; + + #if BEAM_PASS <= 2 /* the column */ + float angle = vA * TAU; + // Filaments streaming downrange, stretched hard along the axis: this is + // gas being pushed through a barrel, not a bolt looking for ground. + float flow = ridged(vec3( + vT * uStreakScale - uTime * uFlowSpeed, + cos(angle) * uStreakBands, + sin(angle) * uStreakBands + uSeed + ), 4); + float streak = smoothstep(mix(0.42, 0.86, clamp(uStreakSharp, 0.0, 1.0)), 0.99, flow) * uStreak; + + float facing = clamp(vFacing, 0.0, 1.0); + float axisward = pow(facing, max(uCoreSharp, 0.05)); + float rim = pow(1.0 - facing, max(uEdgePower, 0.05)); + + #if BEAM_PASS == 0 /* CORE */ + color = mix(uColorInner, uColorCore, clamp(0.35 + streak, 0.0, 1.0)); + alpha = uCoreFill * mix(0.28, 1.0, axisward) + streak * 0.35; + + #elif BEAM_PASS == 1 /* SHELL */ + color = mix(uColorOuter, uColorInner, clamp(rim * 0.55 + streak, 0.0, 1.0)); + color += uColorCore * streak * uStreakGlow; + alpha = rim * uShellRim + uShellFill * mix(0.12, 1.0, axisward) + streak * 0.4; + + #else /* HALO */ + float wide = pow(1.0 - facing, max(uHaloRim, 0.05)); + color = mix(uColorHalo, uColorOuter, wide); + alpha = wide; + #endif + + // The mouth, where the column is still being fed out of the orb. + float mouth = smoothstep(uMouthLength, 0.0, vT); + color += uColorCore * mouth * uMouthGlow; + alpha += mouth * uMouthGlow * 0.2; + + // The leading edge while it travels — and, once it has landed, the + // white-hot end where it is still burning into the floor. + float lead = smoothstep(uProgress - tip * 2.0, uProgress, vT); + color += uColorCore * lead * uTipGlow; + alpha += lead * uTipGlow * 0.18; + + #elif BEAM_PASS == 3 /* the coils */ + float v = clamp(abs(vSide), 0.0, 1.0); + float profile = pow(1.0 - v, max(uCoilSharp, 0.05)); + // A charge running out along the ribbon, which is what stops the coils + // reading as static piping wrapped around the beam. + float pulse = 0.5 + 0.5 * sin((vT * uCoilPulseFreq - uTime * uCoilPulseSpeed) * TAU); + color = mix(uColorCoilEdge, uColorCoil, profile); + color += uColorCore * pulse * profile * uCoilPulse; + alpha = profile * mix(1.0 - clamp(uCoilPulse, 0.0, 0.9) * 0.5, 1.0, pulse); + + #else /* the shock discs */ + float band = 1.0 - abs(vBand * 2.0 - 1.0); + float profile = pow(clamp(band, 0.0, 1.0), max(uRingSharp, 0.05)); + // Discs thin out as they run downrange and the pressure behind them + // drops off. + color = mix(uColorRing, uColorCore, profile); + alpha = profile * mix(1.0, uRingFade, vPhase); + #endif + + alpha *= drawn; + #endif + + alpha *= uFade * uOpacity * uPassOpacity; + if (alpha < 0.003) discard; + + vec2 screenUV = gl_FragCoord.xy / uResolution; + alpha *= softFade(uSceneDepth, screenUV, vViewZ, uCameraNear, uCameraFar, uSoftFade); + if (alpha < 0.003) discard; + + color *= uGlow * uGlobalGlow; + gl_FragColor = vec4(color, alpha); + } +`; + +/** + * One pass of a beam. + * + * Every pass shares the same uniform block and the same `userData.sync()`, so + * the ability hands all six the identical state object and the editor drives + * them together. What differs is the compile-time `BEAM_PASS` and two uniforms + * the pass owns: `uRadiusScale` (which of the three tube radii it is) and + * `uPassOpacity`. + * + * @param {number} pass BeamPass.* + */ +export function createBeamMaterial(pass = BeamPass.CORE) { + const material = new ShaderMaterial({ + defines: { BEAM_PASS: pass }, + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uOrigin: { value: new Vector3() }, + uTarget: { value: new Vector3(0, 0, 1) }, + uSide: { value: new Vector3(1, 0, 0) }, + uSeed: { value: 0 }, + uProgress: { value: 0 }, + uFade: { value: 1 }, + uWidthFade: { value: 1 }, + uCharge: { value: 0 }, + + uRadius: { value: 0.62 }, + uRadiusNear: { value: 0.22 }, + uRadiusCurve: { value: 0.7 }, + uRadiusScale: { value: 1 }, + uFlare: { value: 0.9 }, + uFlareWidth: { value: 0.22 }, + uThrob: { value: 0.06 }, + uThrobScale: { value: 2.4 }, + uThrobSpeed: { value: 1.6 }, + uWander: { value: 0.06 }, + uWanderScale: { value: 0.9 }, + uWanderSpeed: { value: 0.7 }, + + uRipple: { value: 0.12 }, + uRippleBands: { value: 1.6 }, + uRippleScale: { value: 3.2 }, + uRippleSpeed: { value: 2.4 }, + + uStreak: { value: 0.9 }, + uStreakSharp: { value: 0.45 }, + uStreakScale: { value: 5.5 }, + uStreakBands: { value: 2.6 }, + uStreakGlow: { value: 1.1 }, + uFlowSpeed: { value: 7 }, + + uCoreSharp: { value: 1.4 }, + uCoreFill: { value: 0.85 }, + uEdgePower: { value: 2.2 }, + uShellRim: { value: 0.9 }, + uShellFill: { value: 0.18 }, + uHaloRim: { value: 3.4 }, + uPassOpacity: { value: 1 }, + + uMouthGlow: { value: 1.5 }, + uMouthLength: { value: 0.1 }, + uTipGlow: { value: 1.6 }, + uTipLength: { value: 0.06 }, + + uCoils: { value: 4 }, + uCoilTurns: { value: 2.2 }, + uCoilSpeed: { value: 0.9 }, + uCoilRadius: { value: 1.35 }, + uCoilFlare: { value: 0.8 }, + uCoilWidth: { value: 0.1 }, + uCoilWidthTip: { value: 1.8 }, + uCoilSharp: { value: 2.2 }, + uCoilPulse: { value: 0.6 }, + uCoilPulseFreq: { value: 3 }, + uCoilPulseSpeed: { value: 1.6 }, + + uRingCount: { value: 6 }, + uRingSpeed: { value: 1.3 }, + uRingInner: { value: 1.15 }, + uRingOuter: { value: 2.1 }, + uRingSwell: { value: 0.5 }, + uRingFade: { value: 0.15 }, + uRingSharp: { value: 1.6 }, + + uOrbTurbulence: { value: 0.22 }, + uOrbScale: { value: 2.2 }, + uOrbFlow: { value: 0.8 }, + uOrbBands: { value: 5 }, + uOrbRim: { value: 1.8 }, + + uOpacity: { value: 1 }, + uGlow: { value: 2.2 }, + uSoftFade: { value: 0.6 }, + uColorCore: { value: new Color(1, 1, 1) }, + uColorInner: { value: new Color(0.82, 0.96, 1) }, + uColorOuter: { value: new Color(0.29, 0.78, 1) }, + uColorHalo: { value: new Color(0.05, 0.2, 0.85) }, + uColorCoil: { value: new Color(1, 0.86, 0.5) }, + uColorCoilEdge: { value: new Color(1, 0.45, 0.12) }, + uColorRing: { value: new Color(0.6, 0.94, 1) } + }), + vertexShader: BEAM_VERTEX, + fragmentShader: BEAM_FRAGMENT + }); + + /** + * Push the live settings and the current cast state into the uniforms. + * + * Called every frame — including on a zero-length frame while the sandbox is + * paused, which is what keeps every control below a live slider. + * + * @param {object} state { origin, target, side, progress, fade, widthFade, + * charge, seed, coils, rings } + */ + material.userData.sync = (state) => { + const c = settings.beam; + const g = settings.global; + const u = material.uniforms; + + u.uOrigin.value.copy(state.origin); + u.uTarget.value.copy(state.target); + u.uSide.value.copy(state.side); + u.uSeed.value = state.seed; + u.uProgress.value = state.progress; + u.uFade.value = state.fade; + u.uWidthFade.value = state.widthFade; + u.uCharge.value = state.charge; + + u.uRadius.value = c.radius; + u.uRadiusNear.value = c.radiusNear; + u.uRadiusCurve.value = c.radiusCurve; + u.uFlare.value = c.flare; + u.uFlareWidth.value = c.flareWidth; + u.uThrob.value = c.throb; + u.uThrobScale.value = c.throbScale; + u.uThrobSpeed.value = c.throbSpeed * g.animationSpeed; + // The drift is the one place `randomness` and the noise multipliers have + // anything to bite on, so they scale here rather than everywhere. + u.uWander.value = c.wander * g.randomness * g.noiseStrength; + u.uWanderScale.value = c.wanderScale * g.noiseFrequency; + u.uWanderSpeed.value = c.wanderSpeed * g.noiseSpeed; + + u.uRipple.value = c.ripple * g.noiseStrength; + u.uRippleBands.value = c.rippleBands; + u.uRippleScale.value = c.rippleScale * g.noiseFrequency; + u.uRippleSpeed.value = c.rippleSpeed * g.noiseSpeed; + + u.uStreak.value = c.streak; + u.uStreakSharp.value = c.streakSharp; + u.uStreakScale.value = c.streakScale * g.noiseFrequency; + u.uStreakBands.value = c.streakBands; + u.uStreakGlow.value = c.streakGlow; + u.uFlowSpeed.value = c.flowSpeed * g.noiseSpeed; + + u.uCoreSharp.value = c.coreSharp; + u.uCoreFill.value = c.coreFill; + u.uEdgePower.value = c.edgePower; + u.uShellRim.value = c.shellRim; + u.uShellFill.value = c.shellFill; + u.uHaloRim.value = c.haloRim; + + u.uMouthGlow.value = c.mouthGlow; + u.uMouthLength.value = c.mouthLength; + u.uTipGlow.value = c.tipGlow; + u.uTipLength.value = c.tipLength; + + u.uCoils.value = state.coils; + u.uCoilTurns.value = c.coilTurns; + u.uCoilSpeed.value = c.coilSpeed; + u.uCoilRadius.value = c.coilRadius; + u.uCoilFlare.value = c.coilFlare; + u.uCoilWidth.value = c.coilWidth; + u.uCoilWidthTip.value = c.coilWidthTip; + u.uCoilSharp.value = c.coilSharp; + u.uCoilPulse.value = c.coilPulse; + u.uCoilPulseFreq.value = c.coilPulseFreq; + u.uCoilPulseSpeed.value = c.coilPulseSpeed; + + u.uRingCount.value = state.rings; + u.uRingSpeed.value = c.ringSpeed; + u.uRingInner.value = c.ringInner; + u.uRingOuter.value = c.ringOuter; + u.uRingSwell.value = c.ringSwell; + u.uRingFade.value = c.ringFade; + u.uRingSharp.value = c.ringSharp; + + u.uOrbTurbulence.value = c.orbTurbulence * g.turbulence; + u.uOrbScale.value = c.orbScale; + u.uOrbFlow.value = c.orbFlow * g.noiseSpeed; + u.uOrbBands.value = c.orbBands; + u.uOrbRim.value = c.orbRim; + + // Which of the three tube radii this pass is, and how hard it is drawn. + switch (pass) { + case BeamPass.CORE: + u.uRadiusScale.value = c.coreWidth; + u.uPassOpacity.value = 1; + u.uGlow.value = c.glow; + break; + case BeamPass.SHELL: + u.uRadiusScale.value = c.shellWidth; + u.uPassOpacity.value = c.shellOpacity; + u.uGlow.value = c.glow; + break; + case BeamPass.HALO: + u.uRadiusScale.value = c.haloWidth; + u.uPassOpacity.value = c.haloOpacity; + u.uGlow.value = c.glow * 0.8; + break; + case BeamPass.COIL: + u.uRadiusScale.value = 1; + u.uPassOpacity.value = c.coilOpacity; + u.uGlow.value = c.coilGlow; + break; + case BeamPass.RING: + u.uRadiusScale.value = 1; + u.uPassOpacity.value = c.ringOpacity; + u.uGlow.value = c.ringGlow; + break; + default: + u.uRadiusScale.value = 1; + u.uPassOpacity.value = c.orbOpacity; + u.uGlow.value = c.orbGlow; + break; + } + + u.uOpacity.value = c.opacity * g.opacity; + u.uSoftFade.value = c.softFade; + u.uColorCore.value.copy(getColor(c.colorCore)); + u.uColorInner.value.copy(getColor(c.colorInner)); + u.uColorOuter.value.copy(getColor(c.colorOuter)); + u.uColorHalo.value.copy(getColor(c.colorHalo)); + u.uColorCoil.value.copy(getColor(c.colorCoil)); + u.uColorCoilEdge.value.copy(getColor(c.colorCoilEdge)); + u.uColorRing.value.copy(getColor(c.colorRing)); + }; + + return material; +} diff --git a/src/skillshot/materials/FrostFieldMaterial.js b/src/skillshot/materials/FrostFieldMaterial.js new file mode 100644 index 0000000..3543290 --- /dev/null +++ b/src/skillshot/materials/FrostFieldMaterial.js @@ -0,0 +1,401 @@ +import { ShaderMaterial, AdditiveBlending, Color, DoubleSide } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/* ==================================================================== */ +/* The floor the crown stands on */ +/* ==================================================================== */ + +const FIELD_VERTEX = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +/** + * The circle the indicator promised, **frozen** into the floor. + * + * Same signed-distance construction and the same thick boundary as the aim + * circle and the Voltaic Snare's burnt field — that correspondence is the point: + * what you measured out before you clicked is what is standing on the ground + * afterwards. What it does not share is the treatment. Where the snare's field + * is charred and veined, this one is a *sheet of ice*, and it is built from the + * three things that actually read as frozen ground: + * + * - **plates.** A voronoi field whose *seams* are bright, because rime piles + * up thickest where two plates meet. The cell id also breaks the tone of + * each plate, so the sheet has facets rather than a wash. + * - **fingers.** Domain-warped ridged noise weighted to the radius: frost + * never advances as a circle, it reaches. Sampled in the *plane* and never + * on `atan()`, which would hand every radius along a bearing the same value + * and draw dead-straight spokes out of the middle. + * - **a front.** `uFreeze` is how far the ice has got, 0..1 of the boundary, + * with the same angular fingering dragging the edge out of round. It runs + * forward as the crown opens and *retreats* as it collapses, so the disc + * freezes outward and thaws inward instead of fading in place. + * + * It is an ability-owned mesh rather than a pooled decal for one reason: a decal + * captures its radius when it spawns, and this circle has to re-scale under + * `zoneRadius` while it is still standing. + */ +const FIELD_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uQuadSize; // metres the quad covers, edge to edge + uniform float uRadius; // footprint radius, metres + uniform float uBoundary; // thickness of the band that is the edge + uniform float uBoundaryGlow; + uniform float uFill; + uniform float uFalloff; + uniform float uPlates; + uniform float uPlateScale; + uniform float uSeam; + uniform float uFingers; + uniform float uFingerScale; + uniform float uWarp; + uniform float uCrawl; + uniform float uRings; + uniform float uRingSpeed; + uniform float uSweep; + uniform float uSweepSpeed; + uniform float uCore; + uniform float uCoreSize; + uniform float uPulse; + uniform float uPulseSpeed; + uniform float uFreeze; // 0..1 — how far the sheet has spread + uniform float uSeed; + uniform float uFade; + uniform float uOpacity; + uniform vec3 uColorField; + uniform vec3 uColorEdge; + uniform float uGlobalGlow; + + varying vec2 vUv; + + ${noiseGLSL} + ${commonGLSL} + + #define TAU 6.28318530718 + + void main() { + /* ---- uv → metres, measured from the centre of the footprint ---- */ + vec2 p = vec2(vUv.x - 0.5, 0.5 - vUv.y) * uQuadSize; + float d = length(p); + + float outer = uRadius + uBoundary * 0.4; + float inner = max(0.01, uRadius - uBoundary * 0.6); + + float aa = fwidth(d) + 0.02; + if (d > outer + aa * 4.0) discard; + + /* ---- how far the ice has actually got ---- */ + // The front is dragged out of round by an angular lookup — the one place an + // angular function is the right tool, because this *is* a boundary. + vec2 bearing = d > 1e-4 ? p / d : vec2(1.0, 0.0); + float fingers = 0.78 + 0.3 * snoise(vec3(bearing * 3.1, uSeed * 6.0)); + float reach = uFreeze * outer * fingers; + float frozen = smoothstep(reach, reach - uBoundary * 1.6, d); + if (frozen < 0.004) discard; + + float band = smoothstep(outer + aa, outer - aa, d) * smoothstep(inner - aa, inner + aa, d); + float interior = smoothstep(inner + aa, inner - aa, d); + float radial = clamp(d / inner, 0.0, 1.0); + + /* ---- the sheet: plates, and the rime piled in their seams ---- */ + vec2 cell = voronoi2(p * uPlateScale + uSeed * 20.0); + float seams = smoothstep(0.28, 0.02, cell.x) * uSeam; + float plate = mix(0.55, 1.0, cell.y) * uPlates; + + /* ---- frost fingers crawling over it ---- */ + float warp = fbm3(vec3(p * 0.5, uTime * 0.12 + uSeed)) * uWarp; + float fil = ridged(vec3(p * uFingerScale + warp, uSeed * 11.0 + uTime * uCrawl), 4); + float frost = smoothstep(0.66, 0.95, fil) * uFingers * (0.35 + 0.65 * radial); + + /* ---- pressure rings, travelling inward toward the spire ---- */ + float ring = 0.5 + 0.5 * cos((radial * uRings + uTime * uRingSpeed) * TAU); + ring = pow(ring, 8.0) * interior; + + /* ---- a slow cold sweep, so a standing field is never quite still ---- */ + float ang = atan(p.y, p.x) / TAU + 0.5; + float sweepPhase = fract(ang - uTime * uSweepSpeed); + float sweep = pow(1.0 - sweepPhase, 6.0) * smoothstep(0.0, 0.05, sweepPhase) * uSweep * interior; + + /* ---- the pool the spire stands in ---- */ + float core = smoothstep(uCoreSize * uRadius, 0.0, d) * uCore; + + float breathe = 1.0 + uPulse * sin(uTime * uPulseSpeed * TAU); + float wash = interior * pow(radial, uFalloff) * uFill * plate; + float body = (wash + frost * 0.8) * breathe; + float lines = (band * uBoundaryGlow + seams * interior * 0.9 + ring * 0.4 + sweep + core) * breathe; + + float alpha = clamp(body + lines, 0.0, 1.0) * frozen * uFade * uOpacity; + if (alpha < 0.004) discard; + + vec3 color = uColorField * body + uColorEdge * lines; + // The advancing lip is still freezing, so it stays lit while it travels. + color += uColorEdge * smoothstep(uBoundary * 1.2, 0.0, abs(d - reach)) * 0.9 * + step(0.02, uFreeze) * step(uFreeze, 0.985); + + gl_FragColor = vec4(color * uGlobalGlow, alpha); + } +`; + +/** + * The sheet of ice on the floor. One quad, re-sized and re-shaded from + * `settings.glacier` every frame. + */ +export function createFrostFieldMaterial() { + const material = new ShaderMaterial({ + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uQuadSize: { value: 12 }, + uRadius: { value: 4.6 }, + uBoundary: { value: 0.4 }, + uBoundaryGlow: { value: 2.4 }, + uFill: { value: 0.26 }, + uFalloff: { value: 1.4 }, + uPlates: { value: 1 }, + uPlateScale: { value: 2.2 }, + uSeam: { value: 0.8 }, + uFingers: { value: 0.9 }, + uFingerScale: { value: 1.6 }, + uWarp: { value: 0.5 }, + uCrawl: { value: 0.12 }, + uRings: { value: 2.6 }, + uRingSpeed: { value: -0.5 }, + uSweep: { value: 0.4 }, + uSweepSpeed: { value: 0.12 }, + uCore: { value: 1 }, + uCoreSize: { value: 0.2 }, + uPulse: { value: 0.18 }, + uPulseSpeed: { value: 1.6 }, + uFreeze: { value: 0 }, + uSeed: { value: 0 }, + uFade: { value: 1 }, + uOpacity: { value: 1 }, + uColorField: { value: new Color(0.66, 0.9, 1) }, + uColorEdge: { value: new Color(1, 1, 1) } + }), + vertexShader: FIELD_VERTEX, + fragmentShader: FIELD_FRAGMENT + }); + + /** + * @param {object} state { radius, quadSize, freeze, fade, seed } + */ + material.userData.sync = (state) => { + const c = settings.glacier; + const g = settings.global; + const u = material.uniforms; + + u.uQuadSize.value = state.quadSize; + u.uRadius.value = state.radius; + u.uFreeze.value = state.freeze; + u.uFade.value = state.fade; + u.uSeed.value = state.seed; + + u.uBoundary.value = c.fieldBoundary; + u.uBoundaryGlow.value = c.fieldBoundaryGlow; + u.uFill.value = c.fieldFill; + u.uFalloff.value = c.fieldFalloff; + u.uPlates.value = c.fieldPlates; + u.uPlateScale.value = c.fieldPlateScale * g.noiseFrequency; + u.uSeam.value = c.fieldSeam * g.shaderIntensity; + u.uFingers.value = c.fieldFingers * g.shaderIntensity; + u.uFingerScale.value = c.fieldFingerScale * g.noiseFrequency; + u.uWarp.value = c.fieldWarp * g.noiseStrength; + u.uCrawl.value = c.fieldCrawl * g.noiseSpeed; + u.uRings.value = c.fieldRings; + u.uRingSpeed.value = c.fieldRingSpeed; + u.uSweep.value = c.fieldSweep; + u.uSweepSpeed.value = c.fieldSweepSpeed; + u.uCore.value = c.fieldCore; + u.uCoreSize.value = c.fieldCoreSize; + u.uPulse.value = c.fieldPulse; + u.uPulseSpeed.value = c.fieldPulseSpeed; + u.uOpacity.value = c.fieldOpacity * g.opacity; + u.uColorField.value.copy(getColor(c.colorField)); + u.uColorEdge.value.copy(getColor(c.colorFieldEdge)); + }; + + return material; +} + +/* ==================================================================== */ +/* The curtain of cold air standing on the ring */ +/* ==================================================================== */ + +/** + * A cylinder in *parameter* space: `uv.x` runs once around the boundary and + * `uv.y` from the floor to the top of the curtain, and the vertex stage flares + * it outward with height and pushes the silhouette off round with the same noise + * the fragment stage erodes it with. Without that displacement the veil is a + * cylinder no matter how hard the fragment shader is worked, and it reads as a + * fog machine rather than as air falling off a wall of ice. + */ +const VEIL_VERTEX = /* glsl */ ` + uniform float uTime; + uniform float uFlare; + uniform float uBillow; + uniform float uScale; + uniform float uFlow; + uniform float uSeed; + + varying vec2 vUv; + varying vec3 vWorld; + varying float vViewZ; + + ${noiseGLSL} + + void main() { + vUv = uv; + + vec3 pos = position; + vec2 outward = normalize(pos.xz + vec2(1e-5)); + + // Cold air spills *outward* as it falls off the rim, so the curtain leans + // out with height rather than standing straight up. + pos.xz *= 1.0 + uFlare * pow(clamp(uv.y, 0.0, 1.0), 1.6); + + // Metre-scale lobes, so the silhouette bulges and pinches around the ring. + float lobe = snoise(vec3(outward * uScale * 1.4, uTime * uFlow * 0.35 + uSeed)); + pos.xz += outward * lobe * uBillow * (0.25 + 0.75 * uv.y); + + vec4 world = modelMatrix * vec4(pos, 1.0); + vWorld = world.xyz; + + vec4 mv = viewMatrix * world; + vViewZ = mv.z; + gl_Position = projectionMatrix * mv; + } +`; + +/** + * The veil itself: ridged noise stretched hard vertically and scrolled *down*, + * thresholded harder the higher it goes, so the curtain tears into separate + * falls of vapour toward the top instead of ending on a line. Soft-faded against + * the depth prepass, which is what lets it stand between the shards without + * cutting into them. + */ +const VEIL_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uScale; + uniform float uStretch; + uniform float uFlow; + uniform float uErode; + uniform float uFalloff; + uniform float uSoftFade; + uniform float uSeed; + uniform float uFade; + uniform float uOpacity; + uniform vec3 uColorVeil; + uniform vec3 uColorCrest; + uniform float uGlobalGlow; + + uniform vec2 uResolution; + uniform sampler2D uSceneDepth; + uniform float uCameraNear; + uniform float uCameraFar; + + varying vec2 vUv; + varying vec3 vWorld; + varying float vViewZ; + + ${noiseGLSL} + ${commonGLSL} + + void main() { + float up = clamp(vUv.y, 0.0, 1.0); + + // Sampled in world space so the curtain does not swim when the ring is + // re-scaled, and squashed on Y so the structures are falls rather than + // clouds. + vec3 sp = vec3(vWorld.xz * uScale, vWorld.y * uScale * uStretch + uTime * uFlow + uSeed * 7.0); + float fil = ridged(sp, 4); + + // Erode harder with height: the top of the curtain is mostly gaps. + float threshold = mix(0.52, 0.95, up * uErode); + float veil = smoothstep(threshold, threshold + 0.22, fil); + + // Weighted to the floor, and pinched off right at it so the curtain does not + // end in a hard line where it meets the ground. + float profile = pow(1.0 - up, uFalloff) * smoothstep(0.0, 0.08, up); + + float alpha = veil * profile * uFade * uOpacity; + if (alpha < 0.004) discard; + + vec2 screenUV = gl_FragCoord.xy / uResolution; + alpha *= softFade(uSceneDepth, screenUV, vViewZ, uCameraNear, uCameraFar, uSoftFade); + if (alpha < 0.004) discard; + + vec3 color = mix(uColorVeil, uColorCrest, clamp(veil * 1.3 * (1.0 - up), 0.0, 1.0)); + gl_FragColor = vec4(color * uGlobalGlow, clamp(alpha, 0.0, 1.0)); + } +`; + +/** + * The standing curtain of freezing vapour. One open cylinder seated on the + * boundary, re-sized and re-shaded from `settings.glacier` every frame. + */ +export function createFrostVeilMaterial() { + const material = new ShaderMaterial({ + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uFlare: { value: 0.35 }, + uBillow: { value: 0.22 }, + uScale: { value: 1.4 }, + uStretch: { value: 0.55 }, + uFlow: { value: 0.35 }, + uErode: { value: 0.55 }, + uFalloff: { value: 1.8 }, + uSoftFade: { value: 0.8 }, + uSeed: { value: 0 }, + uFade: { value: 1 }, + uOpacity: { value: 0.55 }, + uColorVeil: { value: new Color(0.55, 0.82, 1) }, + uColorCrest: { value: new Color(1, 1, 1) } + }), + vertexShader: VEIL_VERTEX, + fragmentShader: VEIL_FRAGMENT + }); + + /** + * @param {object} state { fade, seed } + */ + material.userData.sync = (state) => { + const c = settings.glacier; + const g = settings.global; + const u = material.uniforms; + + u.uFade.value = state.fade; + u.uSeed.value = state.seed; + + u.uFlare.value = c.veilFlare; + u.uBillow.value = c.veilBillow * g.noiseStrength; + u.uScale.value = c.veilScale * g.noiseFrequency; + u.uStretch.value = c.veilStretch; + u.uFlow.value = c.veilFlow * g.noiseSpeed; + u.uErode.value = c.veilErode; + u.uFalloff.value = c.veilFalloff; + u.uSoftFade.value = c.veilSoftFade; + u.uOpacity.value = c.veil * g.opacity; + u.uColorVeil.value.copy(getColor(c.colorVeil)); + u.uColorCrest.value.copy(getColor(c.colorVeilCrest)); + }; + + return material; +} diff --git a/src/skillshot/materials/GlacierMaterial.js b/src/skillshot/materials/GlacierMaterial.js new file mode 100644 index 0000000..a612288 --- /dev/null +++ b/src/skillshot/materials/GlacierMaterial.js @@ -0,0 +1,351 @@ +import { ShaderMaterial, Color, Vector3, DoubleSide, NormalBlending } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +const GLACIER_VERTEX = /* glsl */ ` + attribute float aSeed; + attribute float aBirth; + attribute float aGrow; + attribute float aShatter; + + varying vec3 vLocal; + varying vec3 vWorld; + varying vec3 vNormalW; + varying vec3 vView; + varying vec3 vAxis; + varying float vSeed; + varying float vBirth; + varying float vGrow; + varying float vShatter; + + void main() { + vLocal = position; + vSeed = aSeed; + vBirth = aBirth; + vGrow = aGrow; + vShatter = aShatter; + + vec3 objectNormal = normal; + vec3 objectAxis = vec3(0.0, 1.0, 0.0); + vec4 world; + + #ifdef USE_INSTANCING + // A blade is scaled (radius, height, radius), which is anisotropic — the + // instance matrix would tip every normal toward the long axis. Dividing by + // the squared column lengths is the inverse-transpose three itself uses. + mat3 im = mat3(instanceMatrix); + vec3 invScale = vec3(dot(im[0], im[0]), dot(im[1], im[1]), dot(im[2], im[2])); + objectNormal = im * (objectNormal / invScale); + objectAxis = im * (objectAxis / invScale); + world = modelMatrix * instanceMatrix * vec4(position, 1.0); + #else + world = modelMatrix * vec4(position, 1.0); + #endif + + vWorld = world.xyz; + vNormalW = normalize(mat3(modelMatrix) * objectNormal); + vAxis = normalize(mat3(modelMatrix) * objectAxis); + vView = cameraPosition - world.xyz; + + gl_Position = projectionMatrix * viewMatrix * world; + } +`; + +/** + * Not ice you look *at* — ice you look *through*, lit from inside. + * + * The Frost Lance's crystals are quarried stone: a patched standard material, + * milky with feather frost and rime, tinted deeper the thicker the body gets. If + * this ability borrowed that treatment it would read as the same spell twice, so + * the Glacial Crown's blades are shaded from scratch and from the opposite end. + * Almost nothing is diffuse. What you see is: + * + * - **a dispersive edge.** The fresnel term is evaluated *three times*, at + * three slightly different exponents, and the results drive red, green and + * blue independently — so every silhouette carries a genuine chromatic + * fringe, cyan on one lip and violet on the other, the way a real prism + * splits a highlight. `dispersion` is how far the three come apart. + * - **light piped up the blade.** Emission gathers on the faces the axis runs + * *along* rather than the ones facing you, and climbs toward the point + * (`tipBias`) with a slow band travelling up it (`bands`, `pulseSpeed`), + * ending in an incandescent tip. That is what makes a blade read as a fibre + * of frozen light rather than as a lump. + * - **striations, not frost.** Ridged noise stretched hard along the blade's + * own axis, so the interior shows flow lines running its length instead of + * the Lance's milky clouding. + * - **a real reflection.** One equirect sample of the stage's HDR probe off + * the facet, plus a tight sun lobe, so the facets actually catch the room + * they are standing in. + * + * On top of that sit the ability's two signatures, the reason this is a material + * and not a colour swap: + * + * - **the freeze front.** `aGrow` is a per-instance height, 0 at the floor and + * 1 at the point, and everything above it is discarded against a noisy cut, + * so a blade *crystallises upward* out of the ground rather than sliding out + * of a hole. The travelling lip is lit (`frontGlow`). + * - **the shatter.** `aShatter` is a threshold against a per-fragment chunk id + * — half a voronoi cell in the blade's own space, half a hash of its flat + * face normal — so a dying blade loses whole plates and wedges one at a + * time, each break edge flaring as it goes. + * + * Per-instance inputs arrive as instanced attributes (`aSeed`, `aBirth`, + * `aGrow`, `aShatter`), which is why this material is only ever used on an + * InstancedMesh. The geometry is non-indexed with per-face normals, so the + * facets come out crisp without a `flatShading` flag to ask for it. + */ +const GLACIER_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform sampler2D uEnvMap; + uniform vec3 uSunDir; + + uniform vec3 uColorGlass; + uniform vec3 uColorEdge; + uniform vec3 uColorPrismA; + uniform vec3 uColorPrismB; + uniform vec3 uColorCore; + uniform vec3 uColorTip; + + uniform float uBody; + uniform float uEdgePower; + uniform float uEdgeGain; + uniform float uDispersion; + uniform float uPipe; + uniform float uTipBias; + uniform float uBands; + uniform float uPulseSpeed; + uniform float uTipStart; + uniform float uTipGlow; + uniform float uStria; + uniform float uStriaScale; + uniform float uEnvIntensity; + uniform float uSpecular; + uniform float uBirthGlow; + + uniform float uFrontRough; + uniform float uFrontWidth; + uniform float uFrontGlow; + uniform float uShatterScale; + uniform float uShatterEdge; + uniform float uShatterGlow; + + uniform float uGlow; + uniform float uOpacity; + uniform float uGlobalGlow; + + varying vec3 vLocal; + varying vec3 vWorld; + varying vec3 vNormalW; + varying vec3 vView; + varying vec3 vAxis; + varying float vSeed; + varying float vBirth; + varying float vGrow; + varying float vShatter; + + ${noiseGLSL} + + #define TAU 6.28318530718 + + vec2 equirectUv(vec3 dir) { + return vec2(atan(dir.z, dir.x) * 0.15915494 + 0.5, + asin(clamp(dir.y, -1.0, 1.0)) * 0.31830989 + 0.5); + } + + void main() { + vec3 N = normalize(vNormalW); + vec3 V = normalize(vView); + // Double-sided: the far wall of a blade is half of what you see through the + // near one, and its normal has to be flipped or it shades inside out. + if (dot(N, V) < 0.0) N = -N; + + float ndv = clamp(dot(N, V), 0.0, 1.0); + float h = clamp(vLocal.y, 0.0, 1.0); + + /* ---- the freeze front: everything above it does not exist yet ---- */ + // The roughness is added back on before the noise is taken off again, so a + // fully grown blade (aGrow = 1) can never cut into itself. + float ragged = snoise01(vLocal * 5.0 + vSeed * 7.0) * uFrontRough; + float front = vGrow * (1.0 + uFrontRough) - ragged; + if (h > front) discard; + float freezing = smoothstep(front - uFrontWidth, front, h) * + (1.0 - smoothstep(0.985, 1.0, vGrow)); + + /* ---- the shatter: plates and wedges leaving one at a time ---- */ + // Two structures averaged. The voronoi half breaks the body into chunks that + // ignore the geometry; the facet half takes whole flat faces away, which is + // what actually happens to a crystal. + float breaking = 0.0; + if (vShatter > 0.001) { + vec2 cell = voronoi2(vLocal.xz * uShatterScale + vLocal.y * 4.0 + vSeed * 13.0); + float facet = hash13(floor(N * 6.0) + vSeed * 3.0); + float chunk = 0.55 * cell.y + 0.45 * facet; + if (chunk < vShatter) discard; + breaking = smoothstep(vShatter + uShatterEdge, vShatter, chunk); + } + + /* ---- the dispersive edge ---- */ + vec3 fres = pow( + vec3(1.0 - ndv), + vec3(uEdgePower * (1.0 - uDispersion * 0.4), uEdgePower, uEdgePower * (1.0 + uDispersion * 0.55)) + ); + // Each facet takes its own place in the split, so neighbouring faces on one + // blade do not all fringe the same colour. + vec3 prism = mix(uColorPrismA, uColorPrismB, hash13(floor(N * 7.0) + vSeed * 5.0)); + + /* ---- light piped up the blade to the point ---- */ + float along = 1.0 - abs(dot(N, vAxis)); // the long faces, not the caps + float pipe = along * pow(h, uTipBias); + float band = 0.5 + 0.5 * sin((h * uBands - uTime * uPulseSpeed) * TAU + vSeed * 5.0); + pipe *= mix(0.6, 1.0, band * band); + float tip = smoothstep(uTipStart, 1.0, h); + + /* ---- flow lines running its length ---- */ + float stria = ridged(vec3(vLocal.xz * uStriaScale, vLocal.y * uStriaScale * 0.3 + vSeed * 9.0), 4); + stria = smoothstep(0.7, 0.97, stria) * along; + + /* ---- the room it is standing in ---- */ + vec3 refl = reflect(-V, N); + vec3 env = texture2D(uEnvMap, equirectUv(refl)).rgb * uEnvIntensity; + float sun = pow(max(dot(refl, -uSunDir), 0.0), 90.0) * uSpecular; + + vec3 color = uColorGlass * uBody * (0.25 + 0.75 * ndv); + color += prism * fres * uEdgeGain; + color += uColorEdge * fres.g * fres.g * uEdgeGain * 0.5; + color += uColorCore * pipe * uPipe; + color += uColorEdge * stria * uStria; + color += uColorTip * tip * uTipGlow; + color += env * (0.35 + 0.65 * fres.g) + uColorEdge * sun; + color += uColorEdge * freezing * uFrontGlow; + color += uColorCore * breaking * uShatterGlow; + color += uColorCore * vBirth * uBirthGlow; + color *= uGlow; + + // Soft ceiling. Every term peaks at a grazing angle and they stack; a + // Reinhard rolloff leaves anything under ~1 alone and asymptotes at 1/0.16 ≈ + // 6, so the sliders keep biting at the top of their range without flattening + // the blade into a white cutout. + color /= 1.0 + color * 0.16; + + // Glass: thin and clear through the body, solid at the silhouette, the tip + // and any edge that is currently freezing or breaking. + float alpha = uBody * 0.4 + fres.g * 1.25 + pipe * 0.45 + stria * 0.3 + + tip * 0.55 + freezing + breaking; + alpha = clamp(alpha, 0.0, 1.0) * uOpacity; + if (alpha < 0.004) discard; + + gl_FragColor = vec4(color * uGlobalGlow, alpha); + } +`; + +/** + * The blades of a Glacial Crown. One material for every shard in the cast; the + * per-shard state rides in on instanced attributes. + */ +export function createGlacierMaterial() { + const material = new ShaderMaterial({ + transparent: true, + // Kept on: the crown is a wall you look into, and depth writes are what stop + // it sorting through itself and let the mist and snow fade against it. + depthWrite: true, + depthTest: true, + blending: NormalBlending, + side: DoubleSide, + toneMapped: false, + uniforms: { + uTime: frame.uTime, + uEnvMap: frame.uEnvMap, + uGlobalGlow: frame.uGlobalGlow, + uSunDir: { value: new Vector3(0, -1, 0) }, + + uColorGlass: { value: new Color() }, + uColorEdge: { value: new Color() }, + uColorPrismA: { value: new Color() }, + uColorPrismB: { value: new Color() }, + uColorCore: { value: new Color() }, + uColorTip: { value: new Color() }, + + uBody: { value: 0.55 }, + uEdgePower: { value: 2.2 }, + uEdgeGain: { value: 1.6 }, + uDispersion: { value: 0.6 }, + uPipe: { value: 1.5 }, + uTipBias: { value: 1.6 }, + uBands: { value: 1.4 }, + uPulseSpeed: { value: 0.6 }, + uTipStart: { value: 0.62 }, + uTipGlow: { value: 1.4 }, + uStria: { value: 0.7 }, + uStriaScale: { value: 6.0 }, + uEnvIntensity: { value: 0.6 }, + uSpecular: { value: 2.0 }, + uBirthGlow: { value: 2.2 }, + + uFrontRough: { value: 0.35 }, + uFrontWidth: { value: 0.12 }, + uFrontGlow: { value: 2.6 }, + uShatterScale: { value: 7.0 }, + uShatterEdge: { value: 0.08 }, + uShatterGlow: { value: 3.0 }, + + uGlow: { value: 1.0 }, + uOpacity: { value: 1.0 } + }, + vertexShader: GLACIER_VERTEX, + fragmentShader: GLACIER_FRAGMENT + }); + + const u = material.uniforms; + + /** Pull the palette and every shading control from the live settings. */ + material.userData.sync = () => { + const c = settings.glacier; + const g = settings.global; + const env = settings.environment; + + u.uColorGlass.value.copy(getColor(c.colorGlass)); + u.uColorEdge.value.copy(getColor(c.colorEdge)); + u.uColorPrismA.value.copy(getColor(c.colorPrismA)); + u.uColorPrismB.value.copy(getColor(c.colorPrismB)); + u.uColorCore.value.copy(getColor(c.colorCore)); + u.uColorTip.value.copy(getColor(c.colorTip)); + + u.uBody.value = c.body; + u.uEdgePower.value = c.edgePower; + u.uEdgeGain.value = c.edgeGain * g.fresnel; + u.uDispersion.value = c.dispersion; + u.uPipe.value = c.pipe * g.shaderIntensity; + u.uTipBias.value = c.tipBias; + u.uBands.value = c.bands; + u.uPulseSpeed.value = c.pulseSpeed * g.noiseSpeed; + u.uTipStart.value = c.tipStart; + u.uTipGlow.value = c.tipGlow; + u.uStria.value = c.stria * g.shaderIntensity; + u.uStriaScale.value = c.striaScale * g.noiseFrequency; + u.uEnvIntensity.value = c.envIntensity; + u.uSpecular.value = c.specular; + u.uBirthGlow.value = c.birthGlow; + + u.uFrontRough.value = c.frontRough * g.noiseStrength; + u.uFrontWidth.value = c.frontWidth; + u.uFrontGlow.value = c.frontGlow; + u.uShatterScale.value = c.shatterScale * g.noiseFrequency; + u.uShatterEdge.value = c.shatterEdge; + u.uShatterGlow.value = c.shatterGlow; + + u.uGlow.value = c.glow * g.glow; + u.uOpacity.value = c.opacity * g.opacity; + + // The sun the stage is actually lit by, so the glint sits where the rest of + // the scene's highlights do. + const cosE = Math.cos(env.sunElevation); + u.uSunDir.value + .set(-Math.cos(env.sunAzimuth) * cosE, -Math.sin(env.sunElevation), -Math.sin(env.sunAzimuth) * cosE) + .normalize(); + }; + + material.userData.sync(); + return material; +} diff --git a/src/skillshot/materials/IceMaterial.js b/src/skillshot/materials/IceMaterial.js new file mode 100644 index 0000000..b8c7585 --- /dev/null +++ b/src/skillshot/materials/IceMaterial.js @@ -0,0 +1,237 @@ +import { MeshStandardMaterial, Color, DoubleSide } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/** + * Procedurally shaded ice. + * + * Built on MeshStandardMaterial rather than a raw ShaderMaterial so the crystals + * cast and receive the stage's real shadows and pick up the HDR probe — the + * stylisation is injected on top: + * + * - **thickness tint** — a facet seen head-on has the longest path through the + * crystal, so it darkens toward `colorDeep`; grazing edges stay pale. This is + * the term that makes the field read as a solid you can see *into* rather + * than as blue plastic. + * - **internal fracture** — ridged noise sampled in *world* space, so the crack + * planes stay a fixed physical size whether a spike is ankle-high or three + * metres tall, and neighbouring crystals look quarried from the same block. + * - **feather frost & rime** — fbm sampled in *local* space (0..1 up the + * crystal), so the milky veining and the frost creeping up from the base + * follow each spike's own axis however it is scaled or leaned. + * - **sparkle** — a hard-thresholded high-frequency field scrolling in world + * space, biased toward grazing angles, which is where real ice glints. + * - **birth flash** — a per-instance value the ability drives from 1 to 0 over + * `birthFade`, so a crystal is lit from within for the moment it erupts. + * + * Per-instance inputs arrive as instanced attributes (`aSeed`, `aBirth`), which + * is why this material is only ever used on an InstancedMesh. + */ +export function createIceMaterial(environment) { + const material = new MeshStandardMaterial({ + color: 0xffffff, + roughness: 0.16, + metalness: 0.0, + flatShading: true, + transparent: true, + // Ice is translucent, so the far wall of a crystal is part of what you see + // through the near one. Culling it left the interior empty and the spikes + // read as hollow shells wherever the body was thin. + side: DoubleSide, + // Kept on: the crystals are near-opaque, and writing depth is what stops the + // field from sorting through itself and lets the mist fade against it. + depthWrite: true + }); + + const uniforms = { + uTime: frame.uTime, + uColorDeep: { value: new Color() }, + uColorIce: { value: new Color() }, + uColorRim: { value: new Color() }, + uColorCore: { value: new Color() }, + uDensity: { value: 1.15 }, + uFresnel: { value: 2.3 }, + uFresnelPower: { value: 2.4 }, + uTranslucency: { value: 1.5 }, + uFacetSharp: { value: 0.68 }, + uFracture: { value: 0.62 }, + uFractureScale: { value: 6.5 }, + uVeins: { value: 0.45 }, + uVeinScale: { value: 3.2 }, + uSparkle: { value: 1.6 }, + uSparkleScale: { value: 34 }, + uSparkleSpeed: { value: 0.7 }, + uFrostLine: { value: 0.5 }, + uGlow: { value: 1.35 }, + uEdgeGlow: { value: 2.0 }, + uBirthGlow: { value: 3.2 } + }; + + environment.registerShadowCasterWithPatch(material, (shader) => { + Object.assign(shader.uniforms, uniforms); + + shader.vertexShader = shader.vertexShader + .replace( + '#include ', + `#include + attribute float aSeed; + attribute float aBirth; + varying vec3 vIceLocal; + varying vec3 vIceWorld; + varying float vIceSeed; + varying float vIceBirth;` + ) + .replace( + '#include ', + `#include + vIceLocal = transformed; + vIceSeed = aSeed; + vIceBirth = aBirth; + #ifdef USE_INSTANCING + vIceWorld = (modelMatrix * instanceMatrix * vec4(transformed, 1.0)).xyz; + #else + vIceWorld = (modelMatrix * vec4(transformed, 1.0)).xyz; + #endif` + ); + + shader.fragmentShader = shader.fragmentShader + .replace( + '#include ', + `#include + uniform float uTime; + uniform vec3 uColorDeep; + uniform vec3 uColorIce; + uniform vec3 uColorRim; + uniform vec3 uColorCore; + uniform float uDensity; + uniform float uFresnel; + uniform float uFresnelPower; + uniform float uTranslucency; + uniform float uFacetSharp; + uniform float uFracture; + uniform float uFractureScale; + uniform float uVeins; + uniform float uVeinScale; + uniform float uSparkle; + uniform float uSparkleScale; + uniform float uSparkleSpeed; + uniform float uFrostLine; + uniform float uGlow; + uniform float uEdgeGlow; + uniform float uBirthGlow; + varying vec3 vIceLocal; + varying vec3 vIceWorld; + varying float vIceSeed; + varying float vIceBirth; + ${noiseGLSL}` + ) + // Injected once the normal is resolved: with `flatShading` there is no + // `vNormal` varying, so every view-dependent term here has to read the + // face normal that derives from derivatives. + .replace( + '#include ', + `#include + { + vec3 N = normalize(normal); + float ndv = clamp(dot(N, normalize(vViewPosition)), 0.0, 1.0); + + // Facing a facet you look down the long axis of the crystal; at a + // grazing angle you are only clipping its edge. + float thickness = clamp(ndv * uDensity, 0.0, 1.0); + float fres = pow(1.0 - ndv, uFresnelPower) * uFresnel; + + // World space: crack planes keep a fixed physical scale, so a small + // shard and a tall spike look cut from the same glacier. + vec3 fp = vIceWorld * uFractureScale + vIceSeed * 37.0; + float cracks = smoothstep(0.55, 0.98, ridged(fp, 4)); + + // Local space: veining follows each crystal's own axis. + float veins = fbm3(vIceLocal * uVeinScale * 4.0 + vIceSeed * 11.0) * 0.5 + 0.5; + veins = smoothstep(0.45, 0.92, veins); + + vec3 body = mix(uColorIce, uColorDeep, thickness); + body = mix(body, uColorRim, veins * uVeins * 0.55); + body = mix(body, uColorRim, cracks * uFracture * 0.4); + + // Rime gathers where the crystal left the floor. + float rime = smoothstep(0.55, 0.0, vIceLocal.y) * + (0.5 + 0.5 * fbm3(vIceLocal * 9.0 + vIceSeed * 5.0)); + body = mix(body, uColorRim, clamp(rime, 0.0, 1.0) * uFrostLine); + + // Lift the facets that point at the camera so the silhouette reads as + // a bundle of planes rather than one smooth mass. + body *= mix(1.0, 0.55 + 0.9 * ndv, uFacetSharp); + + // Pinpoint glints, biased to the grazing angles where ice catches. + float sp = snoise(vIceWorld * uSparkleScale + + vec3(0.0, uTime * uSparkleSpeed, 0.0) + vIceSeed * 23.0); + sp = pow(clamp(sp, 0.0, 1.0), 14.0) * smoothstep(0.0, 0.7, fres + 0.3); + + diffuseColor.rgb *= body; + + // The rim *glow* rides a normalised 0..1 fresnel, not fres. fres + // carries uFresnel (2.3 by default) because it also drives opacity, + // and folding that gain into the emissive too was multiplying the + // silhouette up past 4x white before any of the other terms landed. + float rimAmount = pow(1.0 - ndv, uFresnelPower); + + vec3 glow = uColorRim * rimAmount * uEdgeGlow; + glow += uColorCore * (cracks * uFracture * 0.8 + veins * uVeins * 0.35) * uTranslucency; + glow += uColorRim * sp * uSparkle * 1.5; + glow += uColorCore * vIceBirth * uBirthGlow; + glow *= uGlow; + + // Soft ceiling. The terms above are independent and all peak at a + // grazing angle, so they stack: without this a facet on the silhouette + // sums to well over 10 and the crystal reads as a white blob with the + // bloom pass smeared across it. A Reinhard rolloff leaves everything + // under ~1 essentially untouched and asymptotes at 1/0.22 ≈ 4.5, so + // the sliders still bite at the top of their range without blowing out. + glow /= 1.0 + glow * 0.22; + + totalEmissiveRadiance += glow; + + // Thin at the edges, denser through the body and along the cracks. + diffuseColor.a = clamp(diffuseColor.a * (0.62 + 0.5 * fres) + cracks * 0.12, 0.0, 1.0); + }` + ); + }); + + material.userData.uniforms = uniforms; + + /** Pull the palette and every shading control from the live settings. */ + material.userData.sync = () => { + const c = settings.ice; + const g = settings.global; + + uniforms.uColorDeep.value.copy(getColor(c.colorDeep)); + uniforms.uColorIce.value.copy(getColor(c.colorIce)); + uniforms.uColorRim.value.copy(getColor(c.colorRim)); + uniforms.uColorCore.value.copy(getColor(c.colorCore)); + + uniforms.uDensity.value = c.depthTint; + uniforms.uFresnel.value = c.fresnel * g.fresnel; + uniforms.uFresnelPower.value = c.fresnelPower; + uniforms.uTranslucency.value = c.translucency; + uniforms.uFacetSharp.value = c.facetSharp; + uniforms.uFracture.value = c.fracture * g.shaderIntensity; + uniforms.uFractureScale.value = c.fractureScale * g.noiseFrequency; + uniforms.uVeins.value = c.veins * g.shaderIntensity; + uniforms.uVeinScale.value = c.veinScale * g.noiseFrequency; + uniforms.uSparkle.value = c.glint * g.shaderIntensity; + uniforms.uSparkleScale.value = c.glintScale; + uniforms.uSparkleSpeed.value = c.glintSpeed * g.noiseSpeed; + uniforms.uFrostLine.value = c.frostLine; + uniforms.uGlow.value = c.glow * g.glow; + uniforms.uEdgeGlow.value = c.edgeGlow; + uniforms.uBirthGlow.value = c.birthGlow; + + material.opacity = c.opacity * g.opacity; + material.envMapIntensity = c.envIntensity; + }; + + material.userData.sync(); + return material; +} diff --git a/src/skillshot/materials/LightningMaterial.js b/src/skillshot/materials/LightningMaterial.js new file mode 100644 index 0000000..1ca5dce --- /dev/null +++ b/src/skillshot/materials/LightningMaterial.js @@ -0,0 +1,415 @@ +import { ShaderMaterial, AdditiveBlending, Color, DoubleSide, Vector3 } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/** + * The two passes a bolt is drawn in. Same geometry, same path, same uniforms — + * only the ribbon width and the cross-ribbon profile differ. + */ +export const BoltPass = Object.freeze({ + CORE: 0, // the hot filament itself + GLOW: 1 // the wide halo the filament sits inside +}); + +/** + * The whole bolt lives in this vertex shader. + * + * A vertex arrives as `(t, side)` — how far along the bolt it is and which edge + * of the ribbon it is on — and leaves as a world position. Everything in + * between is derived from `settings.thunder`, so there is no path on the CPU to + * go stale: dragging `jitter` re-kinks a bolt that is already in the air, and + * dragging `spread` re-fans it, both with the clock stopped. + * + * Three things stack to make the shape: + * + * 1. **the axis** — a straight line from the hand to the impact point, bowed + * by `sag`. This is the only part that knows where the cast is pointing. + * 2. **the fan** — a constant per-filament offset in the plane perpendicular + * to the axis, opening from `spreadNear` at the hand to `spread` at the + * target and rolling around the axis with `twist`. This is what separates + * one filament from the next. + * 3. **the kinks** — octaves of *linearly* interpolated value noise. Linear + * on purpose: smoothstep would round the corners off, and the corners are + * the entire reason it reads as lightning rather than as a wobbly tube. + * + * The ribbon is then turned to face the camera by crossing the local tangent + * with the view vector, which is why the bolt keeps its apparent thickness from + * any angle without ever being a screen-space line. + */ +const BOLT_VERTEX = /* glsl */ ` + #define PI 3.141592653589793 + #define TAU 6.283185307179586 + + uniform float uTime; + uniform vec3 uOrigin; + uniform vec3 uTarget; + uniform vec3 uSide; + uniform float uSag; + uniform float uSeed; + uniform float uRestrike; + + uniform float uStrands; + uniform float uSpread; + uniform float uSpreadNear; + uniform float uSpreadCurve; + uniform float uTwist; + uniform float uTwistSpeed; + + uniform float uJitter; + uniform float uJitterScale; + uniform float uOctaves; + uniform float uJitterFalloff; + uniform float uCrawl; + uniform float uPinch; + uniform float uConverge; + + uniform float uWidth; + uniform float uWidthTip; + uniform float uWidthCurve; + uniform float uCoreWidth; + uniform float uWidthScale; + uniform float uStrandFlash; + uniform float uFlickerSpeed; + uniform float uFade; + + attribute float aStrand; + + varying float vT; + varying float vSide; + varying float vStrand; + varying float vFlash; + varying float vViewZ; + + ${noiseGLSL} + + /** Value noise with a *linear* ramp — piecewise-linear output, sharp corners. */ + float vnoise(float x, float seed) { + float i = floor(x); + float f = x - i; + return mix(hash11(i + seed), hash11(i + 1.0 + seed), f) * 2.0 - 1.0; + } + + /** + * Offset of one filament from the axis, in the perpendicular plane. + * span is the length of the cast, so uJitterScale stays kinks per *metre* + * however far the bolt reaches. + */ + vec2 kink(float t, float seed, float span) { + vec2 o = vec2(0.0); + float amp = 1.0; + float freq = max(uJitterScale, 0.01) * span; + float scroll = uTime * uCrawl; + + // Fixed trip count with a per-octave gate: a dynamic bound is not portable, + // and five multiply-adds are cheaper than the branch would be anyway. + for (int i = 0; i < 5; i++) { + float on = step(float(i), uOctaves - 1.0); + o.x += on * amp * vnoise(t * freq + scroll, seed + 13.0 * float(i)); + o.y += on * amp * vnoise(t * freq + scroll * 1.17, seed + 71.3 + 13.0 * float(i)); + amp *= uJitterFalloff; + freq *= 2.0; + scroll *= 1.63; + } + return o; + } + + vec3 boltPoint(float t, float seed, float radial, vec3 n1, vec3 n2, float span) { + vec3 axis = mix(uOrigin, uTarget, t); + axis.y += uSag * sin(t * PI); + + // Pinned at the hand always, and at the target as hard as uConverge asks — + // a bolt that lands somewhere other than where it was aimed reads as a bug. + float pinch = max(uPinch, 1e-3); + float ends = smoothstep(0.0, pinch, t) * + mix(1.0, smoothstep(0.0, pinch, 1.0 - t), clamp(uConverge, 0.0, 1.0)); + + vec2 offset = kink(t, seed, span) * uJitter * ends; + + float angle = seed * TAU + (t * uTwist + uTime * uTwistSpeed) * TAU; + float reach = mix(uSpreadNear, uSpread, pow(clamp(t, 0.0, 1.0), max(uSpreadCurve, 0.01))); + offset += vec2(cos(angle), sin(angle)) * reach * radial; + + return axis + n1 * offset.x + n2 * offset.y; + } + + void main() { + float t = position.x; + float side = position.y; + vT = t; + vSide = side; + + /* ---- the frame the offsets live in ---- */ + vec3 delta = uTarget - uOrigin; + float span = max(length(delta), 0.01); + vec3 dir = delta / span; + // Gram-Schmidt rather than the raw lateral: the axis tilts downward from the + // hand, so uSide is only approximately perpendicular to it. + vec3 n1 = uSide - dir * dot(uSide, dir); + n1 = length(n1) > 1e-4 ? normalize(n1) : normalize(cross(dir, vec3(0.0, 1.0, 0.0))); + vec3 n2 = normalize(cross(dir, n1)); + + /* ---- which filament is this, and what shape is it wearing ---- */ + // The strike index snaps every filament onto a new shape uRestrike times a + // second; the crawl inside kink() slides it continuously in between. Both + // together are what stops a held bolt looking like a static ribbon. + float strike = floor(uTime * max(uRestrike, 0.01)); + float seed = hash11(aStrand * 7.13 + uSeed + strike * 3.77) * 97.0; + float radial = uStrands <= 1.0 ? 0.0 : aStrand / (uStrands - 1.0); + vStrand = radial; + + vec3 here = boltPoint(t, seed, radial, n1, n2, span); + + // Tangent by finite difference, mirrored at the far end so the last node + // still has a neighbour to look at. + float step_ = 0.02; + float ahead = t + step_; + float flip = 1.0; + if (ahead > 1.0) { ahead = t - step_; flip = -1.0; } + vec3 next = boltPoint(ahead, seed, radial, n1, n2, span); + vec3 tangent = (next - here) * flip; + tangent = length(tangent) > 1e-5 ? normalize(tangent) : dir; + + /* ---- turn the ribbon to face the camera ---- */ + vec3 toCamera = normalize(cameraPosition - here); + vec3 binormal = cross(tangent, toCamera); + float bl = length(binormal); + binormal = bl > 1e-4 ? binormal / bl : n1; + + /* ---- width ---- */ + // A stuttering per-filament blink, quantised to uFlickerSpeed so the whole + // bundle strobes on the same clock instead of shimmering independently. + float flash = mix(1.0, hash11(floor(uTime * uFlickerSpeed) + aStrand * 3.7 + uSeed), uStrandFlash); + vFlash = flash; + + float halfWidth = uWidth * uWidthScale; + halfWidth *= mix(1.0, uWidthTip, pow(clamp(t, 0.0, 1.0), max(uWidthCurve, 0.01))); + halfWidth *= mix(uCoreWidth, 1.0, radial); + halfWidth *= flash * uFade; + + // World space throughout: the ability's group is an identity transform, and + // going through modelMatrix would only invite it to drift. + vec4 mv = viewMatrix * vec4(here + binormal * side * halfWidth, 1.0); + vViewZ = mv.z; + gl_Position = projectionMatrix * mv; + } +`; + +const BOLT_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uSeed; + uniform float uProgress; + uniform float uTipGlow; + uniform float uTipLength; + uniform float uCoreSharp; + uniform float uGlowFalloff; + uniform float uBranchDim; + uniform float uFlicker; + uniform float uFlickerSpeed; + uniform float uPassOpacity; + uniform float uOpacity; + uniform float uGlow; + uniform float uFade; + uniform float uSoftFade; + uniform vec3 uColorCore; + uniform vec3 uColorInner; + uniform vec3 uColorOuter; + uniform vec3 uColorHalo; + + uniform float uGlobalGlow; + uniform vec2 uResolution; + uniform sampler2D uSceneDepth; + uniform float uCameraNear; + uniform float uCameraFar; + + varying float vT; + varying float vSide; + varying float vStrand; + varying float vFlash; + varying float vViewZ; + + ${noiseGLSL} + ${commonGLSL} + + void main() { + // Ahead of the strike front there is no bolt yet. The ribbon is drawn whole + // and clipped here rather than scaled, so the *shape* never changes as the + // front travels — only how much of it exists. + float tip = max(uTipLength, 1e-3); + float drawn = smoothstep(uProgress, uProgress - tip, vT); + if (drawn <= 0.002) discard; + + float v = clamp(abs(vSide), 0.0, 1.0); + + #ifdef BOLT_GLOW + float profile = pow(1.0 - v, max(uGlowFalloff, 0.05)); + vec3 color = mix(uColorHalo, uColorOuter, profile); + float alpha = profile; + #else + float profile = pow(1.0 - v, max(uCoreSharp, 0.05)); + vec3 color = mix(uColorOuter, uColorInner, smoothstep(0.0, 0.5, profile)); + color = mix(color, uColorCore, smoothstep(0.45, 1.0, profile)); + float alpha = profile; + #endif + + // The leading edge is where the air is actually breaking down. + color += uColorCore * smoothstep(uProgress - tip * 2.0, uProgress, vT) * uTipGlow; + + // Quantised, not sinusoidal: real lightning stutters between brightnesses, + // it does not breathe. + float flicker = 1.0 - uFlicker * hash11(floor(uTime * uFlickerSpeed) + uSeed); + + alpha *= drawn * flicker * vFlash * uFade * uPassOpacity * uOpacity; + alpha *= mix(1.0, clamp(uBranchDim, 0.0, 1.0), vStrand); + + vec2 screenUV = gl_FragCoord.xy / uResolution; + alpha *= softFade(uSceneDepth, screenUV, vViewZ, uCameraNear, uCameraFar, uSoftFade); + if (alpha < 0.003) discard; + + color *= uGlow * uGlobalGlow; + gl_FragColor = vec4(color, alpha); + } +`; + +/** + * One pass of a bolt. + * + * Both passes share every uniform except the two that define the pass itself + * (`uWidthScale`, `uPassOpacity`), so `userData.sync()` can be handed the same + * state for each and the editor drives them together. + * + * @param {number} pass BoltPass.* + */ +export function createLightningMaterial(pass = BoltPass.CORE) { + const glow = pass === BoltPass.GLOW; + + const material = new ShaderMaterial({ + defines: glow ? { BOLT_GLOW: '' } : {}, + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uOrigin: { value: new Vector3() }, + uTarget: { value: new Vector3(0, 0, 1) }, + uSide: { value: new Vector3(1, 0, 0) }, + uSag: { value: 0.2 }, + uSeed: { value: 0 }, + uRestrike: { value: 24 }, + uProgress: { value: 0 }, + uFade: { value: 1 }, + + uStrands: { value: 9 }, + uSpread: { value: 0.75 }, + uSpreadNear: { value: 0.05 }, + uSpreadCurve: { value: 1.6 }, + uTwist: { value: 0.45 }, + uTwistSpeed: { value: 0.8 }, + uBranchDim: { value: 0.72 }, + + uJitter: { value: 0.34 }, + uJitterScale: { value: 0.85 }, + uOctaves: { value: 4 }, + uJitterFalloff: { value: 0.55 }, + uCrawl: { value: 3.2 }, + uPinch: { value: 0.14 }, + uConverge: { value: 0.8 }, + + uWidth: { value: 0.085 }, + uWidthTip: { value: 0.5 }, + uWidthCurve: { value: 1 }, + uCoreWidth: { value: 2.1 }, + uCoreSharp: { value: 3.4 }, + uGlowFalloff: { value: 2.4 }, + uWidthScale: { value: glow ? 8 : 1 }, + uPassOpacity: { value: glow ? 0.32 : 1 }, + uSoftFade: { value: 0.5 }, + + uFlicker: { value: 0.3 }, + uFlickerSpeed: { value: 34 }, + uStrandFlash: { value: 0.5 }, + uTipGlow: { value: 2 }, + uTipLength: { value: 0.08 }, + + uOpacity: { value: 1 }, + uGlow: { value: 2.3 }, + uColorCore: { value: new Color(1, 1, 1) }, + uColorInner: { value: new Color(0.78, 0.92, 1) }, + uColorOuter: { value: new Color(0.22, 0.62, 1) }, + uColorHalo: { value: new Color(0.04, 0.24, 0.78) } + }), + vertexShader: BOLT_VERTEX, + fragmentShader: BOLT_FRAGMENT + }); + + /** + * Push the live settings and the current cast state into the uniforms. + * + * Called every frame — including on a zero-length frame while the sandbox is + * paused, which is what keeps every control below a live slider. + * + * @param {object} state { origin, target, side, progress, fade, seed, strands } + */ + material.userData.sync = (state) => { + const c = settings.thunder; + const g = settings.global; + const u = material.uniforms; + + u.uOrigin.value.copy(state.origin); + u.uTarget.value.copy(state.target); + u.uSide.value.copy(state.side); + u.uSeed.value = state.seed; + u.uProgress.value = state.progress; + u.uFade.value = state.fade; + u.uStrands.value = state.strands; + + u.uSag.value = c.sag; + u.uRestrike.value = c.restrike; + + u.uSpread.value = c.spread; + u.uSpreadNear.value = c.spreadNear; + u.uSpreadCurve.value = c.spreadCurve; + u.uTwist.value = c.twist; + u.uTwistSpeed.value = c.twistSpeed; + u.uBranchDim.value = c.branchDim; + + // The kinks are the one place `global.randomness` and the noise multipliers + // have anything to bite on, so they scale here rather than everywhere. + u.uJitter.value = c.jitter * g.randomness * g.noiseStrength; + u.uJitterScale.value = c.jitterScale * g.noiseFrequency; + u.uOctaves.value = Math.round(c.octaves); + u.uJitterFalloff.value = c.jitterFalloff; + u.uCrawl.value = c.crawl * g.noiseSpeed; + u.uPinch.value = c.pinch; + u.uConverge.value = c.converge; + + u.uWidth.value = c.width; + u.uWidthTip.value = c.widthTip; + u.uWidthCurve.value = c.widthCurve; + u.uCoreWidth.value = c.coreWidth; + u.uCoreSharp.value = c.coreSharp; + u.uGlowFalloff.value = c.glowFalloff; + u.uWidthScale.value = glow ? c.glowWidth : 1; + u.uPassOpacity.value = glow ? c.glowOpacity : 1; + u.uSoftFade.value = c.softFade; + + u.uFlicker.value = c.flicker; + u.uFlickerSpeed.value = c.flickerSpeed; + u.uStrandFlash.value = c.strandFlash; + u.uTipGlow.value = c.tipGlow; + u.uTipLength.value = c.tipLength; + + u.uOpacity.value = c.opacity * g.opacity; + u.uGlow.value = c.glow; + u.uColorCore.value.copy(getColor(c.colorCore)); + u.uColorInner.value.copy(getColor(c.colorInner)); + u.uColorOuter.value.copy(getColor(c.colorOuter)); + u.uColorHalo.value.copy(getColor(c.colorHalo)); + }; + + return material; +} diff --git a/src/skillshot/materials/MeteorMaterial.js b/src/skillshot/materials/MeteorMaterial.js new file mode 100644 index 0000000..a87f9eb --- /dev/null +++ b/src/skillshot/materials/MeteorMaterial.js @@ -0,0 +1,271 @@ +import { MeshStandardMaterial, Color, Vector3 } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { frame } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/** + * Burning rock — the meteor and the chunks it breaks into. + * + * Built on MeshStandardMaterial rather than a raw ShaderMaterial for the same + * reason the ice is: the rock is *solid*, so it has to cast and receive the + * stage's real shadows and pick up the HDR probe. The stylisation is injected on + * top: + * + * - **lava seams** — the signature. Cracks are the zero crossing of an fbm + * field: `1 - smoothstep(0, width, abs(fbm))` draws a thin, branching sheet + * wherever the noise changes sign, which is what a fracture actually looks + * like — meandering and forked, never a scratch. A second, finer octave + * adds the twigs that split off the main seams. + * - **the field is sampled in LOCAL space**, so the cracks are welded to the + * rock and tumble with it. Sampling in world space (as the ice fracture + * does, deliberately, so a whole field looks quarried from one block) would + * make the seams swim across a spinning meteor and instantly read as fake. + * - **soot** — the rock is charred in a halo either side of every seam, which + * is what keeps the glow from looking painted on. + * - **charge** — `uCharge` runs 0 → 1 as the meteor bears down on its target. + * It widens and brightens the seams, so the rock visibly heats up on the way + * in and the explosion is something you saw coming. + * - **leading-face heat** — the facets pointing along the direction of travel + * take the compression heat and go white; the trailing side stays dark. + * - **per-chunk cooling** — `aHeat` is driven from 1 to 0 per instance, so the + * debris thrown out of the impact dims as it lands. + * + * Per-instance inputs arrive as instanced attributes (`aSeed`, `aHeat`), so this + * material is only ever used on an InstancedMesh. + */ +export function createMeteorMaterial(environment) { + const material = new MeshStandardMaterial({ + color: 0xffffff, + roughness: 0.94, + metalness: 0.0, + // Faceted, like the crystals: it is what makes a low-poly rock read as rock + // rather than as a smooth ball with a texture on it. + flatShading: true + }); + + const uniforms = { + uTime: frame.uTime, + uColorRock: { value: new Color() }, + uColorChar: { value: new Color() }, + uColorCrack: { value: new Color() }, + uColorHot: { value: new Color() }, + uCrackScale: { value: 2.4 }, + uCrackWidth: { value: 0.1 }, + uCrackBranches: { value: 0.65 }, + uCrackGlow: { value: 3.2 }, + uFlow: { value: 0.7 }, + uFlowSpeed: { value: 0.9 }, + uRockScale: { value: 3.4 }, + uFacetTint: { value: 0.35 }, + uCavity: { value: 0.45 }, + uSoot: { value: 0.8 }, + uRimHeat: { value: 1.1 }, + uLead: { value: 1.6 }, + uLeadSharp: { value: 2.6 }, + /** Unit heading of the meteor, world space — drives the leading-face heat. */ + uHeading: { value: new Vector3(0, 0, 1) }, + uCharge: { value: 0 }, + uGlow: { value: 1 } + }; + + environment.registerShadowCasterWithPatch(material, (shader) => { + Object.assign(shader.uniforms, uniforms); + + shader.vertexShader = shader.vertexShader + .replace( + '#include ', + `#include + attribute float aSeed; + attribute float aHeat; + varying vec3 vRockLocal; + varying vec3 vRockNormalW; + varying float vRockSeed; + varying float vRockHeat;` + ) + // `objectNormal` is declared by , which runs before + // this chunk; three only ever takes it into *view* space, and the + // leading-face term needs it in world space. + .replace( + '#include ', + `#include + vRockLocal = transformed; + vRockSeed = aSeed; + vRockHeat = aHeat; + #ifdef USE_INSTANCING + vRockNormalW = normalize(mat3(modelMatrix) * (instanceMatrix * vec4(objectNormal, 0.0)).xyz); + #else + vRockNormalW = normalize(mat3(modelMatrix) * objectNormal); + #endif` + ); + + shader.fragmentShader = shader.fragmentShader + .replace( + '#include ', + `#include + uniform float uTime; + uniform vec3 uColorRock; + uniform vec3 uColorChar; + uniform vec3 uColorCrack; + uniform vec3 uColorHot; + uniform float uCrackScale; + uniform float uCrackWidth; + uniform float uCrackBranches; + uniform float uCrackGlow; + uniform float uFlow; + uniform float uFlowSpeed; + uniform float uRockScale; + uniform float uFacetTint; + uniform float uCavity; + uniform float uSoot; + uniform float uRimHeat; + uniform float uLead; + uniform float uLeadSharp; + uniform vec3 uHeading; + uniform float uCharge; + uniform float uGlow; + varying vec3 vRockLocal; + varying vec3 vRockNormalW; + varying float vRockSeed; + varying float vRockHeat; + ${noiseGLSL}` + ) + // Injected once the normal is resolved: with `flatShading` there is no + // `vNormal` varying, so the view-dependent terms have to read the face + // normal derives from screen-space derivatives. + .replace( + '#include ', + `#include + { + vec3 N = normalize(normal); + float ndv = clamp(dot(N, normalize(vViewPosition)), 0.0, 1.0); + float rim = pow(1.0 - ndv, 2.2); + + /* + * The seam, as a cross-section rather than as a line. + * + * "distance" is how far this fragment is from the nearest zero + * crossing of the crack field, and the three bands cut out of it are + * what make the crack read as a *split in stone* instead of a bright + * worm painted over it: + * + * fissure — the open gap. There is no rock here, so the albedo is + * killed and only the magma below shows. + * lip — a much wider charred band either side of it. A crack is + * a shadow first and a light second; without this the + * glow just sits on top of the surface. + * core — the middle of the gap, the only part hot enough to run + * white. + * + * Local space throughout, so the whole network is welded to the rock + * and tumbles with it. + */ + vec3 p = vRockLocal * uCrackScale + vRockSeed * 19.0; + float f1 = fbm3(p); + float f2 = fbm3(p * 2.7 + 11.3); + + // The charge prises the seams open as the meteor heats up. + float width = max(0.004, uCrackWidth * (1.0 + uCharge * 0.8)); + float distance = min(abs(f1), abs(f2) / max(uCrackBranches, 0.05)); + + float fissure = 1.0 - smoothstep(width * 0.35, width, distance); + float lip = 1.0 - smoothstep(width, width * 2.0, distance); + float core = 1.0 - smoothstep(0.0, width * 0.45, distance); + + // Magma is not static: brightness crawls along the inside of a seam. + float pulse = snoise(vRockLocal * 4.0 + vec3(0.0, uTime * uFlowSpeed, 0.0) + vRockSeed * 7.0); + float flow = mix(1.0, 0.45 + 0.75 * (pulse * 0.5 + 0.5), uFlow); + + /* --- the rock itself --- */ + float mottle = fbm3(vRockLocal * uRockScale + vRockSeed * 31.0) * 0.5 + 0.5; + vec3 rock = mix(uColorRock, uColorChar, smoothstep(0.3, 0.85, mottle)); + + // Per-facet value break-up. The geometric normal in *object* space is + // constant across a triangle, so hashing it gives every flat face its + // own shade — the thing that separates cut stone from a noise-painted + // ball, and it costs two derivatives. + vec3 faceN = normalize(cross(dFdx(vRockLocal), dFdy(vRockLocal))); + float facet = hash13(faceN * 37.0 + vRockSeed + 0.5); + rock *= 1.0 + (facet - 0.5) * uFacetTint; + + // Cheap curvature occlusion: craters and cut faces sit closer to the + // centre than the lumps do, so radius doubles as a cavity term. + float cavity = smoothstep(0.55, 1.0, length(vRockLocal)); + rock *= mix(1.0 - uCavity, 1.0, cavity); + + // Charred around every seam, and gone entirely inside one. + rock = mix(rock, uColorChar, lip * uSoot); + rock *= 1.0 - fissure * 0.92; + + // Hard lighting contrast across the facets, so the silhouette reads + // as a bundle of planes rather than one smooth mass. + rock *= mix(0.55, 1.15, ndv); + diffuseColor.rgb *= rock; + + /* --- what is burning --- */ + // The gap is the only thing that emits, and only its middle runs + // white. Everything else is rock. + float heat = fissure * flow * vRockHeat; + vec3 glow = mix(uColorCrack, uColorHot, core * core) * heat * uCrackGlow; + + // A sheath of heat around the silhouette, and the compression heat on + // the leading facets. Both are squared against the charge: at launch + // this is a cold rock with lit cracks, and it is only on the way down + // that the whole thing starts to burn. + float charge2 = uCharge * uCharge; + glow += uColorCrack * rim * uRimHeat * vRockHeat * charge2; + + float lead = pow(clamp(dot(normalize(vRockNormalW), uHeading), 0.0, 1.0), uLeadSharp); + glow += uColorHot * lead * uLead * vRockHeat * charge2; + + glow *= uGlow; + + // The same soft ceiling the ice uses: these terms are independent and + // stack, and without it a seam crossing the rim sums past 10 and the + // bloom pass smears the whole rock into a white blob. + glow /= 1.0 + glow * 0.22; + + totalEmissiveRadiance += glow; + }` + ); + }); + + material.userData.uniforms = uniforms; + + /** + * Pull the palette and every shading control from the live settings. + * @param {number} charge 0..1 — how far into its run-up the meteor is + * @param {THREE.Vector3} heading unit direction of travel, world space + */ + material.userData.sync = (charge = 0, heading = null) => { + const c = settings.meteor; + const g = settings.global; + + uniforms.uColorRock.value.copy(getColor(c.colorRock)); + uniforms.uColorChar.value.copy(getColor(c.colorChar)); + uniforms.uColorCrack.value.copy(getColor(c.colorCrack)); + uniforms.uColorHot.value.copy(getColor(c.colorHot)); + + uniforms.uCrackScale.value = c.crackScale * g.noiseFrequency; + uniforms.uCrackWidth.value = c.crackWidth; + uniforms.uCrackBranches.value = c.crackBranches; + uniforms.uCrackGlow.value = c.crackGlow * g.shaderIntensity; + uniforms.uFlow.value = c.crackFlow; + uniforms.uFlowSpeed.value = c.crackFlowSpeed * g.noiseSpeed; + uniforms.uRockScale.value = c.rockScale * g.noiseFrequency; + uniforms.uFacetTint.value = c.facetTint * g.randomness; + uniforms.uCavity.value = c.cavity; + uniforms.uSoot.value = c.soot; + uniforms.uRimHeat.value = c.rimHeat * g.fresnel; + uniforms.uLead.value = c.leadGlow; + uniforms.uLeadSharp.value = c.leadSharp; + uniforms.uCharge.value = charge; + uniforms.uGlow.value = c.glow * g.glow; + + if (heading) uniforms.uHeading.value.copy(heading); + material.envMapIntensity = c.envIntensity; + }; + + material.userData.sync(); + return material; +} diff --git a/src/skillshot/materials/SnareMaterial.js b/src/skillshot/materials/SnareMaterial.js new file mode 100644 index 0000000..58e3b9e --- /dev/null +++ b/src/skillshot/materials/SnareMaterial.js @@ -0,0 +1,774 @@ +import { ShaderMaterial, AdditiveBlending, Color, DoubleSide, Vector3 } from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/** + * The two passes the cage is drawn in. Same geometry, same paths, same + * uniforms — only the ribbon width and the cross-ribbon profile differ, exactly + * as with the bolt. + */ +export const SnarePass = Object.freeze({ + CORE: 0, // the hot filaments themselves + GLOW: 1 // the wide halo they sit inside +}); + +/** + * The four things a filament can be. + * + * A role is decided from the instance index against four live counts, which is + * what lets one instanced strip draw the whole trap: the whip that plants it, + * the pillar in the middle, the tendrils crawling out to the boundary and the + * arcs running around it are four *parametric paths*, not four meshes. Two draw + * calls cover all of them however many filaments are in the air, and setting a + * count to zero retires that role outright — which is how the leash disappears + * the instant the trap snaps open. + */ +export const SnareRole = Object.freeze({ + LEASH: 0, + COLUMN: 1, + TENDRIL: 2, + RIM: 3 +}); + +/** + * The whole cage lives in this vertex shader. + * + * A vertex arrives as `(t, side)` — how far along its filament it is and which + * edge of the ribbon it is on — and leaves as a world position. Nothing about + * any path exists on the CPU, so there is no record to go stale: dragging + * `zoneRadius` re-scales a trap that is already standing, dragging `height` + * re-grows the pillar, and both do it with the clock stopped. + * + * Three things stack, as they do on the bolt: + * + * 1. **the path** — the role's own centreline. A sagging line for the leash, + * a twisting climb for the column, a meander running outward for a + * tendril, an arc travelling around the circle for the rim. This is the + * only part that knows the trap's geometry. + * 2. **the frame** — a tangent by finite difference on that path, and two + * normals off it. Every offset below lives in this frame, which is what + * lets one kink function serve a vertical pillar and a filament crawling + * flat across the floor. + * 3. **the kinks** — octaves of *linearly* interpolated value noise. Linear + * on purpose: smoothstep would round the corners off, and the corners are + * the entire reason it reads as lightning rather than as a wobbly tube. + * + * The two ground-hugging roles damp the vertical component of that offset and + * clamp their result above the floor, because a kink with a free y is a + * filament that spends half its length buried. + */ +const CAGE_VERTEX = /* glsl */ ` + #define PI 3.141592653589793 + #define TAU 6.283185307179586 + + uniform float uTime; + uniform vec3 uCentre; // the trap's point on the floor + uniform vec3 uHand; // where the leash leaves the caster + uniform vec3 uFront; // the leash's travelling tip + uniform float uRadius; // footprint, metres — already eased by the snap + uniform float uHeight; // column height, metres + uniform float uSeed; + uniform float uRestrike; + uniform float uFade; + + uniform float uCountLeash; + uniform float uCountColumn; + uniform float uCountTendril; + uniform float uCountRim; + + uniform float uLeashSag; + uniform float uLeashSpread; + uniform float uLeashCling; + uniform float uLeashKink; + uniform float uLeashWidth; + + uniform float uHeightCurve; + uniform float uThroat; + uniform float uColumnSpread; + uniform float uColumnCurve; + uniform float uColumnFlare; + uniform float uColumnTwist; + uniform float uColumnSpin; + uniform float uColumnKink; + uniform float uColumnWidth; + uniform float uColumnTaper; + + uniform float uTendrilInner; + uniform float uTendrilReach; + uniform float uTendrilCurve; + uniform float uTendrilWander; + uniform float uTendrilArch; + uniform float uTendrilHug; + uniform float uTendrilSpin; + uniform float uTendrilKink; + uniform float uTendrilWidth; + uniform float uTendrilDim; + + uniform float uRimSpan; + uniform float uRimSpeed; + uniform float uRimHeight; + uniform float uRimJitter; + uniform float uRimKink; + uniform float uRimWidth; + uniform float uRimDim; + + uniform float uJitter; + uniform float uJitterScale; + uniform float uOctaves; + uniform float uJitterFalloff; + uniform float uCrawl; + uniform float uPinch; + + uniform float uWidth; + uniform float uWidthScale; + uniform float uStrandFlash; + uniform float uFlickerSpeed; + + attribute float aStrand; + + varying float vT; + varying float vSide; + varying float vDim; + varying float vFlash; + varying float vViewZ; + + ${noiseGLSL} + + /** Value noise with a *linear* ramp — piecewise-linear output, sharp corners. */ + float vnoise(float x, float seed) { + float i = floor(x); + float f = x - i; + return mix(hash11(i + seed), hash11(i + 1.0 + seed), f) * 2.0 - 1.0; + } + + /** + * Offset of one filament from its path, in the perpendicular plane. + * span is the length of that path, so uJitterScale stays kinks per *metre* + * whether the filament is a 20 m whip or a 1 m hop around the rim. + */ + vec2 kink(float t, float seed, float span) { + vec2 o = vec2(0.0); + float amp = 1.0; + float freq = max(uJitterScale, 0.01) * span; + float scroll = uTime * uCrawl; + + // Fixed trip count with a per-octave gate: a dynamic bound is not portable, + // and five multiply-adds are cheaper than the branch would be anyway. + for (int i = 0; i < 5; i++) { + float on = step(float(i), uOctaves - 1.0); + o.x += on * amp * vnoise(t * freq + scroll, seed + 13.0 * float(i)); + o.y += on * amp * vnoise(t * freq + scroll * 1.17, seed + 71.3 + 13.0 * float(i)); + amp *= uJitterFalloff; + freq *= 2.0; + scroll *= 1.63; + } + return o; + } + + /** The role's centreline at t, before any kink is added. */ + vec3 pathAt(float role, float f, float seed, float t) { + if (role < 0.5) { + /* ---- the leash: a line from the hand to the travelling tip ---- */ + vec3 p = mix(uHand, uFront, t); + p.y += uLeashSag * sin(t * PI); + // Negative sag drops the whip onto the floor, which is where a whip + // thrown at the ground belongs — but not through it. + p.y = max(p.y, uLeashCling); + return p; + } + + if (role < 1.5) { + /* ---- the column: a twisting climb out of the middle ---- */ + float a = f * TAU + (t * uColumnTwist + uTime * uColumnSpin) * TAU; + float r = uRadius * (mix(uThroat, uColumnSpread, pow(t, max(uColumnCurve, 0.01))) + + uColumnFlare * smoothstep(0.72, 1.0, t)); + vec3 p = uCentre + vec3(cos(a), 0.0, sin(a)) * r; + p.y = pow(t, max(uHeightCurve, 0.01)) * uHeight; + return p; + } + + if (role < 2.5) { + /* ---- a tendril: a meander running out to the boundary ---- */ + // The veer is a per-filament constant rather than noise so the tendril + // curves *consistently*, the way a discharge that has committed to a + // direction does; the kinks on top of it supply the rest. + float veer = (hash11(seed + 5.0) - 0.5) * 2.0 * uTendrilWander; + float a = f * TAU + uTime * uTendrilSpin * TAU + hash11(seed) * 0.4 + veer * pow(t, 1.4); + float r = uRadius * mix(uTendrilInner, uTendrilReach, pow(t, max(uTendrilCurve, 0.01))); + vec3 p = uCentre + vec3(cos(a), 0.0, sin(a)) * r; + p.y = uTendrilHug + uTendrilArch * sin(t * PI); + return p; + } + + /* ---- a rim arc: current hopping around the boundary ---- */ + float a = (f + uTime * uRimSpeed) * TAU + hash11(seed) * 0.3 + t * uRimSpan * TAU; + float r = uRadius * (1.0 + uRimJitter * 0.25 * sin(t * 6.0 + seed)); + vec3 p = uCentre + vec3(cos(a), 0.0, sin(a)) * r; + p.y = uTendrilHug + uRimHeight * sin(t * PI); + return p; + } + + void main() { + float t = position.x; + float side = position.y; + vT = t; + vSide = side; + + /* ---- which role is this filament wearing ---- */ + float b1 = uCountLeash; + float b2 = b1 + uCountColumn; + float b3 = b2 + uCountTendril; + + float role = 0.0; + float local = aStrand; + float count = max(uCountLeash, 1.0); + if (aStrand >= b3) { + role = 3.0; local = aStrand - b3; count = max(uCountRim, 1.0); + } else if (aStrand >= b2) { + role = 2.0; local = aStrand - b2; count = max(uCountTendril, 1.0); + } else if (aStrand >= b1) { + role = 1.0; local = aStrand - b1; count = max(uCountColumn, 1.0); + } + // Open interval on purpose: 0 and 1 would put two filaments on the same + // bearing for the roles that fan around a circle. + float f = local / count; + + // The strike index snaps every filament onto a new shape uRestrike times a + // second; the crawl inside kink() slides it continuously in between. + float strike = floor(uTime * max(uRestrike, 0.01)); + float seed = hash11(aStrand * 7.13 + uSeed + strike * 3.77) * 97.0; + + /* ---- per-role constants ---- */ + float amp = uLeashKink; + float widthMul = uLeashWidth; + float span = max(length(uFront - uHand), 0.01); + float dim = 1.0; + if (role > 2.5) { + amp = uRimKink; widthMul = uRimWidth; dim = uRimDim; + span = max(uRadius * uRimSpan * TAU, 0.01); + } else if (role > 1.5) { + amp = uTendrilKink; widthMul = uTendrilWidth; dim = uTendrilDim; + span = max(uRadius * max(uTendrilReach - uTendrilInner, 0.05), 0.01); + } else if (role > 0.5) { + amp = uColumnKink; widthMul = uColumnWidth; + span = max(uHeight, 0.01); + } + vDim = dim; + + /* ---- the frame the offsets live in ---- */ + float step_ = 0.02; + vec3 here = pathAt(role, f, seed, t); + vec3 behind = pathAt(role, f, seed, max(t - step_, 0.0)); + vec3 ahead = pathAt(role, f, seed, min(t + step_, 1.0)); + + vec3 tangent = ahead - behind; + tangent = length(tangent) > 1e-5 ? normalize(tangent) : vec3(0.0, 1.0, 0.0); + // The column runs vertically, so the usual world-up reference degenerates + // on exactly the role that needs the frame most. + vec3 upRef = abs(tangent.y) > 0.9 ? vec3(1.0, 0.0, 0.0) : vec3(0.0, 1.0, 0.0); + vec3 n1 = normalize(cross(tangent, upRef)); + vec3 n2 = normalize(cross(tangent, n1)); + + /* ---- the kinks, and the fan that separates the whip's filaments ---- */ + float pinch = max(uPinch, 1e-3); + float ends = smoothstep(0.0, pinch, t) * smoothstep(0.0, pinch, 1.0 - t); + + vec2 k = kink(t, seed, span) * amp * uJitter * ends; + if (role < 0.5) { + float fan = (f - 0.5) * 2.0 * uLeashSpread; + k += vec2(cos(seed), sin(seed)) * fan * ends; + } + + vec3 offset = n1 * k.x + n2 * k.y; + // Ground roles keep their kinks in the floor plane. A free y here buries + // half of every tendril and the effect reads as a broken dotted line. + if (role > 1.5) offset.y *= 0.3; + + vec3 world = here + offset; + vec3 nextWorld = ahead + offset; + if (role > 1.5) { + world.y = max(world.y, uTendrilHug * 0.4); + nextWorld.y = max(nextWorld.y, uTendrilHug * 0.4); + } + + /* ---- turn the ribbon to face the camera ---- */ + vec3 tan2 = nextWorld - world; + tan2 = length(tan2) > 1e-5 ? normalize(tan2) : tangent; + vec3 toCamera = normalize(cameraPosition - world); + vec3 binormal = cross(tan2, toCamera); + float bl = length(binormal); + binormal = bl > 1e-4 ? binormal / bl : n1; + + /* ---- width ---- */ + // A stuttering per-filament blink, quantised to uFlickerSpeed so the whole + // cage strobes on one clock instead of shimmering independently. + float flash = mix(1.0, hash11(floor(uTime * uFlickerSpeed) + aStrand * 3.7 + uSeed), uStrandFlash); + vFlash = flash; + + float halfWidth = uWidth * uWidthScale * widthMul; + // Every filament is an arc with two loose ends, so it tapers at both. + halfWidth *= mix(1.0, pow(sin(clamp(t, 0.0, 1.0) * PI), 0.35), 0.85); + if (role > 0.5 && role < 1.5) halfWidth *= mix(1.0, uColumnTaper, t); + halfWidth *= flash * uFade; + + // World space throughout: the ability's group is an identity transform, and + // going through modelMatrix would only invite it to drift. + vec4 mv = viewMatrix * vec4(world + binormal * side * halfWidth, 1.0); + vViewZ = mv.z; + gl_Position = projectionMatrix * mv; + } +`; + +const CAGE_FRAGMENT = /* glsl */ ` + #define PI 3.141592653589793 + + uniform float uTime; + uniform float uSeed; + uniform float uCoreSharp; + uniform float uGlowFalloff; + uniform float uFlicker; + uniform float uFlickerSpeed; + uniform float uPassOpacity; + uniform float uOpacity; + uniform float uGlow; + uniform float uFade; + uniform float uSoftFade; + uniform vec3 uColorCore; + uniform vec3 uColorInner; + uniform vec3 uColorOuter; + uniform vec3 uColorHalo; + + uniform float uGlobalGlow; + uniform vec2 uResolution; + uniform sampler2D uSceneDepth; + uniform float uCameraNear; + uniform float uCameraFar; + + varying float vT; + varying float vSide; + varying float vDim; + varying float vFlash; + varying float vViewZ; + + ${noiseGLSL} + ${commonGLSL} + + void main() { + float v = clamp(abs(vSide), 0.0, 1.0); + + #ifdef CAGE_GLOW + float profile = pow(1.0 - v, max(uGlowFalloff, 0.05)); + vec3 color = mix(uColorHalo, uColorOuter, profile); + float alpha = profile; + #else + float profile = pow(1.0 - v, max(uCoreSharp, 0.05)); + vec3 color = mix(uColorOuter, uColorInner, smoothstep(0.0, 0.5, profile)); + color = mix(color, uColorCore, smoothstep(0.45, 1.0, profile)); + float alpha = profile; + #endif + + // Quantised, not sinusoidal: real lightning stutters between brightnesses, + // it does not breathe. + float flicker = 1.0 - uFlicker * hash11(floor(uTime * uFlickerSpeed) + uSeed); + + // The loose ends fade as well as thin. A ribbon that only narrows leaves a + // hard dot at each tip, and every filament here has two of them. + alpha *= pow(sin(clamp(vT, 0.0, 1.0) * PI), 0.35); + alpha *= flicker * vFlash * vDim * uFade * uPassOpacity * uOpacity; + + vec2 screenUV = gl_FragCoord.xy / uResolution; + alpha *= softFade(uSceneDepth, screenUV, vViewZ, uCameraNear, uCameraFar, uSoftFade); + if (alpha < 0.003) discard; + + color *= uGlow * uGlobalGlow; + gl_FragColor = vec4(color, alpha); + } +`; + +/** + * One pass of the cage. + * + * Both passes share every uniform except the two that define the pass itself + * (`uWidthScale`, `uPassOpacity`), so `userData.sync()` can be handed the same + * state for each and the editor drives them together. + * + * @param {number} pass SnarePass.* + */ +export function createSnareCageMaterial(pass = SnarePass.CORE) { + const glow = pass === SnarePass.GLOW; + + const material = new ShaderMaterial({ + defines: glow ? { CAGE_GLOW: '' } : {}, + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uCentre: { value: new Vector3() }, + uHand: { value: new Vector3() }, + uFront: { value: new Vector3(0, 0, 1) }, + uRadius: { value: 4.4 }, + uHeight: { value: 6.2 }, + uSeed: { value: 0 }, + uRestrike: { value: 21 }, + uFade: { value: 1 }, + + uCountLeash: { value: 3 }, + uCountColumn: { value: 8 }, + uCountTendril: { value: 12 }, + uCountRim: { value: 7 }, + + uLeashSag: { value: -0.35 }, + uLeashSpread: { value: 0.22 }, + uLeashCling: { value: 0.12 }, + uLeashKink: { value: 0.3 }, + uLeashWidth: { value: 1 }, + + uHeightCurve: { value: 0.85 }, + uThroat: { value: 0.07 }, + uColumnSpread: { value: 0.17 }, + uColumnCurve: { value: 1.35 }, + uColumnFlare: { value: 0.12 }, + uColumnTwist: { value: 0.55 }, + uColumnSpin: { value: 0.4 }, + uColumnKink: { value: 0.24 }, + uColumnWidth: { value: 1.6 }, + uColumnTaper: { value: 0.55 }, + + uTendrilInner: { value: 0.06 }, + uTendrilReach: { value: 1 }, + uTendrilCurve: { value: 0.8 }, + uTendrilWander: { value: 0.9 }, + uTendrilArch: { value: 0.22 }, + uTendrilHug: { value: 0.05 }, + uTendrilSpin: { value: 0.05 }, + uTendrilKink: { value: 0.18 }, + uTendrilWidth: { value: 0.75 }, + uTendrilDim: { value: 0.8 }, + + uRimSpan: { value: 0.19 }, + uRimSpeed: { value: 0.28 }, + uRimHeight: { value: 0.35 }, + uRimJitter: { value: 0.16 }, + uRimKink: { value: 0.12 }, + uRimWidth: { value: 0.7 }, + uRimDim: { value: 0.9 }, + + uJitter: { value: 1 }, + uJitterScale: { value: 1.4 }, + uOctaves: { value: 4 }, + uJitterFalloff: { value: 0.55 }, + uCrawl: { value: 2.4 }, + uPinch: { value: 0.16 }, + + uWidth: { value: 0.032 }, + uWidthScale: { value: glow ? 6.2 : 1 }, + uPassOpacity: { value: glow ? 0.44 : 1 }, + uStrandFlash: { value: 0.45 }, + uFlickerSpeed: { value: 30 }, + uFlicker: { value: 0.26 }, + uCoreSharp: { value: 4.4 }, + uGlowFalloff: { value: 2.3 }, + uSoftFade: { value: 0.7 }, + + uOpacity: { value: 1 }, + uGlow: { value: 2.2 }, + uColorCore: { value: new Color(1, 1, 1) }, + uColorInner: { value: new Color(0.86, 0.82, 1) }, + uColorOuter: { value: new Color(0.56, 0.42, 1) }, + uColorHalo: { value: new Color(0.16, 0.05, 0.55) } + }), + vertexShader: CAGE_VERTEX, + fragmentShader: CAGE_FRAGMENT + }); + + /** + * Push the live settings and the current cast state into the uniforms. + * + * Called every frame — including on a zero-length frame while the sandbox is + * paused, which is what keeps every control below a live slider. + * + * @param {object} state + * { centre, hand, front, radius, height, fade, seed, counts } + */ + material.userData.sync = (state) => { + const c = settings.snare; + const g = settings.global; + const u = material.uniforms; + + u.uCentre.value.copy(state.centre); + u.uHand.value.copy(state.hand); + u.uFront.value.copy(state.front); + u.uRadius.value = state.radius; + u.uHeight.value = state.height; + u.uSeed.value = state.seed; + u.uFade.value = state.fade; + + u.uCountLeash.value = state.counts.leash; + u.uCountColumn.value = state.counts.column; + u.uCountTendril.value = state.counts.tendril; + u.uCountRim.value = state.counts.rim; + + u.uRestrike.value = c.restrike; + + u.uLeashSag.value = c.leashSag; + u.uLeashSpread.value = c.leashSpread; + u.uLeashCling.value = c.leashCling; + u.uLeashKink.value = c.leashKink; + u.uLeashWidth.value = c.leashWidth; + + u.uHeightCurve.value = c.heightCurve; + u.uThroat.value = c.throat; + u.uColumnSpread.value = c.columnSpread; + u.uColumnCurve.value = c.columnCurve; + u.uColumnFlare.value = c.columnFlare; + u.uColumnTwist.value = c.columnTwist; + u.uColumnSpin.value = c.columnSpin; + u.uColumnKink.value = c.columnKink; + u.uColumnWidth.value = c.columnWidth; + u.uColumnTaper.value = c.columnTaper; + + u.uTendrilInner.value = c.tendrilInner; + u.uTendrilReach.value = c.tendrilReach; + u.uTendrilCurve.value = c.tendrilCurve; + u.uTendrilWander.value = c.tendrilWander; + u.uTendrilArch.value = c.tendrilArch; + u.uTendrilHug.value = c.tendrilHug; + u.uTendrilSpin.value = c.tendrilSpin; + u.uTendrilKink.value = c.tendrilKink; + u.uTendrilWidth.value = c.tendrilWidth; + u.uTendrilDim.value = c.tendrilDim; + + u.uRimSpan.value = c.rimSpan; + u.uRimSpeed.value = c.rimSpeed; + u.uRimHeight.value = c.rimHeight; + u.uRimJitter.value = c.rimJitter; + u.uRimKink.value = c.rimKink; + u.uRimWidth.value = c.rimWidth; + u.uRimDim.value = c.rimDim; + + // The kinks are the one place `global.randomness` and the noise multipliers + // have anything to bite on, so they scale here rather than everywhere. + u.uJitter.value = c.jitter * g.randomness * g.noiseStrength; + u.uJitterScale.value = c.jitterScale * g.noiseFrequency; + u.uOctaves.value = Math.round(c.octaves); + u.uJitterFalloff.value = c.jitterFalloff; + u.uCrawl.value = c.crawl * g.noiseSpeed; + u.uPinch.value = c.pinch; + + u.uWidth.value = c.width; + u.uWidthScale.value = glow ? c.glowWidth : 1; + u.uPassOpacity.value = glow ? c.glowOpacity : 1; + u.uStrandFlash.value = c.strandFlash; + u.uFlickerSpeed.value = c.flickerSpeed; + u.uFlicker.value = c.flicker; + u.uCoreSharp.value = c.coreSharp; + u.uGlowFalloff.value = c.glowFalloff; + u.uSoftFade.value = c.softFade; + + u.uOpacity.value = c.opacity * g.opacity; + u.uGlow.value = c.glow; + u.uColorCore.value.copy(getColor(c.colorCore)); + u.uColorInner.value.copy(getColor(c.colorInner)); + u.uColorOuter.value.copy(getColor(c.colorOuter)); + u.uColorHalo.value.copy(getColor(c.colorHalo)); + }; + + return material; +} + +/* ==================================================================== */ +/* The field burnt into the floor */ +/* ==================================================================== */ + +const FIELD_VERTEX = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +/** + * The circle the indicator promised, burnt into the floor. + * + * Same signed-distance construction as the aim circle and the same thick band, + * because that correspondence *is* the effect: what you measured out before you + * clicked is what is now standing on the ground. What it does not share is the + * treatment — the interior is charred and veined rather than washed, the ring + * is a burn rather than a line, and everything in it crawls. + * + * It is an ability-owned mesh rather than a pooled decal for one reason: a + * decal captures its radius when it spawns, and this circle has to re-scale + * under `zoneRadius` while it is still standing. + */ +const FIELD_FRAGMENT = /* glsl */ ` + uniform float uTime; + uniform float uQuadSize; + uniform float uRadius; + uniform float uBoundary; + uniform float uBoundaryGlow; + uniform float uFill; + uniform float uFalloff; + uniform float uVeins; + uniform float uVeinScale; + uniform float uVeinSharp; + uniform float uWarp; + uniform float uCrawl; + uniform float uRings; + uniform float uRingSpeed; + uniform float uSpokes; + uniform float uSpokeLength; + uniform float uSpin; + uniform float uCore; + uniform float uCoreSize; + uniform float uPulse; + uniform float uPulseSpeed; + uniform float uSeed; + uniform float uFade; + uniform float uOpacity; + uniform vec3 uColorField; + uniform vec3 uColorEdge; + uniform float uGlobalGlow; + + varying vec2 vUv; + + ${noiseGLSL} + ${commonGLSL} + + #define TAU 6.28318530718 + + void main() { + vec2 p = vec2(vUv.x - 0.5, 0.5 - vUv.y) * uQuadSize; + float d = length(p); + + float outer = uRadius + uBoundary * 0.4; + float inner = max(0.01, uRadius - uBoundary * 0.6); + + float aa = fwidth(d) + 0.02; + if (d > outer + aa * 4.0) discard; + + float band = smoothstep(outer + aa, outer - aa, d) * smoothstep(inner - aa, inner + aa, d); + float interior = smoothstep(inner + aa, inner - aa, d); + float radial = clamp(d / inner, 0.0, 1.0); + + /* ---- the veins burnt across the disc ---- */ + // Sampled in the *plane* and domain warped, never on atan(): an angular + // lookup hands every radius along a bearing the same value and draws + // dead-straight spokes out of the centre — a firework, not a burn. + float warp = fbm3(vec3(p * 0.5, uTime * 0.2 + uSeed)) * uWarp; + float fil = ridged(vec3(p * uVeinScale + warp, uSeed * 11.0 + uTime * uCrawl), 4); + float veins = smoothstep(mix(0.55, 0.86, uVeinSharp), 0.98, fil) * interior * uVeins; + + /* ---- pressure rings running out of the middle ---- */ + float ring = 0.5 + 0.5 * cos((radial * uRings - uTime * uRingSpeed) * TAU); + ring = pow(ring, 8.0) * interior; + + /* ---- ticks stepping around the boundary ---- */ + float ang = atan(p.y, p.x) / TAU + 0.5; + float spokePhase = fract(ang * uSpokes + uTime * uSpin * uSpokes); + float spoke = 1.0 - smoothstep(0.22, 0.3, spokePhase); + spoke *= smoothstep(inner - uSpokeLength, inner, d) * smoothstep(outer, inner, d); + + /* ---- the pool the column stands in ---- */ + float core = smoothstep(uCoreSize * uRadius, 0.0, d) * uCore; + + float breathe = 1.0 + uPulse * sin(uTime * uPulseSpeed * TAU); + float wash = interior * pow(radial, uFalloff) * uFill; + float lines = (band * uBoundaryGlow + spoke + core + ring * 0.35) * breathe; + float body = (wash + veins * 0.9) * breathe; + + float alpha = clamp(body + lines, 0.0, 1.0) * uFade * uOpacity; + if (alpha < 0.004) discard; + + vec3 color = uColorField * body + uColorEdge * lines; + gl_FragColor = vec4(color * uGlobalGlow, alpha); + } +`; + +/** + * The ground field. One quad, re-sized and re-shaded from `settings.snare` + * every frame. + */ +export function createSnareFieldMaterial() { + const material = new ShaderMaterial({ + transparent: true, + depthWrite: false, + depthTest: true, + blending: AdditiveBlending, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + uQuadSize: { value: 12 }, + uRadius: { value: 4.4 }, + uBoundary: { value: 0.4 }, + uBoundaryGlow: { value: 2.6 }, + uFill: { value: 0.3 }, + uFalloff: { value: 1.7 }, + uVeins: { value: 1 }, + uVeinScale: { value: 1.5 }, + uVeinSharp: { value: 0.72 }, + uWarp: { value: 0.55 }, + uCrawl: { value: 0.5 }, + uRings: { value: 2.4 }, + uRingSpeed: { value: 0.8 }, + uSpokes: { value: 20 }, + uSpokeLength: { value: 0.5 }, + uSpin: { value: 0.05 }, + uCore: { value: 1.3 }, + uCoreSize: { value: 0.22 }, + uPulse: { value: 0.3 }, + uPulseSpeed: { value: 3.4 }, + uSeed: { value: 0 }, + uFade: { value: 1 }, + uOpacity: { value: 1 }, + uColorField: { value: new Color(0.56, 0.42, 1) }, + uColorEdge: { value: new Color(1, 1, 1) } + }), + vertexShader: FIELD_VERTEX, + fragmentShader: FIELD_FRAGMENT + }); + + /** + * @param {object} state { radius, quadSize, fade, seed } + */ + material.userData.sync = (state) => { + const c = settings.snare; + const g = settings.global; + const u = material.uniforms; + + u.uQuadSize.value = state.quadSize; + u.uRadius.value = state.radius; + u.uFade.value = state.fade; + u.uSeed.value = state.seed; + + u.uBoundary.value = c.fieldBoundary; + u.uBoundaryGlow.value = c.fieldBoundaryGlow; + u.uFill.value = c.fieldFill; + u.uFalloff.value = c.fieldFalloff; + u.uVeins.value = c.fieldVeins * g.shaderIntensity; + u.uVeinScale.value = c.fieldVeinScale * g.noiseFrequency; + u.uVeinSharp.value = c.fieldVeinSharp; + u.uWarp.value = c.fieldWarp * g.noiseStrength; + u.uCrawl.value = c.fieldCrawl * g.noiseSpeed; + u.uRings.value = c.fieldRings; + u.uRingSpeed.value = c.fieldRingSpeed; + u.uSpokes.value = Math.max(0, Math.round(c.fieldSpokes)); + u.uSpokeLength.value = c.fieldSpokeLength; + u.uSpin.value = c.fieldSpin; + u.uCore.value = c.fieldCore; + u.uCoreSize.value = c.fieldCoreSize; + u.uPulse.value = c.fieldPulse; + u.uPulseSpeed.value = c.fieldPulseSpeed; + u.uOpacity.value = c.fieldOpacity * g.opacity; + u.uColorField.value.copy(getColor(c.colorField)); + u.uColorEdge.value.copy(getColor(c.colorFieldEdge)); + }; + + return material; +} diff --git a/src/skillshot/materials/VolumetricFireMaterial.js b/src/skillshot/materials/VolumetricFireMaterial.js new file mode 100644 index 0000000..48da1bb --- /dev/null +++ b/src/skillshot/materials/VolumetricFireMaterial.js @@ -0,0 +1,833 @@ +import { + ShaderMaterial, + CustomBlending, + AddEquation, + OneFactor, + OneMinusSrcAlphaFactor, + Color, + DoubleSide +} from 'three'; +import { noiseGLSL } from '../../shaders/lib/noise.glsl.js'; +import { commonGLSL } from '../../shaders/lib/common.glsl.js'; +import { sharedUniforms } from '../../core/FrameUniforms.js'; +import { settings } from '../linearSettings.js'; +import { getColor } from '../../utils/color.js'; + +/** + * How far past the nominal tube radius the fringe shred can push density, + * before the low-frequency bulge is taken into account. Matches the + * `smoothstep(REACH_LO, REACH_HI, q)` cutoff in the fragment shader. + */ +export const FIRE_SHRED_REACH = 1.55; + +/** + * Total outward reach of the volume in units of the nominal tube radius. + * + * The proxy hull has to *contain* the density field: anything the field can + * reach and the hull cannot is sliced off along a dead straight edge. The bulge + * scales the local radius directly, so it multiplies the shred reach. + */ +export function fireHullReach() { + // The 1.3 is margin, not slop. The hull is a flat strip and the volume it has + // to contain is a stretched capsule, so matching the two exactly leaves the + // cap's silhouette sitting on the hull's terminal edge — and a volume that + // ends one pixel outside its proxy is sliced off along a dead straight line, + // which is the single most obvious way this technique fails. + return FIRE_SHRED_REACH * (1 + settings.meteor.trailBulge) * 1.3; +} + +/** + * Radius of the flame at `u` along the stream, in units of `uRadius`. + * + * Two competing terms: the burn grows toward the head, while the spent gas + * behind it has had time to expand — which is what gives the wake its billowing, + * roughly cylindrical silhouette instead of tapering to a needle. + * + * Shared verbatim by the vertex shader (hull sizing), the fragment shader (the + * density field) and `MeteorAbility`'s `radiusProfile` (the proxy geometry). All + * four have to agree or the hull clips the volume. + */ +const PROFILE_GLSL = /* glsl */ ` + uniform float uWakeSpread; + float flameProfile(float u) { + return (0.34 + 0.66 * pow(u, 0.55)) * (1.0 + uWakeSpread * pow(1.0 - u, 1.8)); + } +`; + +/** + * The meteor's burning wake, raymarched as a black-body volume. + * + * Brought over wholesale from the freehand firebending sandbox and re-aimed at + * the meteor's arc: it is the same volume renderer, driven by `settings.meteor`'s + * `trail*` block instead of that project's `fire` block. + * + * The mesh this material is applied to is *not* the flame — it is only a + * camera-facing proxy hull around the flight path (see RibbonGeometry's `frame` + * option). Every fragment reconstructs the curve's local frame from + * `aCenter` / `aTangent`, fires a ray from the camera through itself and + * integrates emission and absorption through the field described below. + * + * The field is built in four layers, coarsest first, because that is the order + * in which the eye reads a real fireball: + * + * 1. *silhouette* — a capsule around the flight path, teardrop in cross + * section (buoyancy stretches it upward), whose local radius is modulated + * by one very low frequency noise octave. That last part is not decoration: + * a fireball's outline is dominated by lobes metres across, and multi-octave + * turbulence at flame-detail frequency can only nibble at the edge of a + * capsule — the result still reads as a shaded tube. + * 2. *vortex roll-up* — hot gas sheds ring vortices that travel back down the + * wake and grow as they age. Rotating the noise domain in the (streamwise, + * vertical) plane by a travelling wave folds the field over on itself, which + * is what produces the curling, mushrooming billows. Plain fbm, however many + * octaves, makes clouds; it never makes curls. + * 3. *turbulence* — 5 progressively domain-warped octaves, rotated between + * octaves to kill lattice alignment, each drifting upward faster than the + * last so fine detail outruns the coarse shapes it rides on and tears into + * upward-licking tongues. + * 4. *shred* — the erosion is weak on the axis and violent at the fringe, so + * the flame keeps a solid burning heart while its edge dissolves into the + * strands and detached wisps a real flame has. + * + * Shading is temperature-driven rather than gradient-driven: + * + * temperature carried by the capsule falloff — geometry — and only *nudged* + * by the two coarsest noise octaves, so a tongue stays hot along + * its own spine without the noise drawing itself. It flattens out + * with age, because spent gas has mixed and no longer has a hot + * axis and a cool skin. + * emission Planckian black-body colour at that temperature, radiated as + * (T/Tcore)^3. Fire is not a palette, it is a temperature: the + * red fringe → orange body → white core continuum, and the vast + * brightness range across it, both fall out of the physics. + * scattering cool gas near the burning core is bathed in its light. Without + * this the sooty fringe goes flat black instead of glowing. + * absorption soot extinction. Cold smoke occludes hard, but even the hottest + * gas keeps a floor of it (`uCoreClarity`): without that floor the + * march integrates the entire depth of the fireball into one + * clipped white blob instead of showing its near face. + * + * A warning that cost a lot of renders to learn, since it governs three separate + * decisions in here (the coarse-only heat field, the modest `uHeatFollow`, the + * age flattening): radiated power goes as a high power of temperature, so + * whatever drives the temperature has its contrast multiplied several-fold on + * screen. Point that amplifier at a turbulent field and it lands on the field's + * level sets, which are nested closed loops, and the flame renders as polished + * agate — dense contour lines wrapping every blob. No amount of colour tuning + * fixes it; the fix is always to make the temperature blunter. + * + * That last point is why the blend mode is premultiplied "over" rather than the + * additive blending the rest of the VFX use: a real flame both emits and blocks. + * The march is clipped by the opaque depth prepass, so the volume is occluded by + * the ground and the character and fades softly where it touches them. + */ +export class VolumetricFireMaterial extends ShaderMaterial { + constructor() { + super({ + transparent: true, + depthWrite: false, + // Per-sample scene-depth clipping inside the march does the occlusion job + // properly; testing the flat proxy hull's own depth would slice the volume. + depthTest: false, + blending: CustomBlending, + blendEquation: AddEquation, + blendSrc: OneFactor, + blendDst: OneMinusSrcAlphaFactor, + blendSrcAlpha: OneFactor, + blendDstAlpha: OneMinusSrcAlphaFactor, + side: DoubleSide, + toneMapped: false, + uniforms: sharedUniforms({ + /* shape */ + uRadius: { value: 0.95 }, + uHeadSize: { value: 2.1 }, + uPlume: { value: 1.8 }, + uWakeSpread: { value: 1.1 }, + uStreamLength: { value: 6.0 }, + uArcLength: { value: 7.0 }, + uTailPad: { value: 0.3 }, + uDetachment: { value: 0.5 }, + uSoftness: { value: 0.45 }, + uShred: { value: 1.35 }, + uBulge: { value: 0.28 }, + uBulgeScale: { value: 0.5 }, + /* motion */ + uFlow: { value: 2.4 }, + uBuoyancy: { value: 2.6 }, + uSwirl: { value: 0.45 }, + uSwirlSpeed: { value: 0.9 }, + uVortex: { value: 1.15 }, + uRingFreq: { value: 0.55 }, + uRingSpeed: { value: 1.6 }, + uNoiseFrequency: { value: 3.2 }, + uNoiseStrength: { value: 0.9 }, + uWarp: { value: 0.2 }, + uTongue: { value: 0.5 }, + uStreamStretch: { value: 0.3 }, + uWisp: { value: 0.0 }, + uFlicker: { value: 0.45 }, + uLick: { value: 3.2 }, + uOctaves: { value: 5 }, + /* temperature & radiance */ + uTempCore: { value: 3500 }, + uTempEdge: { value: 1550 }, + uEmissionCurve: { value: 2.6 }, + uHeatFocus: { value: 1.0 }, + uHeatFalloff: { value: 1.15 }, + uHeatFollow: { value: 0.2 }, + uTailHeat: { value: 0.5 }, + uPalette: { value: 0.0 }, + uScatter: { value: 0.9 }, + uScatterFalloff: { value: 2.2 }, + /* rendering */ + uDensity: { value: 1.35 }, + uSoot: { value: 1.5 }, + uCoreClarity: { value: 0.62 }, + uEmission: { value: 6.5 }, + uOpacity: { value: 1 }, + uSteps: { value: 44 }, + uSeed: { value: Math.random() * 20 }, + uHeadFade: { value: 0 }, + uTailFade: { value: 0.1 }, + uColorCore: { value: new Color(1, 0.96, 0.82) }, + uColorMid: { value: new Color(1, 0.69, 0.18) }, + uColorEdge: { value: new Color(1, 0.24, 0.06) }, + uColorSmoke: { value: new Color(0.14, 0.1, 0.09) } + }), + vertexShader: /* glsl */ ` + uniform float uArcLength; + uniform float uTailPad; + uniform float uStreamLength; + uniform float uRadius; + uniform float uHeadSize; + + attribute float aDist; + attribute float aRandom; + attribute vec3 aCenter; + attribute vec3 aTangent; + + varying vec3 vCenter; + varying vec3 vTangent; + varying float vS; + varying float vRadius; + varying vec3 vWorld; + varying float vRandom; + + ${PROFILE_GLSL} + + void main() { + vCenter = aCenter; + vTangent = normalize(aTangent); + // Metres from the tail of the *stream* (the hull is padded at both + // ends so its end caps are covered). + vS = aDist * uArcLength - uTailPad; + vRandom = aRandom; + + // Local tube radius, so the fragment can size its march window to the + // flame in front of it instead of to the fattest part of the stream — + // the thin tail gets fine steps rather than mostly empty ones. + float u = clamp(vS / max(uStreamLength, 0.01), 0.0, 1.0); + vRadius = uRadius * flameProfile(u) + * (1.0 + (uHeadSize - 1.0) * smoothstep(0.62, 1.0, u)); + + vec4 world = modelMatrix * vec4(position, 1.0); + vWorld = world.xyz; + gl_Position = projectionMatrix * viewMatrix * world; + } + `, + fragmentShader: /* glsl */ ` + uniform float uTime; + uniform float uRadius; + uniform float uHeadSize; + uniform float uPlume; + uniform float uStreamLength; + uniform float uDetachment; + uniform float uSoftness; + uniform float uShred; + uniform float uBulge; + uniform float uBulgeScale; + uniform float uFlow; + uniform float uBuoyancy; + uniform float uSwirl; + uniform float uSwirlSpeed; + uniform float uVortex; + uniform float uRingFreq; + uniform float uRingSpeed; + uniform float uNoiseFrequency; + uniform float uNoiseStrength; + uniform float uWarp; + uniform float uTongue; + uniform float uStreamStretch; + uniform float uWisp; + uniform float uFlicker; + uniform float uLick; + uniform float uOctaves; + uniform float uTempCore; + uniform float uTempEdge; + uniform float uEmissionCurve; + uniform float uHeatFocus; + uniform float uHeatFalloff; + uniform float uHeatFollow; + uniform float uTailHeat; + uniform float uPalette; + uniform float uScatter; + uniform float uScatterFalloff; + uniform float uDensity; + uniform float uSoot; + uniform float uCoreClarity; + uniform float uEmission; + uniform float uOpacity; + uniform float uSteps; + uniform float uSeed; + uniform float uHeadFade; + uniform float uTailFade; + uniform vec3 uColorCore; + uniform vec3 uColorMid; + uniform vec3 uColorEdge; + uniform vec3 uColorSmoke; + uniform float uShaderIntensity; + uniform float uGlobalGlow; + uniform vec2 uResolution; + uniform sampler2D uSceneDepth; + uniform float uCameraNear; + uniform float uCameraFar; + + varying vec3 vCenter; + varying vec3 vTangent; + varying float vS; + varying float vRadius; + varying vec3 vWorld; + varying float vRandom; + + ${noiseGLSL} + ${commonGLSL} + ${PROFILE_GLSL} + + /* Where the fringe shred starts and stops. REACH_HI bounds how far + outside the nominal tube the noise can push density, and therefore how + wide the proxy hull has to be — it is mirrored by FIRE_SHRED_REACH on + the JS side. */ + const float REACH_LO = 1.15; + const float REACH_HI = 1.55; + + /* A volume march needs a *cheap* field: this is trilinear value noise on + a hashed lattice, roughly a third of the cost of the simplex noise the + surface shaders use, and indistinguishable once five octaves of it are + being advected, warped and rolled. */ + float vnoise(vec3 p) { + vec3 i = floor(p); + vec3 f = fract(p); + f = f * f * (3.0 - 2.0 * f); + + float a = hash13(i); + float b = hash13(i + vec3(1.0, 0.0, 0.0)); + float c = hash13(i + vec3(0.0, 1.0, 0.0)); + float d = hash13(i + vec3(1.0, 1.0, 0.0)); + float e = hash13(i + vec3(0.0, 0.0, 1.0)); + float g = hash13(i + vec3(1.0, 0.0, 1.0)); + float h = hash13(i + vec3(0.0, 1.0, 1.0)); + float k = hash13(i + vec3(1.0, 1.0, 1.0)); + + return mix( + mix(mix(a, b, f.x), mix(c, d, f.x), f.y), + mix(mix(e, g, f.x), mix(h, k, f.x), f.y), + f.z + ); + } + + /* Unit-determinant rotation applied between octaves. Value noise is + built on an axis-aligned lattice, so stacking octaves along the same + axes lines their features up into a visible grid; rotating the domain + between them scatters that structure. */ + const mat3 OCTAVE_ROT = mat3( + 0.00, 0.80, 0.60, + -0.80, 0.36, -0.48, + -0.60, -0.48, 0.64 + ); + + /** + * Turbulent flame field, normalised to 0..1. + * + * Three things separate this from a plain fbm, and two of them are free — + * they reuse octave values that have already been paid for: + * + * - *progressive domain warping*: each octave is displaced by the one + * before it, which folds the field over itself. Straight fbm gives + * round, cloud-like blobs; warped fbm gives the curling, billowing + * sheets fire actually makes. + * - *buoyant shear*: every octave also drifts upward faster than the + * last, so fine detail outruns the coarse shapes it sits on and the + * structures tear into upward-licking tongues. + * - *inter-octave rotation*: see OCTAVE_ROT. + * + * @param ridge out — sharp ridged filaments from the two finest octaves, + * 0..1, used to shred the fringe into strands. + * @param coarse out — the field truncated to its two largest scales, + * 0..1. Temperature is read off *this* rather than off the + * full field; see the note where it is used. + */ + float flameFbm(vec3 p, float rise, out float ridge, out float coarse) { + float v = 0.0; + float a = 0.5; + float norm = 0.0; + float scale = 1.0; + ridge = 0.0; + coarse = 0.5; + + for (int i = 0; i < 5; i++) { + if (float(i) >= uOctaves) break; + + float n = vnoise(p); + v += a * n; + norm += a; + if (i == 1) coarse = v / norm; + // Ridges come from the finest octaves alone. Averaging the coarse + // octaves in as well produces broad, smooth ridges that trace the + // iso-lines of the field, and those read as contour lines drawn on + // the flame rather than as strands in it. + if (i >= 3) ridge = max(ridge, 1.0 - abs(n * 2.0 - 1.0)); + + p = OCTAVE_ROT * p * 2.17 + (n - 0.5) * uWarp * vec3(1.7, 0.9, 1.3); + scale *= 2.17; + p.y -= rise * scale * (1.0 + 0.6 * float(i)); + a *= 0.55; + } + + return v / max(norm, 1e-4); + } + + /** + * Planckian radiator, converted to the linear working space. + * + * Fire is not a palette, it is a temperature. The reason a real flame + * runs deep red at its fringe, orange through its body and white in its + * core is that those are the colours a grey body emits between roughly + * 1200 K and 3300 K — and the reason the core blows out while the fringe + * stays dim is that the power emitted scales as T^4. Both fall out of the + * physics here instead of having to be dialled in by hand, which is why + * the result holds together at any exposure. + * + * Fit: Tanner Helland's piecewise approximation, valid 1000-40000 K. + */ + vec3 blackbody(float kelvin) { + float t = clamp(kelvin, 1000.0, 12000.0) * 0.01; + vec3 c; + if (t <= 66.0) { + c.r = 1.0; + c.g = 0.3900816 * log(t) - 0.6318414; + c.b = t <= 19.0 ? 0.0 : 0.5432068 * log(t - 10.0) - 1.1962540; + } else { + c.r = 1.2929362 * pow(t - 60.0, -0.1332047); + c.g = 1.1298909 * pow(t - 60.0, -0.0755148); + c.b = 1.0; + } + c = clamp(c, 0.0, 1.0); + return c * c * (0.6 + 0.4 * c); // cheap, accurate-enough sRGB -> linear + } + + /** + * Density of the flame at a world point. + * + * @param heat out — normalised temperature, 0 at the perturbed surface, + * 1 in the burning core. + * @param bath out — how strongly this point is lit by the flame around + * it, for the single-scatter term. + */ + float flameSample(vec3 p, out float heat, out float bath) { + heat = 0.0; + bath = 0.0; + + vec3 rel = p - vCenter; + float ax = dot(rel, vTangent); + vec3 perp = rel - vTangent * ax; + + float s = vS + ax; // metres from the tail + float u = clamp(s / max(uStreamLength, 0.01), 0.0, 1.0); + float age = 1.0 - u; + + // Billowing wake behind a fireball at the nose. + float radius = uRadius * flameProfile(u) + * (1.0 + (uHeadSize - 1.0) * smoothstep(0.62, 1.0, u)); + + // A local frame whose "up" is world up (projected off the tangent), so + // buoyancy, the teardrop stretch and the vertical elongation of the + // noise all pull in the direction gravity actually cares about. + vec3 upAxis = vec3(0.0, 1.0, 0.0) - vTangent * vTangent.y; + float upLen = length(upAxis); + upAxis = upLen > 1e-3 ? upAxis / upLen : vec3(1.0, 0.0, 0.0); + vec3 sideAxis = cross(vTangent, upAxis); + + float py = dot(perp, upAxis); + float px = dot(perp, sideAxis); + + // Buoyant stretch: mostly upward, so the cross-section is a teardrop + // rather than a symmetric tube. The underside gets a quarter of it — + // enough that the flame drapes instead of ending on a flat horizontal + // cut, which is what an unstretched underside reads as. + float plume = max(uPlume, 0.05); + float shapedY = py > 0.0 ? py / plume : py / (1.0 + (plume - 1.0) * 0.25); + float rad = length(vec2(px, shapedY)); + + // Fold the axial overshoot past either end into the radius: a capsule, + // so the stream ends in round caps instead of being sliced flat. + float over = s - clamp(s, 0.0, uStreamLength); + rad = sqrt(rad * rad + over * over); + + // Conservative reject *before* paying for any noise: even with the + // bulge pushed fully outward the field cannot reach past this. Most + // samples along most rays land here, so this test is what pays for the + // headroom the hull needs. + float qNominal = rad / max(radius, 1e-3); + if (qNominal > REACH_HI * (1.0 + uBulge)) return 0.0; + + float sFlow = s - uTime * uFlow; // streams backwards + + // ---- 1. silhouette: metre-scale lobes ------------------------- + // One octave, wavelength of order the flame's own width. A fireball's + // outline is dominated by lobes this size; turbulence at flame-detail + // frequency can only nibble the edge of a capsule and the result still + // reads as a shaded tube. + float lobe = vnoise(vec3(px, py * 0.8, sFlow * 0.7) * uBulgeScale + + vec3(uSeed, uTime * 0.25, 0.0)) * 2.0 - 1.0; + radius *= 1.0 + uBulge * lobe; + + float q = rad / max(radius, 1e-3); + float edge = 1.0 - q; + if (edge < -1.2) return 0.0; + + // ---- 2. vortex roll-up ---------------------------------------- + // Noise domain attached to the stream (a local frame around the axis) + // so the fire is carried along instead of swimming through the world. + vec2 rp = rot2(s * uSwirl * 0.3 + uTime * uSwirlSpeed + uSeed) * vec2(px, py); + + // Hot gas sheds ring vortices that travel back down the wake and grow + // as they age. Rotating the domain in the (streamwise, vertical) plane + // by that travelling wave folds the field over on itself — this is what + // produces curling, mushrooming billows. Plain fbm, at any octave + // count, makes clouds; it never makes curls. The rotation is strongest + // on the axis and dies off outside the tube, so the fringe is sheared + // past the core rather than rotating rigidly with it. + float phase = s * uRingFreq - uTime * uRingSpeed + uSeed; + float roll = uVortex * sin(phase * 6.2831853) + * smoothstep(0.0, 0.32, age) * exp(-q * q * 0.7); + vec2 rolled = rot2(roll) * vec2(sFlow * uStreamStretch, rp.y * uTongue); + + // ---- 3. turbulence -------------------------------------------- + // Anisotropic on purpose: a lower frequency vertically and along the + // flow draws every structure out into a tall, streamwise tongue. + vec3 np = vec3(rp.x, rolled.y, rolled.x) * uNoiseFrequency; + np.y -= uTime * uBuoyancy; // hot gas climbs + + // Radial shear. Gas at the fringe is unconfined and has been climbing + // and falling behind for longer than gas on the axis, so the domain is + // dragged upward and backward in proportion to how far out it sits. + // This is what turns rounded blobs into the tongues that lick up off a + // flame's edge: without a shear the structures are the same shape + // everywhere and only their size changes. + np.y -= uLick * q * q; + np.z += uLick * 0.45 * q * q; + + float ridge, coarse; + float n = flameFbm(np, uTime * uBuoyancy * 0.06, ridge, coarse) * 2.0 - 1.0; + coarse = coarse * 2.0 - 1.0; + + // Filaments belong to the fringe. In the body they only mottle a core + // that should read as one continuous sheet of burning gas. + // + // Note the sign: the ridge peaks *on* an iso-line of the noise, so + // adding it thickens the iso-lines and the flame ends up drawn in + // bright closed loops, like a contour map. Subtracting it instead + // deepens the gaps between lobes, which is what actually separates the + // fringe into strands. + float fringe = smoothstep(0.3, 1.05, q); + float turbulent = n * 0.75 + (0.5 - ridge) * uWisp * fringe; + + // ---- 4. shred -------------------------------------------------- + // The heart of the flame is barely touched by the noise while the + // fringe is torn apart by it. That contrast — solid burning core, an + // edge dissolving into wisps — is most of what reads as "fire". + // + // The falloff past REACH_LO is not cosmetic: it is what bounds how far + // outside the nominal tube the noise can push density. Without it the + // volume reaches well past the proxy hull and the flame is sliced off + // along a visibly straight edge. + // The coherent burning core belongs to the head. By the far end of the + // wake the gas has been churning for the whole flight and there is no + // quiet axis left, so the erosion floor climbs with age. + float shred = mix(mix(0.26, 1.0, age), 1.8 * uShred, smoothstep(0.06, 1.05, q)) + * (1.0 - smoothstep(REACH_LO, REACH_HI, q)); + // Turbulence is *fully developed* in the wake and still growing at the + // head: the gas at the far end has had the whole flight to break up. + // Without this the tail is the smoothest part of the volume, and a + // smooth field marched with a hard density threshold renders its own + // iso-surfaces as concentric shells — the flame ends up wearing + // contour rings exactly where it should look most chaotic. + float erosion = uNoiseStrength * shred * (1.0 + 1.3 * age); + float field = edge + turbulent * erosion; + field -= uDetachment * age * 0.55; // the tail tears into puffs + + // Temperature gets its own, deliberately blunter field, and the reason + // is the single least obvious thing in this shader. + // + // Radiated power goes as a high power of temperature, so whatever + // drives the temperature gets its contrast multiplied several-fold on + // screen. Drive it from the full density field and that amplification + // lands on the level sets of turbulent noise — which are nested closed + // loops — and the flame comes out looking like polished agate: dense + // parallel contour lines wrapping every blob. It is unmistakable once + // seen and impossible to tune away from the colour end. + // + // So the contrast is carried by the capsule falloff, i.e. geometry: hot on the + // axis, cooling outward, no level sets of its own. The noise only + // *displaces* that profile — enough that a tongue stays hot along its + // spine, not enough to draw itself. And it is the two coarsest octaves + // that do the displacing, since a reaction zone is thick compared with + // the smallest turbulent scales. + // Deliberately not the age-amplified erosion the density uses: letting + // the wake's extra churn into the temperature brings the agate straight + // back, and only in the tail, which is the last place it is wanted. + float heatField = edge + coarse * uHeatFollow * uNoiseStrength * shred + - uDetachment * age * 0.55; + + float d = smoothstep(0.0, clamp(uSoftness, 0.05, 1.0), field); + if (d <= 0.0) return 0.0; + + d *= smoothstep(0.0, max(uTailFade, 0.01), u); + d *= 1.0 - smoothstep(1.0 - uHeadFade, 1.0, u) * step(0.001, uHeadFade); + + // ---- temperature ------------------------------------------------ + // Follows the *perturbed* shape rather than the distance to the axis, so + // a tongue the large eddies pushed outward stays hot along its own spine + // and cools toward its own tip — the difference between filaments that + // are burning and filaments that have been painted orange onto a + // cylinder. Reading it off the density instead would clamp to 1 across + // the whole core and flatten the flame into a single white blob. + float interior = clamp(heatField * uHeatFocus, 0.0, 1.0); + // Spent gas has mixed. It no longer has a hot axis and a cool skin — + // it is all roughly one temperature, simply dimming. Flattening the + // radial profile with age is also what keeps the thin tail from + // rendering as a stack of nested rings: that is where the radial + // gradient is steepest, so that is where equal steps of temperature + // are packed closest together and the high radiance exponent turns + // them into visible bands. + interior = mix(interior, 0.55, age * age * 0.65); + // ...and behind the head it has also had time to radiate away. + float cool = mix(uTailHeat, 1.0, pow(u, 0.55)); + heat = clamp(pow(interior, max(uHeatFalloff, 0.05)) * cool, 0.0, 1.0); + + // Cool gas close to the burning core is bathed in its light. + bath = exp(-max(q - 0.5, 0.0) * uScatterFalloff) * (1.0 - heat); + + return d * uDensity; + } + + void main() { + vec3 ro = cameraPosition; + vec3 rd = normalize(vWorld - ro); + + // The hull is a camera-facing strip through the axis, so every pixel + // gets exactly one fragment and that fragment has to march the whole + // volume along its ray. Bounding it with a sphere is what the obvious + // implementation does, and it is ruinous here: the flame is stretched + // two-and-a-half times taller than it is wide, so a sphere big enough + // to hold it is two-and-a-half times too long in the direction almost + // every ray actually travels — the fixed step budget gets spread over + // a slab that is mostly empty and the volume samples at a fraction of + // the resolution it should. + // + // Bounding with the *ellipsoid* the volume actually occupies instead + // costs one extra dot product and buys back that factor directly as + // step density. Solve it as a unit sphere in the squashed frame. + float reach = REACH_HI * (1.0 + uBulge); + vec3 up = vec3(0.0, 1.0, 0.0) - vTangent * vTangent.y; + float upLen = length(up); + up = upLen > 1e-3 ? up / upLen : vec3(1.0, 0.0, 0.0); + vec3 side = cross(vTangent, up); + + float rPerp = max(vRadius * reach, uRadius * 0.2); + float rUp = rPerp * max(uPlume, 1.0); + float rDown = rPerp * (1.0 + (max(uPlume, 1.0) - 1.0) * 0.25); + // The plume only stretches upward, so the bound is an ellipsoid lifted + // off the axis rather than centred on it. + float rVert = (rUp + rDown) * 0.5; + vec3 origin = vCenter + up * (rUp - rDown) * 0.5; + // Looking down the barrel of the stream, a ray stays inside the volume + // for its whole length; broadside, it is through in one diameter. + float axial = abs(dot(rd, vTangent)); + float rLong = mix(rPerp, max(uStreamLength * 0.6, rPerp), axial * axial); + + vec3 o = ro - origin; + vec3 eo = vec3(dot(o, side) / rPerp, dot(o, up) / rVert, dot(o, vTangent) / rLong); + vec3 ed = vec3(dot(rd, side) / rPerp, dot(rd, up) / rVert, dot(rd, vTangent) / rLong); + float ea = dot(ed, ed); + float eb = dot(eo, ed); + float ec = dot(eo, eo) - 1.0; + float disc = eb * eb - ea * ec; + if (disc <= 0.0) discard; + float esq = sqrt(disc); + float maxRadius = rPerp; + float t0 = max((-eb - esq) / ea, 0.02); + float t1 = (-eb + esq) / ea; + + // Clip the march on the opaque scene: correct occlusion by the ground + // and the character, for free. + float dzdt = (viewMatrix * vec4(rd, 0.0)).z; + float packed = unpackRGBAToDepth(texture2D(uSceneDepth, gl_FragCoord.xy / uResolution)); + float sceneViewZ = perspectiveDepthToViewZ(packed, uCameraNear, uCameraFar); + float tScene = dzdt < -1e-5 ? sceneViewZ / dzdt : 1e6; + t1 = min(t1, tScene); + if (t1 <= t0) discard; + + float steps = clamp(uSteps, 6.0, 72.0); + float baseStep = (t1 - t0) / steps; + // Dither the entry point: a fixed start turns the slices into visible + // onion rings, a per-pixel offset turns them into grain the bloom eats. + float t = t0 + baseStep * hash13(vec3(gl_FragCoord.xy, fract(uTime) * 64.0)); + + // One flicker value for the whole ray — the flame brightens and dims + // as a body, not per sample. Two rates: a slow roll from the bulk of + // the gas, a fast tremor from the burn itself. + // Amplitude deliberately modest and clamped. This multiplies the whole + // ray, so a swing wide enough to look dramatic on paper reads on screen + // as the fireball guttering out for a frame at a time. + float flickN = vnoise(vec3(uTime * 5.3, uSeed, 0.0)) * 0.7 + + vnoise(vec3(uTime * 13.1, uSeed * 2.0, 5.0)) * 0.3; + float flick = clamp(1.0 + uFlicker * 0.6 * (flickN * 2.0 - 1.0), 0.4, 1.7); + + vec3 acc = vec3(0.0); + float transmittance = 1.0; + // The hull has to be wide enough for the noise to push the volume out + // to its full reach, which leaves most rays crossing a lot of empty + // space. Coasting through it at a coarser stride — and dropping back + // to the fine one the moment anything is hit — buys back the cost of + // that headroom without thinning the samples where they matter. + float stride = 1.0; + + for (int i = 0; i < 72; i++) { + if (t >= t1 || transmittance < 0.012) break; + + float heat, bath; + float d = flameSample(ro + rd * t, heat, bath); + float stepSize = baseStep * stride; + + if (d > 0.002) { + stride = 1.0; + stepSize = baseStep; + // Soften the contact with whatever the march ran into. + d *= clamp((tScene - t) / max(maxRadius * 0.6, 1e-3), 0.0, 1.0); + + float kelvin = mix(uTempEdge, uTempCore, heat); + // The palette blend keeps the editor's four colour stops meaningful + // without letting them fight the physics: at 0 the flame is a pure + // radiator, at 1 it is the hand-authored ramp. + vec3 artistic = gradient4(uColorCore, uColorMid, uColorEdge, uColorSmoke, + pow(1.0 - heat, 0.85)); + vec3 tint = mix(blackbody(kelvin), artistic, clamp(uPalette, 0.0, 1.0)); + + // Stefan-Boltzmann. This single exponent is what gives the volume + // its enormous internal dynamic range: a white-hot core forty times + // brighter than the red gas two centimetres outside it. + float power = pow(kelvin / max(uTempCore, 1.0), uEmissionCurve); + vec3 emission = tint * power; + + // Single scatter: the sooty gas wrapped around the burn does not + // radiate on its own, it reflects. Without this term the fringe + // and the smoky underside collapse to flat black and the flame + // loses the halo that sits between it and the smoke. + emission += mix(uColorEdge, uColorMid, bath) * uScatter * bath * bath * 0.06; + + acc += emission * uEmission * flick * d * transmittance * stepSize; + + // Soot extinction: the cool fringe blocks the background hard, and + // even the hot gas keeps enough of it to occlude its own far side, + // which is what leaves visible structure in the fireball instead + // of one integrated white disc. + // Squared, so only the genuinely white-hot gas turns clear. Fading + // the soot away linearly with temperature thins the merely warm + // gas on the near face too, and that gas is exactly what draws the + // dark filaments across a real fireball's bright interior. + transmittance *= exp(-d * uSoot * mix(1.0, uCoreClarity, heat * heat) * stepSize); + } else { + stride = min(stride * 1.6, 2.2); + } + + t += stepSize; + } + + float alpha = clamp((1.0 - transmittance) * uOpacity, 0.0, 1.0); + vec3 color = acc * uOpacity * uGlobalGlow * mix(0.65, 1.0, uShaderIntensity); + if (alpha < 0.002 && max(color.r, max(color.g, color.b)) < 0.002) discard; + + gl_FragColor = vec4(color, alpha); + } + ` + }); + } + + /** + * Pull live editor values (called once per frame by the ability). + * + * `uRadius`, `uStreamLength`, `uArcLength` and `uTailPad` are deliberately not + * touched here: they describe the hull the ability has just built and are + * written by it straight afterwards. + */ + sync() { + const c = settings.meteor; + const g = settings.global; + const u = this.uniforms; + + u.uRadius.value = c.trailWidth; + u.uHeadSize.value = c.trailHeadSize; + u.uPlume.value = c.trailPlume; + u.uWakeSpread.value = c.trailWakeSpread; + u.uDetachment.value = c.trailDetachment; + u.uSoftness.value = c.trailSoftness; + u.uShred.value = c.trailShred; + u.uBulge.value = c.trailBulge; + u.uBulgeScale.value = c.trailBulgeScale; + + u.uFlow.value = c.trailSpeed * g.noiseSpeed; + u.uBuoyancy.value = c.trailBuoyancy * g.noiseSpeed; + u.uSwirl.value = c.trailCurl * g.turbulence; + u.uSwirlSpeed.value = c.trailSpeed * 0.35 * g.noiseSpeed; + u.uVortex.value = c.trailVortex * g.turbulence; + u.uRingFreq.value = c.trailRingFrequency; + u.uRingSpeed.value = c.trailRingSpeed * g.noiseSpeed; + u.uNoiseFrequency.value = c.trailNoiseFrequency * g.noiseFrequency; + u.uNoiseStrength.value = + 0.28 * c.trailTurbulence * c.trailNoiseStrength * g.turbulence * g.noiseStrength; + u.uWarp.value = c.trailWarp * g.turbulence; + u.uTongue.value = c.trailTongue; + u.uStreamStretch.value = c.trailStreamStretch; + u.uWisp.value = c.trailWisps; + u.uFlicker.value = c.trailFlicker; + u.uLick.value = c.trailLick; + u.uOctaves.value = c.trailOctaves; + + u.uTempCore.value = c.trailTempCore; + u.uTempEdge.value = c.trailTempEdge; + u.uEmissionCurve.value = c.trailEmissionCurve; + u.uHeatFocus.value = c.trailHeatFocus; + u.uHeatFalloff.value = c.trailHeatFalloff; + u.uHeatFollow.value = c.trailHeatFollow; + u.uTailHeat.value = c.trailTailHeat; + u.uPalette.value = c.trailPalette; + u.uScatter.value = c.trailScatter; + u.uScatterFalloff.value = c.trailScatterFalloff; + + u.uDensity.value = c.trailDensity; + u.uSoot.value = c.trailSoot; + u.uCoreClarity.value = c.trailCoreClarity; + u.uEmission.value = c.trailGlow * 2.4; + u.uOpacity.value = c.trailOpacity * g.opacity; + u.uSteps.value = c.trailSteps; + u.uTailFade.value = c.trailTailFade; + + // The palette is shared with the rock's seams and the detonation, so the + // whole ability stays one temperature range when a colour is dragged. + u.uColorCore.value.copy(getColor(c.colorHot)); + u.uColorMid.value.copy(getColor(c.colorFlameMid)); + u.uColorEdge.value.copy(getColor(c.colorFlameEdge)); + u.uColorSmoke.value.copy(getColor(c.colorFlameSmoke)); + } +} diff --git a/src/ui/AdminHub.js b/src/ui/AdminHub.js new file mode 100644 index 0000000..3791c54 --- /dev/null +++ b/src/ui/AdminHub.js @@ -0,0 +1,1063 @@ +/** + * Admin Hub F1–F4 + ] World — tools / editors for prefab create, saves, deployables. + * + * F1 Player · F2 Assets · F3 Creatures · F4 Prefabs · ] World (not F5) + * Esc closes · ? = keyboard help · ` = auto run / freeride sail + * + * Extends prefabScaffold + deployableContract — no parallel catalog mint. + * + * @see docs/ADMIN_HUB_F1_F5_SSOT.md + */ + +import './adminHub.css'; +import { + ADMIN_TABS, + DEPLOYABLE_KINDS, + BUILDABLE_PURPOSES, + CREATURE_ROLES, + AI_BRAINS, + DEPLOYABLE_ENDPOINTS, + kindsForAdminTab, + validateDeployableDraft +} from '../api/deployableContract.js'; +import { + listDrafts, + getDraft, + saveDraft, + deleteDraft, + createAndSaveDraft, + duplicateDraft, + downloadDraftExport, + importDraftJson, + draftStats +} from '../api/prefabDraftStore.js'; +import { + loadPrefabScaffold, + buildItemScaffoldPack, + downloadJson, + SCAFFOLD_ENDPOINTS +} from '../api/prefabScaffold.js'; +import { + ensureWeaponCatalog, + listEquippableWeapons, + equipWeaponById +} from '../combat/equippedWeaponRuntime.js'; +import { + measureWeaponScale, + importWeaponGlb, + assignModelToDraft, + equipWeaponForLab, + setLiveWeaponScale, + setLiveWeaponAppearance, + formatScaleReadout +} from '../equipment/weaponPrefabLab.js'; +import { getAppearance } from '../equipment/meshAppearance.js'; +import { PREFAB_CATEGORIES, ITEM_BROWSER_URL, WEAPON_SKILLS_HTML } from '../api/gameItemCatalog.js'; +import { raceDef, RACES as RACE_MAP, DEFAULT_RACE } from '../config/grudge6SSOT.js'; + +const RACE_IDS = Object.keys(RACE_MAP || { WK: 1 }); + +export class AdminHub { + /** + * @param {object} [opts] + * @param {object} [opts.character] + * @param {() => object|null} [opts.getDrc] + * @param {(msg: string) => void} [opts.onToast] + * @param {() => void} [opts.onOpenInventoryPrefabs] + * @param {() => void} [opts.onHelp] + * @param {object} [opts.session] + * @param {(n?: number) => void|Promise} [opts.spawnLoot] + * @param {() => void|Promise} [opts.respawnHarvest] + * @param {() => void|Promise} [opts.equipHarvestTool] + * @param {() => void|Promise} [opts.respawnDummies] + */ + constructor(opts = {}) { + this.character = opts.character || null; + this.getDrc = opts.getDrc || (() => null); + this.onToast = opts.onToast || (() => {}); + this.onOpenInventoryPrefabs = opts.onOpenInventoryPrefabs || null; + this.onHelp = opts.onHelp || null; + this.session = opts.session || null; + this.spawnLoot = opts.spawnLoot || null; + this.respawnHarvest = opts.respawnHarvest || null; + this.equipHarvestTool = opts.equipHarvestTool || null; + this.respawnDummies = opts.respawnDummies || null; + + this.open = false; + this._tab = 'prefabs'; + /** @type {object|null} */ + this._selected = null; + this._kind = 'weapon'; + this._form = { + name: '', + purpose: 'structure', + role: 'enemy', + raceId: DEFAULT_RACE || 'WK', + brain: 'guard' + }; + this._scaffold = null; + + this.el = document.createElement('div'); + this.el.id = 'admin-hub'; + this.el.className = 'admin-hub'; + this.el.hidden = true; + document.body.appendChild(this.el); + + this.chips = document.createElement('div'); + this.chips.className = 'admin-hub-chips'; + this.chips.setAttribute('aria-label', 'Admin F1–F4 · ] World'); + document.body.appendChild(this.chips); + + this._renderChips(); + this._renderShell(); + } + + _renderChips() { + this.chips.innerHTML = ADMIN_TABS.map( + (t) => + `` + ).join(''); + this.chips.querySelectorAll('[data-tab]').forEach((btn) => { + btn.addEventListener('pointerdown', (e) => { + e.stopPropagation(); + e.preventDefault(); + this.openTab(btn.dataset.tab); + }); + }); + } + + _renderShell() { + this.el.innerHTML = ` +
+
+

Admin Hub

+

Tools · editors · prefab create/save · deployables

+
+ +
+ +
+
+ F1-F4 · ] World · Esc close · ? help · ` auto + +
+ `; + this.el.querySelector('[data-close]').addEventListener('click', () => this.setOpen(false)); + this.el.addEventListener('pointerdown', (e) => e.stopPropagation()); + this.el.addEventListener('wheel', (e) => e.stopPropagation()); + this._paintTabs(); + } + + _paintTabs() { + const nav = this.el.querySelector('[data-tabs]'); + if (!nav) return; + nav.innerHTML = ADMIN_TABS.map( + (t) => + `` + ).join(''); + nav.querySelectorAll('[data-tab]').forEach((btn) => { + btn.addEventListener('click', () => this.openTab(btn.dataset.tab)); + }); + this.chips.querySelectorAll('[data-tab]').forEach((b) => { + b.classList.toggle('is-active', this.open && b.dataset.tab === this._tab); + }); + } + + toggle() { + this.setOpen(!this.open); + } + + setOpen(open) { + this.open = !!open; + this.el.hidden = !this.open; + if (this.open) this.refresh(); + else this._paintTabs(); + } + + /** + * Open hub on a tab. Same key toggles close if already on that tab. + * @param {string} tabId + */ + openTab(tabId) { + const tab = ADMIN_TABS.find((t) => t.id === tabId); + if (!tab) return; + if (this.open && this._tab === tabId) { + this.setOpen(false); + return; + } + this._tab = tabId; + // default create kind for tab + const kinds = kindsForAdminTab(tabId); + if (kinds.length) this._kind = kinds[0].id; + this.setOpen(true); + } + + /** + * Hotkey by key label or KeyboardEvent.code. + * @param {string} key 'F1'..'F4' | ']' | 'BracketRight' + */ + openByKey(key) { + const k = String(key || ''); + // F5 intentionally unbound — World is ] + if (k === 'F5') return; + const tab = ADMIN_TABS.find((t) => t.key === k || t.keyCode === k); + if (tab) this.openTab(tab.id); + } + + refresh() { + if (!this.open) return; + this._paintTabs(); + const meta = ADMIN_TABS.find((t) => t.id === this._tab); + const sub = this.el.querySelector('[data-sub]'); + if (sub && meta) sub.textContent = meta.blurb; + const stats = draftStats(); + const st = this.el.querySelector('[data-stats]'); + if (st) st.textContent = `Drafts ${stats.total}/${stats.max}`; + + const body = this.el.querySelector('[data-body]'); + if (!body) return; + switch (this._tab) { + case 'player': + body.innerHTML = this._htmlPlayer(); + this._bindPlayer(body); + break; + case 'assets': + body.innerHTML = this._htmlCreateSave('assets'); + this._bindCreateSave(body, 'assets'); + break; + case 'creatures': + body.innerHTML = this._htmlCreateSave('creatures'); + this._bindCreateSave(body, 'creatures'); + break; + case 'prefabs': + body.innerHTML = this._htmlPrefabs(); + this._bindPrefabs(body); + break; + case 'world': + body.innerHTML = this._htmlWorld(); + this._bindWorld(body); + break; + default: + body.innerHTML = '

Unknown tab

'; + } + } + + /* ── F1 Player ─────────────────────────────────────────────── */ + + _htmlPlayer() { + const c = this.character; + const s = c?.getLabSummary?.() || {}; + const snap = this.session?.snapshot?.() || {}; + const eq = c?.equipment?.loadout || {}; + const raceOpts = RACE_IDS.map((rid) => { + const lab = raceDef?.(rid)?.label || rid; + return ``; + }).join(''); + + const act = typeof window !== 'undefined' ? window.app?.activityMode || 'combat' : 'combat'; + return ` +
+

Hero session

+
Race${s.raceLabel || s.raceId || '—'}
+
Height${(s.heightM ?? 0).toFixed?.(2) ?? s.heightM ?? '—'} m
+
Cast mode${snap.mode || '—'}
+
Activity${act} · Hold Q radial
+
DRC${snap.drc || '—'} · Shift+Q equip
+
Anim pack${s.animPackId || '—'}
+
Main hand${eq.mainHand || s.weaponSlot || '—'}
+
Back slotwindsurf vehicle
+
+
+

Activity · equipment

+

Hold Q = mode radial · Tap Q combat = weapon 1↔2 · Hold R harvest tools · F nearest · I paperdoll.

+ +
+ + + +
+
+ + + + +
+
+
+

Main Panel · inventory / equipment

+

LMB pick · RMB item menu (Equip/Unequip/Split/Drop) · paperdoll slots · bag · mini bag (B) · API tab = fleet panel + Railway.

+
+ + + + + + ui Equipment ↗ + Craft ↗ +
+
+
+

Windsurf / freeride

+

M Surf mode · Space deploy · W thrust · coast on water · Space hop · E dismount · ranged = non-focus path cast on board.

+
+ +
+
+
+

Identity / fleet

+

Player · bag · wallet = Railway. Lab drafts local. Catalog = ObjectStore.

+ +
+ `; + } + + _bindPlayer(body) { + body.querySelector('[data-act="open-lab"]')?.addEventListener('click', () => { + this.setOpen(false); + window.__castingInventory?.openTab?.('character'); + }); + body.querySelector('[data-act="open-character"]')?.addEventListener('click', () => { + this.setOpen(false); + window.__castingInventory?.openTab?.('character'); + }); + body.querySelector('[data-act="open-api"]')?.addEventListener('click', () => { + this.setOpen(false); + window.__castingInventory?.openTab?.('api'); + }); + body.querySelector('[data-act="open-slots"]')?.addEventListener('click', () => { + this.setOpen(false); + if (typeof window !== 'undefined' && window.__castingInventory?.openTab) { + window.__castingInventory.openTab('slots'); + } else { + this.onOpenInventoryPrefabs?.(); + } + }); + body.querySelector('[data-act="open-inventory"]')?.addEventListener('click', () => { + this.setOpen(false); + window.__castingInventory?.openTab?.('inventory'); + }); + body.querySelector('[data-act="open-professions"]')?.addEventListener('click', () => { + this.setOpen(false); + window.__castingInventory?.openTab?.('professions'); + }); + body.querySelector('[data-act="equip-t0-sword"]')?.addEventListener('click', async () => { + try { + await ensureWeaponCatalog(); + await equipWeaponById('t0-sword', { + character: this.character, + onToast: this.onToast + }); + this.onToast('Equipped T0 Training Sword'); + } catch (e) { + this.onToast(e?.message || 'Equip failed'); + } + }); + body.querySelector('[data-act="equip-t0-tool"]')?.addEventListener('click', async () => { + try { + await ensureWeaponCatalog(); + await equipWeaponById('t0-tool', { + character: this.character, + onToast: this.onToast + }); + this.onToast('Equipped T0 Tool · F harvest'); + } catch (e) { + this.onToast(e?.message || 'Equip failed'); + } + }); + body.querySelector('[data-act="equip-t0-wand"]')?.addEventListener('click', async () => { + try { + await ensureWeaponCatalog(); + await equipWeaponById('t0-wand', { + character: this.character, + onToast: this.onToast + }); + this.onToast('Equipped T0 Wand · freeride path cast OK'); + } catch (e) { + this.onToast(e?.message || 'Equip failed'); + } + }); + body.querySelector('[data-act="mode-combat"]')?.addEventListener('click', () => { + window.app?.setActivityMode?.('combat'); + this.onToast('Activity · COMBAT'); + this.refresh(); + }); + body.querySelector('[data-act="mode-harvest"]')?.addEventListener('click', () => { + window.app?.setActivityMode?.('harvest'); + this.onToast('Activity · HARVEST'); + this.refresh(); + }); + body.querySelector('[data-act="open-bag"]')?.addEventListener('click', () => { + this.setOpen(false); + window.app?.dropBag?.toggle?.(); + }); + body.querySelector('[data-act="surf-mode"]')?.addEventListener('click', () => { + this.setOpen(false); + window.app?.setMode?.('walk'); + this.onToast('Surf · Space deploy windsurf'); + }); + body.querySelector('[data-race]')?.addEventListener('change', async (e) => { + const id = e.target.value; + try { + await this.character?.setRace?.(id); + this.onToast(`Race · ${raceDef?.(id)?.label || id}`); + this.refresh(); + } catch (err) { + this.onToast(err?.message || 'Race failed'); + } + }); + } + + /* ── F2 / F3 create + save ─────────────────────────────────── */ + + _htmlCreateSave(tabId) { + const kinds = kindsForAdminTab(tabId); + if (!kinds.find((k) => k.id === this._kind)) this._kind = kinds[0]?.id || 'buildable'; + const drafts = listDrafts({ adminTab: tabId }); + const sel = this._selected && this._selected.adminTab === tabId ? this._selected : null; + const val = sel ? validateDeployableDraft(sel) : null; + + const kindOpts = kinds + .map((k) => ``) + .join(''); + + let extraFields = ''; + if (tabId === 'assets') { + const purp = BUILDABLE_PURPOSES.map( + (p) => + `` + ).join(''); + extraFields = ` + +

Scripts + purpose define place / interact / harvest jobs. Not decoration-only unless purpose=decoration.

+ `; + } + if (tabId === 'creatures') { + const roles = CREATURE_ROLES.map( + (r) => + `` + ).join(''); + const brains = AI_BRAINS.map( + (b) => + `` + ).join(''); + const races = RACE_IDS.map( + (r) => + `` + ).join(''); + extraFields = ` + + + +

Models + anim packs + AI brains. CDN: grudge6 race GLBs. Training dummy role for arena targets.

+ `; + } + + const listHtml = + drafts.length === 0 + ? `

No local drafts yet — create one below.

` + : `
${drafts + .map( + (d) => ` + ` + ) + .join('')}
`; + + const detail = sel + ? ` +
+

Selected · ${escapeHtml(sel.name)}

+
id${escapeHtml(sel.id)}
+
kind${sel.kind}
+
score${val.score}
+ ${ + val.warnings?.length + ? val.warnings.map((w) => `

⚠ ${escapeHtml(w)}

`).join('') + : '' + } +
+ ${(sel.jobs || []).map((j) => `${j}`).join('')} +
+ ${ + sel.layers?.purpose + ? `

Purpose: ${sel.layers.purpose.label} — ${escapeHtml(sel.layers.purpose.note || '')}

` + : '' + } + ${ + sel.layers?.script?.module + ? `
${escapeHtml(JSON.stringify(sel.layers.script.module, null, 2))}
` + : '' + } + ${ + sel.layers?.kit + ? `

Kit: ${escapeHtml(sel.layers.kit.raceId)} · ${escapeHtml(sel.layers.kit.kitUrl || '')}

+

AI: ${escapeHtml(sel.layers.ai?.brain || '—')} · HP ${sel.layers.combat?.hp ?? '—'}

` + : '' + } +
+ + + + +
+
` + : ''; + + return ` +
+

${tabId === 'assets' ? 'Buildables & assets' : 'Creatures & kits'}

+

Create drafts · save locally · export for ObjectStore / CDN deploy. No UUID mint in lab.

+ + + ${extraFields} +
+ + + +
+
+
+

Local drafts (${drafts.length})

+ ${listHtml} +
+ ${detail} +
+

Authorities

+

CDN ${DEPLOYABLE_ENDPOINTS.cdn}

+

${kinds.map((k) => k.authority).join(' · ')}

+
+ `; + } + + _bindCreateSave(body, tabId) { + body.querySelector('[data-kind]')?.addEventListener('change', (e) => { + this._kind = e.target.value; + this.refresh(); + }); + body.querySelectorAll('[data-field]').forEach((el) => { + el.addEventListener('change', () => { + this._form[el.dataset.field] = el.value; + }); + el.addEventListener('input', () => { + if (el.dataset.field === 'name') this._form.name = el.value; + }); + }); + body.querySelectorAll('[data-pick]').forEach((btn) => { + btn.addEventListener('click', () => { + this._selected = getDraft(btn.dataset.pick); + this.refresh(); + }); + }); + body.querySelector('[data-act="create"]')?.addEventListener('click', () => { + const name = this._form.name || `New ${this._kind}`; + const fields = { + name, + purpose: this._form.purpose, + role: this._form.role, + raceId: this._form.raceId, + brain: this._form.brain + }; + try { + const d = createAndSaveDraft(this._kind, fields); + this._selected = d; + this._form.name = ''; + this.onToast(`Saved draft · ${d.name}`); + this.refresh(); + } catch (e) { + this.onToast(e?.message || 'Save failed'); + } + }); + body.querySelector('[data-act="save"]')?.addEventListener('click', () => { + if (!this._selected) return; + this._selected = saveDraft(this._selected); + this.onToast('Draft saved'); + this.refresh(); + }); + body.querySelector('[data-act="export"]')?.addEventListener('click', () => { + if (!this._selected) return; + downloadDraftExport(this._selected); + this.onToast('Exported deployable JSON'); + }); + body.querySelector('[data-act="dup"]')?.addEventListener('click', () => { + if (!this._selected) return; + const d = duplicateDraft(this._selected.id); + this._selected = d; + this.onToast('Duplicated'); + this.refresh(); + }); + body.querySelector('[data-act="del"]')?.addEventListener('click', () => { + if (!this._selected) return; + deleteDraft(this._selected.id); + this._selected = null; + this.onToast('Deleted'); + this.refresh(); + }); + const fileIn = body.querySelector('[data-import]'); + body.querySelector('[data-act="import"]')?.addEventListener('click', () => fileIn?.click()); + fileIn?.addEventListener('change', async () => { + const file = fileIn.files?.[0]; + if (!file) return; + try { + const text = await file.text(); + const data = JSON.parse(text); + const d = importDraftJson(data); + this._selected = d; + this.onToast(`Imported · ${d.name}`); + this.refresh(); + } catch (e) { + this.onToast(e?.message || 'Import failed'); + } finally { + fileIn.value = ''; + } + }); + } + + /* ── F4 Prefabs ────────────────────────────────────────────── */ + + _htmlPrefabs() { + const drafts = listDrafts({ adminTab: 'prefabs' }); + const weapons = (() => { + try { + return listEquippableWeapons() || []; + } catch { + return []; + } + })(); + const t0 = weapons.filter((w) => w.tier === 0).slice(0, 16); + const sel = this._selected?.adminTab === 'prefabs' ? this._selected : null; + const val = sel ? validateDeployableDraft(sel) : null; + const measure = measureWeaponScale(this.character); + const scaleReadout = formatScaleReadout(measure); + const appId = sel?.id || this._labWeaponId || ''; + const app = appId ? getAppearance(appId) : {}; + const scaleVal = app.scale ?? 1; + + const t0Html = t0.length + ? t0 + .map( + (w) => + `` + ) + .join('') + : `

Catalog not loaded — press Refresh catalog.

`; + + const draftHtml = + drafts.length === 0 + ? `

No weapon/armour drafts. Create from T0 or blank.

` + : drafts + .map( + (d) => + `` + ) + .join(''); + + return ` +
+

Weapon & item prefabs

+

Edit · import GLB · assign model · live SI scale. Scaffold / UUID mint stays ObjectStore.

+
+ + + + +
+
+ ${PREFAB_CATEGORIES.map((c) => `${c.label}`).join('')} +
+
+ +
+

Live weapon scale & mesh

+

${escapeHtml(scaleReadout)}

+
SI length${measure ? measure.lengthM.toFixed(2) + ' m' : '—'}
+
Lab scale×${Number(scaleVal).toFixed(2)}
+ + + + +
+ + + + +
+

Select a T0 or draft first. Import attaches a local GLB blob to that prefab id. Scale is live on the hero hand.

+
+ +
+

T0 starters (equip / draft)

+
${t0Html}
+
+
+

Local prefab drafts

+
${draftHtml}
+
+ ${ + sel + ? `
+

${escapeHtml(sel.name)}

+
score${val.score}
+
model${escapeHtml(sel.modelUrl || '—')}
+ ${val.warnings?.map((w) => `

⚠ ${escapeHtml(w)}

`).join('') || ''} +
+ + + +
+
` + : '' + } +
+

Endpoints

+

Item browser · + Weapon skills

+

${SCAFFOLD_ENDPOINTS.weaponPrefabs}

+

Pipeline: ${DEPLOYABLE_ENDPOINTS.pipelines.weapons}

+
+ `; + } + + _bindPrefabs(body) { + const refreshMeasure = () => { + const m = measureWeaponScale(this.character); + const el = body.querySelector('[data-scale-readout]'); + const len = body.querySelector('[data-len-m]'); + const lab = body.querySelector('[data-lab-scale]'); + if (el) el.textContent = formatScaleReadout(m); + if (len) len.textContent = m ? `${m.lengthM.toFixed(2)} m` : '—'; + if (lab) { + const id = this._selected?.id || this._labWeaponId; + const sc = id ? getAppearance(id).scale ?? 1 : 1; + lab.textContent = `×${Number(sc).toFixed(2)}`; + } + }; + + body.querySelector('[data-act="refresh-cat"]')?.addEventListener('click', async () => { + try { + await ensureWeaponCatalog(); + this._scaffold = await loadPrefabScaffold(); + this.onToast('Catalog + scaffold loaded'); + this.refresh(); + } catch (e) { + this.onToast(e?.message || 'Load failed'); + } + }); + body.querySelector('[data-act="open-inv"]')?.addEventListener('click', () => { + this.onOpenInventoryPrefabs?.(); + }); + body.querySelector('[data-act="new-weapon"]')?.addEventListener('click', () => { + const d = createAndSaveDraft('weapon', { name: 'New weapon draft', weaponType: 'SWORD' }); + this._selected = d; + this._labWeaponId = d.id; + this.onToast('Weapon draft saved'); + this.refresh(); + }); + body.querySelector('[data-act="new-armour"]')?.addEventListener('click', () => { + const d = createAndSaveDraft('armour', { name: 'New armour draft', slot: 'body' }); + this._selected = d; + this.onToast('Armour draft saved'); + this.refresh(); + }); + + // ── Live scale / import / assign ──────────────────────────── + body.querySelector('[data-wlab-scale]')?.addEventListener('input', (e) => { + const id = this._selected?.id || this._labWeaponId; + if (!id || !this.character) return; + const sc = Number(e.target.value) || 1; + setLiveWeaponScale(id, sc, this.character); + refreshMeasure(); + }); + const applyEuler = () => { + const id = this._selected?.id || this._labWeaponId; + if (!id || !this.character) return; + const pitch = Number(body.querySelector('[data-wlab-pitch]')?.value) || 0; + const yaw = Number(body.querySelector('[data-wlab-yaw]')?.value) || 0; + const scale = Number(body.querySelector('[data-wlab-scale]')?.value) || 1; + setLiveWeaponAppearance(id, { scale, eulerDeg: [pitch, yaw, 0] }, this.character); + refreshMeasure(); + }; + body.querySelector('[data-wlab-yaw]')?.addEventListener('input', applyEuler); + body.querySelector('[data-wlab-pitch]')?.addEventListener('input', applyEuler); + + body.querySelector('[data-act="wlab-equip"]')?.addEventListener('click', async () => { + const id = this._selected?.id || this._labWeaponId; + if (!id) return this.onToast('Select a weapon draft or T0 first'); + try { + const url = body.querySelector('[data-wlab-url]')?.value?.trim(); + const catalogId = id.replace(/^draft-from-/, ''); + await equipWeaponForLab( + catalogId.startsWith('t0-') ? catalogId : id, + { character: this.character, onToast: this.onToast }, + url ? { modelUrl: url } : {} + ); + this._labWeaponId = id; + this.onToast(`Equipped · ${formatScaleReadout(measureWeaponScale(this.character))}`); + refreshMeasure(); + } catch (e) { + this.onToast(e?.message || 'Equip failed'); + } + }); + + body.querySelector('[data-act="wlab-assign"]')?.addEventListener('click', () => { + if (!this._selected?.id) return this.onToast('Select a draft'); + const url = body.querySelector('[data-wlab-url]')?.value?.trim(); + if (!url) return this.onToast('Paste a modelUrl or import GLB first'); + try { + const scale = Number(body.querySelector('[data-wlab-scale]')?.value) || 1; + this._selected = assignModelToDraft(this._selected.id, url, { scale }); + this.onToast(`Assigned model → ${this._selected.name}`); + this.refresh(); + } catch (e) { + this.onToast(e?.message || 'Assign failed'); + } + }); + + body.querySelector('[data-wlab-import]')?.addEventListener('change', async (e) => { + const file = e.target.files?.[0]; + if (!file) return; + let id = this._selected?.id; + if (!id) { + const d = createAndSaveDraft('weapon', { + name: file.name.replace(/\.(glb|gltf)$/i, ''), + weaponType: 'SWORD' + }); + this._selected = d; + id = d.id; + } + try { + const rec = await importWeaponGlb(file, id); + this._selected = assignModelToDraft(id, rec.url, { name: rec.name }); + body.querySelector('[data-wlab-url]').value = rec.url; + await equipWeaponForLab(id, { character: this.character, onToast: this.onToast }, { + modelUrl: rec.url + }); + this._labWeaponId = id; + this.onToast(`Imported ${rec.name} · ${formatScaleReadout(measureWeaponScale(this.character))}`); + this.refresh(); + } catch (err) { + this.onToast(err?.message || 'Import failed'); + } + }); + + body.querySelector('[data-act="wlab-measure"]')?.addEventListener('click', () => { + refreshMeasure(); + this.onToast(formatScaleReadout(measureWeaponScale(this.character))); + }); + + body.querySelectorAll('[data-pick]').forEach((btn) => { + btn.addEventListener('click', () => { + this._selected = getDraft(btn.dataset.pick); + this._labWeaponId = this._selected?.id; + this.refresh(); + }); + }); + body.querySelectorAll('[data-t0]').forEach((btn) => { + btn.addEventListener('click', async () => { + const id = btn.dataset.t0; + try { + await ensureWeaponCatalog(); + const w = listEquippableWeapons().find((x) => x.id === id); + if (!w) throw new Error('Weapon not found'); + // Equip immediately so scale is visible on the hero + await equipWeaponById(id, { character: this.character, onToast: this.onToast }); + this._labWeaponId = id; + const pack = await buildItemScaffoldPack(w); + downloadJson(pack, `scaffold-${id}.json`); + // also save a draft linked to catalog id + const d = createAndSaveDraft('weapon', { + id: `draft-from-${id}`, + name: w.name, + weaponType: w.weaponType, + tier: w.tier, + meshSlot: w.meshSlot, + animPack: w.animPack, + stats: w.stats, + iconUrl: w.iconUrl, + modelUrl: w.modelUrl + }); + if (w.uuid) d.notes = `Source catalog uuid ${w.uuid} — do not re-mint`; + d.layers.skills = { + slots: [], + skillUuids: [w.slot1?.uuid, w.slot2?.uuid].filter(Boolean), + note: 'From T0 catalog — full bodies in scaffold export' + }; + this._selected = saveDraft(d); + this._labWeaponId = d.id; + this.onToast( + `Equipped + draft · ${w.name} · ${formatScaleReadout(measureWeaponScale(this.character))}` + ); + this.refresh(); + } catch (e) { + this.onToast(e?.message || 'Scaffold failed'); + } + }); + }); + body.querySelector('[data-act="export"]')?.addEventListener('click', () => { + if (!this._selected) return; + downloadDraftExport(this._selected); + this.onToast('Exported'); + }); + body.querySelector('[data-act="scaffold"]')?.addEventListener('click', async () => { + try { + await ensureWeaponCatalog(); + const w = + listEquippableWeapons().find((x) => x.id === this._selected?.id?.replace(/^draft-from-/, '')) || + listEquippableWeapons().find((x) => x.name === this._selected?.name); + if (w) { + const pack = await buildItemScaffoldPack(w); + downloadJson(pack, `scaffold-${w.id}.json`); + this.onToast('Scaffold pack downloaded'); + } else { + downloadDraftExport(this._selected); + this.onToast('No catalog match — exported draft'); + } + } catch (e) { + this.onToast(e?.message || 'Failed'); + } + }); + body.querySelector('[data-act="del"]')?.addEventListener('click', () => { + if (!this._selected) return; + deleteDraft(this._selected.id); + this._selected = null; + this.onToast('Deleted'); + this.refresh(); + }); + } + + /* ── ] World ──────────────────────────────────────────────── */ + + _htmlWorld() { + return ` +
+

World / Dev Island ]

+

Baked rocks · harvest · dummies · windsurf pad. Hold Q mode · Hold R tools · F nearest ≤5 m.

+
+ + + + + + Training Room editor ↗ +
+

Training Room = DevIsland = /devnode — one island map (terrain L0–L3 + harvest + PvE). Export layout → play.

+
+
+

Input contract (Open parity)

+
Hold QMode radial · ↑ combat · ↓ harvest
+
Tap QCombat: swap Weapon 1 ↔ 2 (skills+loco)
+
Hold RTool radial (harvest only)
+
Fpickup → harvest ≤5 m → skill
+
Shift+QEquip session (lab panel)
+
I / BMain Panel · Mini bag
+
M · Space · ESurf · deploy/hop · dismount
+
+
+

Harvest / windsurf

+
Range5 m
+
Meshespublic/models/dev-island/rock__*
+
Board art yaw+90° travel frame
+
Water coastlow freerideDrag · release W glides
+
Ranged freeridenon-focus path cast
+
+
+

Deploy surfaces

+
Labcasting.grudge-studio.com
+
Openopen.grudge-studio.com
+
Forgeforge.grudge-studio.com
+
CDNassets.grudge-studio.com
+
Cataloginfo.grudge-studio.com
+
+
+

What we deploy from hub

+
+ ${DEPLOYABLE_KINDS.map((k) => `${k.label}`).join('')} +
+

Create/save here → export JSON → ObjectStore pipeline + R2 → fleet clients consume.

+
+ `; + } + + _bindWorld(body) { + body.querySelector('[data-act="loot"]')?.addEventListener('click', () => { + this.spawnLoot?.(3); + }); + body.querySelector('[data-act="harvest"]')?.addEventListener('click', async () => { + try { + await this.respawnHarvest?.(); + this.onToast('Harvest nodes respawned'); + } catch (e) { + this.onToast(e?.message || 'Harvest respawn failed'); + } + }); + body.querySelector('[data-act="tool"]')?.addEventListener('click', async () => { + try { + await this.equipHarvestTool?.(); + } catch (e) { + this.onToast(e?.message || 'Tool equip failed'); + } + }); + body.querySelector('[data-act="dummies"]')?.addEventListener('click', () => { + this.respawnDummies?.(); + this.onToast('Training dummies respawned'); + }); + body.querySelector('[data-act="help"]')?.addEventListener('click', () => { + this.onHelp?.(); + }); + } + + dispose() { + this.el?.remove(); + this.chips?.remove(); + } +} + +function escapeHtml(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/src/ui/CastBar.js b/src/ui/CastBar.js new file mode 100644 index 0000000..80a6a2f --- /dev/null +++ b/src/ui/CastBar.js @@ -0,0 +1,106 @@ +/** + * CraftPix cast bar — under-crosshair cast progress for Warlords casting. + * Textures: public/ui/craftpix/cast/{bg,fill,icon_frame}.png + */ +import { craftpixUrl } from './craftpixUi.js'; + +export class CastBar { + /** + * @param {HTMLElement} host HUD root + */ + constructor(host) { + this.host = host; + this.el = document.createElement('div'); + this.el.className = 'castbar is-hidden'; + this.el.setAttribute('aria-hidden', 'true'); + this.el.innerHTML = ` +
+
+
+
+
+
Casting
+
+
+
+
+
+
+
+ 0.0s + +
+
+ `; + host.appendChild(this.el); + + // Apply CraftPix images via CSS vars (also set on host by applyCraftpixCssVars) + this.el.style.setProperty('--cp-cast-bg', `url("${craftpixUrl('cast/bg.png')}")`); + this.el.style.setProperty('--cp-cast-fill', `url("${craftpixUrl('cast/fill.png')}")`); + this.el.style.setProperty('--cp-cast-icon', `url("${craftpixUrl('cast/icon_frame.png')}")`); + + this._name = this.el.querySelector('[data-cast-name]'); + this._fill = this.el.querySelector('[data-cast-fill]'); + this._time = this.el.querySelector('[data-cast-time]'); + this._left = this.el.querySelector('[data-cast-left]'); + this._icon = this.el.querySelector('[data-cast-icon]'); + this._visible = false; + } + + /** + * @param {{ + * active?: boolean, + * label?: string, + * progress01?: number, + * duration?: number, + * remaining?: number, + * element?: string, + * interrupted?: boolean + * }|null} state + */ + setState(state) { + if (!state || !state.active) { + this.hide(state?.interrupted); + return; + } + if (!this._visible) { + this.el.classList.remove('is-hidden', 'is-interrupt'); + this.el.setAttribute('aria-hidden', 'false'); + this._visible = true; + } + const p = Math.max(0, Math.min(1, state.progress01 ?? 0)); + const pct = Math.round(p * 100); + if (this._fill) this._fill.style.width = `${pct}%`; + if (this._name) this._name.textContent = state.label || 'Casting'; + const dur = state.duration ?? 0; + const rem = state.remaining ?? Math.max(0, dur * (1 - p)); + if (this._time) this._time.textContent = `${dur.toFixed(1)}s`; + if (this._left) this._left.textContent = rem > 0.05 ? `${rem.toFixed(1)}s` : 'release'; + if (state.element) { + this.el.dataset.element = state.element; + if (this._icon) this._icon.dataset.element = state.element; + } + } + + hide(interrupted = false) { + if (!this._visible && !interrupted) return; + if (interrupted) { + this.el.classList.add('is-interrupt'); + window.setTimeout(() => { + this.el.classList.add('is-hidden'); + this.el.classList.remove('is-interrupt'); + this.el.setAttribute('aria-hidden', 'true'); + this._visible = false; + }, 180); + } else { + this.el.classList.add('is-hidden'); + this.el.setAttribute('aria-hidden', 'true'); + this._visible = false; + } + if (this._fill) this._fill.style.width = '0%'; + } + + dispose() { + this.el.remove(); + } +} diff --git a/src/ui/DropBag.js b/src/ui/DropBag.js new file mode 100644 index 0000000..440bd8c --- /dev/null +++ b/src/ui/DropBag.js @@ -0,0 +1,229 @@ +/** + * Lab bag for world-drop throw / pickup + harvest loot. + * Skin: Warlords miniinventory.png (9×4 + hotbar 1–10 chrome). + * + * @see docs/WARLORDS_DEV_UI_SSOT.md · warlordsUiSkin.js + */ + +import { MINI_INV } from './warlordsUiSkin.js'; +import './dropBag.css'; +import './warlords-dev-ui.css'; +import './itemContextMenu.css'; +import { bindItemContextMenu } from './itemContextMenu.js'; +import { bagAdd } from './mainPanelSlots.js'; +import { MAIN_PANEL_PROD } from './uiAssetCatalog.js'; +import { withResolvedIcon, bagItemFromLoot } from './iconResolve.js'; + +const STORAGE = 'casting.lab.dropBag.v1'; + +export class DropBag { + /** + * @param {{ + * onToast?: (s: string) => void, + * onThrow?: (item: object, clientX: number, clientY: number) => void + * }} opts + */ + constructor(opts = {}) { + this.onToast = opts.onToast || (() => {}); + this.onThrow = opts.onThrow || (() => {}); + /** @type {object[]} */ + this.items = this._load(); + this.open = false; + + this.el = document.createElement('div'); + this.el.id = 'drop-bag'; + this.el.className = 'drop-bag wl-mini-inv'; + this.el.hidden = true; + this.el.setAttribute('aria-label', 'Mini inventory'); + document.body.appendChild(this.el); + this._render(); + } + + _load() { + try { + const raw = localStorage.getItem(STORAGE); + return raw ? JSON.parse(raw) : []; + } catch { + return []; + } + } + + _save() { + localStorage.setItem(STORAGE, JSON.stringify(this.items)); + } + + setOpen(on) { + this.open = !!on; + this.el.hidden = !this.open; + if (this.open) this._render(); + } + + toggle() { + this.setOpen(!this.open); + } + + /** + * @param {object} item bag item + */ + add(item) { + if (!item) return; + const resolved = withResolvedIcon(bagItemFromLoot(item)); + const existing = this.items.find( + (x) => x.id === resolved.id && x.tier === resolved.tier + ); + if (existing) { + existing.qty = (existing.qty || 1) + (resolved.qty || 1); + if (!existing.iconUrl && resolved.iconUrl) { + existing.iconUrl = resolved.iconUrl; + existing.icon = resolved.icon; + } + } else { + this.items.push({ ...resolved, qty: resolved.qty || 1 }); + } + this._save(); + this._render(); + } + + /** + * Remove one qty; returns item or null. + * @param {string} id + */ + takeOne(id) { + const i = this.items.findIndex((x) => x.id === id); + if (i < 0) return null; + const it = this.items[i]; + it.qty = (it.qty || 1) - 1; + const out = { ...it, qty: 1 }; + if (it.qty <= 0) this.items.splice(i, 1); + this._save(); + this._render(); + return out; + } + + _render() { + const bagSlots = MINI_INV.bagSlots; + const hotN = MINI_INV.hotbarSlots; + const cells = []; + for (let i = 0; i < bagSlots; i++) { + const it = this.items[i]; + if (it) { + cells.push(` +
+ ${it.iconUrl ? `` : '' + escapeHtml(it.name || '?') + ''} + ${it.qty || 1} +
`); + } else { + cells.push(`
`); + } + } + + const hotbar = Array.from({ length: hotN }, (_, i) => { + const n = i + 1; + // First 10 bag items can mirror as hotbar preview (visual only — combat bar stays DRC) + const it = this.items[i]; + return ` +
+ ${it?.iconUrl ? `` : ''} +
`; + }).join(''); + + this.el.innerHTML = ` +
+

Bag · harvest loot

+ +
+

Drag → world throw · RMB Equip/Drop menu · F pickup · harvest loot

+
+ ${cells.join('') || '

Empty — harvest nodes or spawn loot (L)

'} +
+ + `; + + this.el.querySelector('[data-close]')?.addEventListener('click', () => this.setOpen(false)); + this.el.addEventListener('pointerdown', (e) => e.stopPropagation()); + this.el.addEventListener('contextmenu', (e) => { + if (!e.target?.closest?.('[data-id]')) e.preventDefault(); + }); + + this.el.querySelectorAll('.drop-bag__slot[draggable="true"]').forEach((slot) => { + slot.addEventListener('dragstart', (e) => { + e.dataTransfer?.setData('text/drop-item-id', slot.dataset.id || ''); + e.dataTransfer.effectAllowed = 'copy'; + slot.classList.add('is-dragging'); + }); + slot.addEventListener('dragend', () => slot.classList.remove('is-dragging')); + + bindItemContextMenu( + slot, + () => { + const id = slot.dataset.id; + const item = this.items.find((x) => x.id === id) || null; + return { source: 'dropbag', dropId: id, item }; + }, + { + onToast: this.onToast, + onEquip: (item) => { + // Move one into Main Panel bag for paperdoll equip + const taken = this.takeOne(item.id); + if (!taken) return; + const r = bagAdd({ + id: taken.id, + name: taken.name, + icon: taken.iconUrl || taken.icon, + kind: taken.kind || taken.category || 'mat', + qty: 1, + tier: taken.tier, + slotHint: taken.slotHint + }); + this.onToast( + r.ok + ? `→ Main bag · I → Inventory · RMB Equip (${taken.name})` + : 'Main bag full' + ); + }, + onDropWorld: (item, cx, cy) => { + const taken = this.takeOne(item.id); + if (taken) this.onThrow?.(taken, cx, cy); + }, + onDeposit: async () => { + window.open(MAIN_PANEL_PROD.craft, '_blank', 'noopener'); + this.onToast('Account bag · Craft SSOT'); + }, + onRefresh: () => this._render() + } + ); + }); + } + + /** + * Call from canvas dragover/drop. + * @param {DragEvent} event + */ + handleCanvasDrop(event) { + const id = event.dataTransfer?.getData('text/drop-item-id'); + if (!id) return false; + const item = this.takeOne(id); + if (!item) return false; + this.onThrow(item, event.clientX, event.clientY); + return true; + } + + dispose() { + this.el.remove(); + } +} + +function escapeHtml(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(//g, '>'); +} + +function escapeAttr(s) { + return escapeHtml(s).replace(/"/g, '"'); +} diff --git a/src/ui/Editor.js b/src/ui/Editor.js index 634a58b..b020d08 100644 --- a/src/ui/Editor.js +++ b/src/ui/Editor.js @@ -1,6 +1,15 @@ import GUI from 'lil-gui'; import { settings, ELEMENTS, ELEMENT_META, MODES } from '../config/settings.js'; import { PresetManager } from './PresetManager.js'; +import { + EFFECT_KINDS, + EFFECT_MESH_IDS, + buildActiveKindPrefab, + buildEffectPrefab, + downloadJson, + STRAWBERRY_MODE_PRESETS, + applyPrimitiveToSettings +} from '../vfx/effectPrefab.js'; /** * Real-time VFX editor. @@ -25,17 +34,21 @@ export class Editor { this._presetState = { name: 'My preset', selected: this.presets.names[0] ?? '' }; this._buildPresets(); + this._buildControls(); + this._buildEffectPrefab(); this._buildGlobal(); this._buildTrail(); for (const element of ELEMENTS) this._buildElement(element); this._buildEnvironment(); this._buildPost(); this._buildCamera(); + this._buildCombatJump(); this._buildCharacter(); this._buildWalk(); - // Element folders start closed — the global block is the common entry point. + // Element folders start closed — Controls + global open for real options. this.gui.folders.forEach((folder) => folder.close()); + this.controlsFolder?.open(); this.globalFolder.open(); } @@ -56,6 +69,17 @@ export class Editor { this.gui.show(!this._hidden); } + /** Force-hide VFX editor (focus mode / combat play). */ + close() { + if (this._hidden) return; + this._hidden = true; + this.gui.show(false); + } + + get isOpen() { + return !this._hidden; + } + /* ------------------------------------------------------------------ */ /* folders */ /* ------------------------------------------------------------------ */ @@ -175,6 +199,134 @@ export class Editor { this.presetFolder = folder; } + /** + * Shared effect primitives — intensity / AOE / speed / size / color / mesh. + * Export JSON prefabs for Warlords skill import. Melee residual = F strike. + */ + _buildEffectPrefab() { + const folder = this.gui.addFolder('⚡ Effect Prefab'); + const e = settings.effect; + const r = settings.residual; + const R = Editor.range; + + folder + .add(e, 'activeKind', EFFECT_KINDS) + .name('active kind') + .onChange(() => this.hooks.onToast?.(`Authoring · ${e.activeKind}`)); + + R(folder, e, 'intensity', 0, 2, 0.01, 'intensity'); + R(folder, e, 'aoe', 0, 8, 0.05, 'aoe (m)'); + R(folder, e, 'speed', 0, 40, 0.1, 'speed (m/s)'); + R(folder, e, 'size', 0.1, 3, 0.01, 'size (SI)'); + folder.addColor(e, 'color').name('color'); + folder.add(e, 'meshId', EFFECT_MESH_IDS).name('mesh id'); + R(folder, e, 'duration', 0.05, 4, 0.01, 'duration (s)'); + folder.add(e, 'attach', ['R_hand', 'L_hand', 'root', 'feet', 'weapon_tip']).name('attach'); + + const residual = folder.addFolder('Melee residual (F attack fallback)'); + residual.add(r, 'enabled').name('enabled'); + R(residual, r, 'range', 1, 10, 0.05, 'range (m)'); + R(residual, r, 'speed', 4, 30, 0.1, 'wave speed'); + R(residual, r, 'meshScale', 0.35, 2.2, 0.01, 'mesh scale'); + R(residual, r, 'contactRadius', 0.2, 2.5, 0.05, 'contact radius'); + R(residual, r, 'aoeRadius', 0, 4, 0.05, 'aoe radius'); + R(residual, r, 'intensity', 0, 2, 0.01, 'intensity'); + residual.addColor(r, 'color').name('color'); + residual.add(r, 'variant', ['slashblue', 'slashred', 'slashpurple', 'slashyellow']).name('slash mesh'); + R(residual, r, 'tipOffset', 0.15, 1.4, 0.01, 'tip offset (m)'); + R(residual, r, 'hitFrameDelay', 0, 0.5, 0.01, 'hit frame delay (apex)'); + R(residual, r, 'beyondBladeM', 0, 1.2, 0.02, 'beyond blade (m)'); + residual.add(r, 'trailEnabled').name('tip trail'); + residual.add(r, 'fireTrail').name('fire blur trail'); + residual.add(r, 'physicsProjectile').name('physics residual'); + residual.add(r, 'pathTrail').name('ability path trail'); + R(residual, r, 'trailDuration', 0.1, 0.8, 0.01, 'trail duration (s)'); + R(residual, r, 'trailWidth', 0.04, 0.4, 0.01, 'trail width (m)'); + residual.addColor(r, 'trailColor').name('trail color'); + + const berry = folder.addFolder('Strawberry modes (learn)'); + for (const [mode, preset] of Object.entries(STRAWBERRY_MODE_PRESETS)) { + berry + .add( + { + apply: () => { + applyPrimitiveToSettings({ ...preset, color: preset.color || e.color }); + this.refresh(); + this.hooks.onToast?.(`Strawberry · ${mode}`); + } + }, + 'apply' + ) + .name(`Apply ${mode}`); + } + + folder + .add( + { + exportSolo: () => { + const prefab = buildActiveKindPrefab(); + downloadJson(prefab, `${prefab.id}.json`); + this.hooks.onToast?.(`Exported ${prefab.id}`); + } + }, + 'exportSolo' + ) + .name('Export active kind JSON'); + + folder + .add( + { + exportMelee: () => { + const prefab = buildEffectPrefab('drc_melee_strike'); + downloadJson(prefab, 'prefab_drc_melee_strike.json'); + this.hooks.onToast?.('Exported melee residual prefab'); + } + }, + 'exportMelee' + ) + .name('Export melee residual JSON'); + + folder + .add( + { + exportFire: () => { + const prefab = buildEffectPrefab('drc_fire_bolt'); + downloadJson(prefab, 'prefab_drc_fire_bolt.json'); + this.hooks.onToast?.('Exported fire bolt prefab'); + } + }, + 'exportFire' + ) + .name('Export fire bolt JSON'); + + folder + .add( + { + exportAll: () => { + const ids = [ + 'drc_melee_strike', + 'drc_fire_bolt', + 'drc_water_lash', + 'drc_earth_spike', + 'drc_wind_tempest' + ]; + const pack = { + source: 'casting-lab', + version: '1.0.0', + exportedAt: new Date().toISOString(), + prefabs: ids.map((id) => buildEffectPrefab(id)) + }; + downloadJson(pack, 'casting-effect-prefabs.json'); + this.hooks.onToast?.(`Exported ${pack.prefabs.length} prefabs`); + } + }, + 'exportAll' + ) + .name('Export all skill prefabs'); + + this.effectFolder = folder; + } + _buildGlobal() { const folder = this.gui.addFolder('Global'); const g = settings.global; @@ -242,7 +394,16 @@ export class Editor { _buildElement(element) { const meta = ELEMENT_META[element]; const folder = this.gui.addFolder(`${meta.glyph} ${meta.label}`); - const c = settings[element]; + // Product ids: ice/storm/nature alias ability blocks; holy/arcane own blocks + const c = + settings[element] || + (element === 'ice' + ? settings.water + : element === 'storm' + ? settings.wind + : element === 'nature' + ? settings.earth + : settings.fire); const R = Editor.range; R(folder, c, 'speed', 0.5, 40, 0.1, 'speed'); @@ -507,7 +668,20 @@ export class Editor { } }; - build[element]?.(); + // Product elements map to ability-shaped editors (ice→water, storm→wind, …) + const buildKey = + { + fire: 'fire', + ice: 'water', + water: 'water', + nature: 'earth', + earth: 'earth', + storm: 'wind', + wind: 'wind', + holy: 'wind', + arcane: 'wind' + }[element] || element; + build[buildKey]?.(); const light = folder.addFolder('Dynamic light'); Editor.range(light, c, 'lightIntensity', 0, 80, 0.1, 'light intensity'); @@ -576,6 +750,59 @@ export class Editor { R(folder, p, 'flashStrength', 0, 2, 0.01, 'impact flash'); } + /** + * Real control options: sprint/focus toggles, look, hotkey reference. + */ + _buildControls() { + const folder = this.gui.addFolder('Controls'); + this.controlsFolder = folder; + const c = settings.controls; + const aim = settings.aim; + const R = Editor.range; + + folder + .add(c, 'sprintToggle') + .name('Sprint = toggle (Shift press)') + .onChange((v) => this.hooks.onToast?.(v ? 'Sprint: toggle' : 'Sprint: hold')); + folder + .add(c, 'focusToggle') + .name('Focus = toggle (RMB click)') + .onChange((v) => this.hooks.onToast?.(v ? 'Focus: toggle' : 'Focus: hold RMB')); + folder.add(c, 'showHotkeyChips').name('Show hotkey chips'); + folder.add(c, 'invertLookY').name('Invert look Y'); + R(folder, c, 'lookSensitivity', 0.25, 3, 0.05, 'Look sensitivity'); + + const aimF = folder.addFolder('Aim / crosshair'); + aimF.add(aim, 'enabled').name('Aim enabled'); + aimF.add(aim, 'crosshair').name('Crosshair'); + aimF.add(aim, 'groundMarker').name('Ground ring (AoE/place only)'); + aimF.add(aim, 'groundMarkerOnPathDraw').name('Ground ring on path draw'); + R(aimF, aim, 'softLockBlend', 0, 1, 0.01, 'Soft-lock blend'); + R(aimF, aim, 'focusTurnSpeed', 2, 20, 0.5, 'Focus body turn (lower=less spin)'); + R(aimF, aim, 'focusTurnDeadzoneDeg', 0, 45, 1, 'Focus turn deadzone °'); + aimF.add(aim, 'focusTurnOnlyWhenMoving').name('Focus turn only when moving'); + R(aimF, aim, 'tankTurnSpeed', 0.5, 8, 0.1, 'Free A/D turn'); + + const keys = folder.addFolder('Hotkeys (reference)'); + const hotkeyState = { + move: 'WASD', + sprint: 'Shift (toggle/hold)', + focus: 'RMB click (toggle) / hold', + attack: 'LMB focus · F strike', + jump: 'Space · 2nd = frontflip · S+Space backflip', + skills: '1–4', + dodge: 'AA / DD / WW / X', + roll: 'Ctrl+WASD', + slide: 'Shift+Ctrl sprint', + editor: 'G · Lab I · Showcase O' + }; + for (const [k, v] of Object.entries(hotkeyState)) { + keys.add(hotkeyState, k).name(k).disable(); + } + keys.close(); + folder.open(); + } + _buildCamera() { const folder = this.gui.addFolder('Camera'); const c = settings.camera; @@ -588,14 +815,42 @@ export class Editor { R(folder, c, 'zoomSpeed', 0.1, 3, 0.01, 'zoom speed'); R(folder, c, 'fov', 20, 90, 0.5, 'field of view'); R(folder, c, 'targetHeight', 0, 4, 0.01, 'target height'); - R(folder, c, 'minPolar', 0.05, 1.5, 0.01, 'min pitch'); - R(folder, c, 'maxPolar', 0.2, 1.55, 0.01, 'max pitch'); - R(folder, c, 'damping', 0.001, 0.5, 0.001, 'follow damping'); + R(folder, c, 'minPolar', 0.05, 1.5, 0.01, 'min pitch (orbit)'); + R(folder, c, 'maxPolar', 0.2, 1.55, 0.01, 'max pitch (orbit)'); + R(folder, c, 'minPitch', 0.05, 1.2, 0.01, 'min pitch (TPS)'); + R(folder, c, 'maxPitch', 0.2, 1.5, 0.01, 'max pitch (TPS)'); + R(folder, c, 'tpsDefaultPitch', 0.05, 1.0, 0.01, 'default TPS pitch'); + R(folder, c, 'shoulderOffset', 0, 1.5, 0.01, 'shoulder offset m'); + R(folder, c, 'shoulderSide', -1, 1, 1, 'shoulder side −1/0/+1'); + R(folder, c, 'orbitSensitivity', 0.001, 0.012, 0.0001, 'orbit / look base sens'); + R(folder, c, 'softLockLook', 0, 1, 0.01, 'soft-lock look weight'); + R(folder, c, 'tpsDamping', 0.02, 0.5, 0.01, 'TPS follow damping'); + R(folder, c, 'damping', 0.001, 0.5, 0.001, 'orbit follow damping'); R(folder, c, 'autoFrame', 0, 1, 0.01, 'auto framing'); folder.add({ clear: () => this.hooks.onClear?.() }, 'clear').name('Clear effects (C)'); } + _buildCombatJump() { + const folder = this.gui.addFolder('Jump / air'); + const d = settings.drc; + const R = Editor.range; + R(folder, d, 'jumpVelocity', 2, 12, 0.1, 'Jump velocity'); + R(folder, d, 'doubleJumpVelocity', 2, 12, 0.1, 'Frontflip jump V'); + R(folder, d, 'frontflipDuration', 0.2, 1.2, 0.01, 'Frontflip duration'); + R(folder, d, 'frontflipSpeed', 0, 10, 0.1, 'Frontflip push m/s'); + R(folder, d, 'backflipVertical', 0.5, 8, 0.1, 'Backflip up V'); + R(folder, d, 'backflipSpeed', 1, 14, 0.1, 'Backflip reverse m/s'); + R(folder, d, 'backflipDuration', 0.2, 1.2, 0.01, 'Backflip spin s'); + R(folder, d, 'backflipHardStop', 0, 0.35, 0.01, 'Hard stop s'); + R(folder, d, 'backflipHangGravity', 0.1, 1, 0.01, 'Hang gravity scale'); + R(folder, d, 'backflipHangDuration', 0.2, 2.5, 0.05, 'Hang duration s'); + folder + .add({ tip: 'Space · Space air=frontflip · S+Space=backflip hang' }, 'tip') + .name('Tip') + .disable(); + } + _buildCharacter() { const folder = this.gui.addFolder('Character'); const c = settings.character; diff --git a/src/ui/HUD.js b/src/ui/HUD.js index 17c1795..081dc2f 100644 --- a/src/ui/HUD.js +++ b/src/ui/HUD.js @@ -1,47 +1,129 @@ import { ELEMENTS, ELEMENT_META, MODES, MODE_META } from '../config/settings.js'; import { ELEMENT_SIGILS } from './glyphs.js'; +import { getActiveSkills, skillForFKey } from '../combat/drcSkills.js'; +import { getSkillBinding } from '../combat/skillBindings.js'; +import { TightBar } from './TightBar.js'; +import { + applyCraftpixCssVars, + preloadCraftpixUi, + racePortraitUrl +} from './craftpixUi.js'; +import { applyBarsHudCssVars, preloadBarsHudUi } from './barsHudUi.js'; +import { CastBar } from './CastBar.js'; +import './tightBar.css'; +import './showcase.css'; +import './craftpix-hud.css'; +import './bars-hud.css'; +import './castbar.css'; /** - * Heads-up display: mode switch, element selector, controls, live stats and - * toasts. + * Production-style combat HUD for Warlords casting frontend: + * - Player frame (self) top-left — real race/name, live STA/MP + * - Target frame top-right — only when soft-lock has a target + * - Ally strip — hidden until party members exist (no fake A1–A3) + * - Bottom action bar (1–6 elements + F residual) with CD overlays + * - Mode switch + compact help * - * Plain DOM — no framework. The switches are the only interactive parts; they - * mirror the keyboard shortcuts and report back through `onSelect` / `onMode`. + * CraftPix / HYDRA layouts are the art SSOT — no invented Main Panel. */ export class HUD { constructor(root) { this.root = root; this.onSelect = null; this.onMode = null; + this.onSkillSlot = null; + this.onMelee = null; + /** @type {((actionId: string) => void)|null} */ + this.onQuickAction = null; + /** @type {((menuId: string) => void)|null} */ + this.onMenu = null; this._toastTimer = 0; this._statsAccumulator = 0; this._frames = 0; this._fps = 0; + this._drcSession = 'combat'; + this._hp = 1; + this._mana = 1; + this._sta = 1; + + applyCraftpixCssVars(root); + applyBarsHudCssVars(root); + applyBarsHudCssVars(document.documentElement); + void preloadCraftpixUi(); + void preloadBarsHudUi(); root.innerHTML = ` -
- Bending Sandbox - Draw a path. Release to cast. +
+ Grudge Warlords · Casting + 1–6 elements · path cast · Surf (M) +
+ + +
+
+
+
+ +
+
+ WK +
+
+
+
+
+
+ 100% +
+
+
+
+ 100 +
+
+
+
+ 100 +
+
+
+
+ + + + + +
+
+
No target
+
+
+
+ +
+
+
+
+
+ ? +
FPS
+
STA 100
Particles 0
-
Draw calls 0
-
Abilities 0
+
Draw 0
-
-
Hold left mouse — draw a path on the ground
-
Release — cast the selected element
-
Right drag — orbit the camera
-
Scroll — zoom in / out
-
- 1234 elements   - QE cycle -
-
G editor   C clear   P pause   H hide
-
T sit / stand   M cast / walk
+
+
Combat · staffs on 1–6 · F residual
+
RMB focus · LMB attack (focus) / select (free) · unlocked mouse free
+
Shift sprint · Shift+Ctrl slide · Ctrl+dir roll
+
F weapon skill · 1–4 bar · LMB path · I panel
+
F1F5 Admin · Player · Assets · Creatures · Prefabs · World · Esc close
+
?/H this help · prefab create/save in Admin
+
Weapon skills (equip I→Weapon) · cast times on F / digits
@@ -55,29 +137,64 @@ export class HUD { M
-
+
${ELEMENTS.map((element, index) => { const meta = ELEMENT_META[element]; + const title = meta.hint || meta.staffLabel || meta.label; return ` -
-
${index + 1}
-
${ELEMENT_SIGILS[element] ?? meta.glyph}
-
${meta.label}
+
+
+
+
${index + 1}
+
${ELEMENT_SIGILS[element] ?? meta.glyph}
+
${meta.short || meta.label}
+
+
`; }).join('')} +
+
+
+
F
+
+
Weapon
+
+
+
+
+ +
`; this.cards = new Map(); - for (const card of root.querySelectorAll('.element-card')) { + for (const card of root.querySelectorAll('.action-slot[data-slot]')) { this.cards.set(card.dataset.element, card); card.addEventListener('pointerdown', (event) => { event.stopPropagation(); this.onSelect?.(card.dataset.element); + this.onSkillSlot?.(Number(card.dataset.slot)); }); } + root.querySelector('[data-melee]')?.addEventListener('pointerdown', (event) => { + event.stopPropagation(); + this.onMelee?.(); + }); this.modeCards = new Map(); for (const card of root.querySelectorAll('.mode-card')) { @@ -90,33 +207,303 @@ export class HUD { this.stats = { fps: root.querySelector('[data-stat="fps"]'), + stamina: root.querySelector('[data-stat="stamina"]'), particles: root.querySelector('[data-stat="particles"]'), - calls: root.querySelector('[data-stat="calls"]'), - abilities: root.querySelector('[data-stat="abilities"]') + calls: root.querySelector('[data-stat="calls"]') }; this.help = root.querySelector('.hud__help'); this.toast = root.querySelector('[data-toast]'); this.blurb = root.querySelector('[data-blurb]'); - this.elements = root.querySelector('.hud__elements'); + this.actionbar = root.querySelector('[data-actionbar]'); + this.elements = this.actionbar; + + this._hpFill = root.querySelector('[data-hp-fill]'); + this._mpFill = root.querySelector('[data-mp-fill]'); + this._staFill = root.querySelector('[data-sta-fill]'); + this._hpText = root.querySelector('[data-hp-text]'); + this._mpText = root.querySelector('[data-mp-text]'); + this._staText = root.querySelector('[data-sta-text]'); + this._playerName = root.querySelector('[data-player-name]'); + this._portrait = root.querySelector('[data-portrait]'); + this._targetName = root.querySelector('[data-target-name]'); + this._targetHp = root.querySelector('[data-target-hp]'); + this._targetHpText = root.querySelector('[data-target-hp-text]'); + this._meleeCd = root.querySelector('[data-cd-melee]'); + this._crosshair = root.querySelector('[data-crosshair]'); + + // Danger Room / threejs-rapier tight bar (6+6 + avatar + orbs) + root.classList.add('hud--tight'); + this.tightBar = new TightBar({ + host: root, + onAction: (id) => this.onQuickAction?.(id), + onMenu: (id) => this.onMenu?.(id) + }); + + /** CraftPix cast bar (cast times + progress under reticle) */ + this.castBar = new CastBar(root); + + this.refreshSkillLabels(); + } + + /** @param {object|null} state from DrcCombatController.getCastBarState */ + setCastBar(state) { + this.castBar?.setState(state); + } + + /** + * Pull labels from catalog bindings (preferred), DRC skills, or staff element meta. + * Element slots stay tied to Fire/Ice/Nature/Storm staffs when no skill bind. + */ + refreshSkillLabels() { + const skills = getActiveSkills(); + let i = 0; + for (const [element, card] of this.cards) { + const lab = card.querySelector('[data-skill-label]'); + const bound = getSkillBinding(i); + const sk = skills.find((s) => s.slot === i); + const meta = ELEMENT_META[element]; + const staffFallback = meta?.short || meta?.label || element; + if (lab) { + lab.textContent = bound?.name || sk?.label || staffFallback; + } + if (meta?.hint) card.title = bound?.name ? `${bound.name} · ${meta.hint}` : meta.hint; + i++; + } + const fLab = + this.root.querySelector('[data-f-skill-label]') || + this.root.querySelector('[data-melee] .action-slot__label'); + const fSkill = skillForFKey(); + const fBound = getSkillBinding('f'); + if (fLab) { + fLab.textContent = fBound?.name || fSkill?.label || 'Weapon'; + } + const fSlot = this.root.querySelector('[data-melee]'); + if (fSlot && fSkill) { + const ct = fSkill.castDuration ?? fSkill.castTime; + fSlot.title = `F · ${fLab?.textContent || 'Weapon skill'}${ + ct > 0.05 ? ` · cast ${Number(ct).toFixed(1)}s` : '' + }`; + } + this.tightBar?.refreshLabels?.(); + } + + /** + * @param {{ name?: string, raceId?: string, hp01?: number, sta01?: number, mana01?: number }} info + */ + setPlayerFrame(info = {}) { + if (info.name && this._playerName) this._playerName.textContent = info.name; + if (info.raceId && this._portrait) { + this._portrait.textContent = String(info.raceId).slice(0, 3); + const img = this.root.querySelector('[data-portrait-img]'); + if (img) { + const url = racePortraitUrl(info.raceId); + if (img.dataset.race !== String(info.raceId)) { + img.dataset.race = String(info.raceId); + img.src = url; + img.onload = () => img.classList.add('is-loaded'); + img.onerror = () => img.classList.remove('is-loaded'); + } + } + } + if (info.hp01 != null) { + this._hp = Math.max(0, Math.min(1, info.hp01)); + if (this._hpFill) this._hpFill.style.width = `${Math.round(this._hp * 100)}%`; + if (this._hpText) this._hpText.textContent = `${Math.round(this._hp * 100)}%`; + } + if (info.mana01 != null) { + this._mana = Math.max(0, Math.min(1, info.mana01)); + if (this._mpFill) this._mpFill.style.width = `${Math.round(this._mana * 100)}%`; + if (this._mpText) this._mpText.textContent = `${Math.round(this._mana * 100)}`; + } + if (info.sta01 != null) { + this._sta = Math.max(0, Math.min(1, info.sta01)); + if (this._staFill) this._staFill.style.width = `${Math.round(this._sta * 100)}%`; + if (this._staText) this._staText.textContent = `${Math.round(this._sta * 100)}`; + if (this.stats.stamina) this.stats.stamina.textContent = String(Math.round(this._sta * 100)); + } + // Legacy: if only sta01 and no mana bar data, keep mp as sta for old callers + if (info.sta01 != null && info.mana01 == null && !this._staFill && this._mpFill) { + this._mpFill.style.width = `${Math.round(this._sta * 100)}%`; + } + this.tightBar?.setState({ + character: info.name || this.tightBar.state.character, + raceId: info.raceId || this.tightBar.state.raceId, + health: (info.hp01 != null ? info.hp01 : this._hp) * 100, + maxHealth: 100, + stamina: (info.sta01 != null ? info.sta01 : this._sta) * 100, + maxStamina: 100, + mana: (info.mana01 != null ? info.mana01 : this._mana ?? 1) * 100, + maxMana: 100 + }); + } + + /** + * @param {{ name?: string, hp01?: number, present?: boolean }|null} info + */ + setTargetFrame(info) { + if (!info || info.present === false) { + if (this._targetName) this._targetName.textContent = 'No target'; + if (this._targetHp) this._targetHp.style.width = '0%'; + if (this._targetHpText) this._targetHpText.textContent = '—'; + this.root.querySelector('[data-target-frame]')?.classList.add('is-empty'); + return; + } + this.root.querySelector('[data-target-frame]')?.classList.remove('is-empty'); + if (info.name && this._targetName) this._targetName.textContent = info.name; + const hp = Math.max(0, Math.min(1, info.hp01 ?? 1)); + if (this._targetHp) this._targetHp.style.width = `${Math.round(hp * 100)}%`; + if (this._targetHpText) this._targetHpText.textContent = `${Math.round(hp * 100)}%`; + } + + /** + * @param {{ id: string, name: string, hp01?: number }[]} allies + */ + setAllies(allies = []) { + const host = this.root.querySelector('[data-allies]'); + if (!host) return; + const list = Array.isArray(allies) ? allies.filter(Boolean) : []; + if (!list.length) { + host.innerHTML = ''; + host.classList.add('is-empty'); + host.hidden = true; + host.setAttribute('aria-hidden', 'true'); + return; + } + host.hidden = false; + host.setAttribute('aria-hidden', 'false'); + host.classList.remove('is-empty'); + // Ally strip chrome = overhead_health_001 + thin health filler + host.innerHTML = list + .slice(0, 4) + .map((a) => { + const pct = Math.round(Math.max(0, Math.min(1, a.hp01 ?? 1)) * 100); + const label = a.name || a.id || 'Ally'; + return `
${label}
`; + }) + .join(''); } setElement(element) { for (const [key, card] of this.cards) { card.classList.toggle('is-active', key === element); } - const meta = ELEMENT_META[element]; - if (meta) this.showToast(`${meta.hint} selected`); } - /** Reflect the interaction mode. Walk mode dims the (unused) element picker. */ setMode(mode) { + this._mode = mode; for (const [key, card] of this.modeCards) { card.classList.toggle('is-active', key === mode); } const meta = MODE_META[mode]; if (!meta) return; - this.blurb.textContent = meta.blurb; - this.elements.classList.toggle('is-dimmed', mode !== 'casting'); + // Walk/Surf mode blurb wins over combat blurb for path-ride context + if (mode === 'walk') { + this.blurb.textContent = meta.blurb; + this._setHelpMode('walk'); + } else if (this._drcSession === 'combat') { + this.blurb.textContent = 'Aim · WASD · 1–6 elements · LMB path cast'; + this._setHelpMode('combat'); + } else { + this.blurb.textContent = meta.blurb; + this._setHelpMode('equip'); + } + } + + /** Compact help line set by mode (no second help panel). */ + _setHelpMode(kind) { + const el = this.root.querySelector('[data-help-elements]'); + if (!el) return; + if (kind === 'walk') { + el.textContent = 'Surf: Space deploy · path = course · WASD boat · skills on board'; + } else if (kind === 'equip') { + el.textContent = 'Panel (I): race · mesh · weapon packs · skills · fleet API'; + } else { + el.textContent = '1–6: Fire · Storm · Ice · Nature · Holy · Arcane · path cast'; + } + } + + setDrcSession(session) { + this._drcSession = session; + if (session === 'combat') { + if (this._mode === 'walk') { + this.blurb.textContent = MODE_META.walk?.blurb || 'Surf freeride'; + this._setHelpMode('walk'); + } else { + this.blurb.textContent = 'Aim · WASD · 1–6 elements · LMB path cast'; + this._setHelpMode('combat'); + } + this.actionbar?.classList.remove('is-dimmed'); + this.tightBar?.setVisible(true); + this.setCrosshairVisible(this._mode !== 'walk' || true); + this.refreshSkillLabels(); + } else { + this.blurb.textContent = 'Panel · race · mesh · weapon packs · fleet API'; + this.actionbar?.classList.add('is-dimmed'); + this.tightBar?.setVisible(true); + this.setCrosshairVisible(false); + this._setHelpMode('equip'); + } + } + + setCrosshairVisible(on) { + this._crosshair?.classList.toggle('is-visible', !!on); + } + + /** + * Dynamic reticle — GRUDOX animator parity: + * centre dot + 4 ticks with bloom gap, range ring, hit-marker pulse. + * @param {{ + * softLock?: boolean, + * fire?: boolean, + * focus?: boolean, + * spread?: number, + * rangeState?: 'close'|'optimal'|'far'|'none', + * hitMarker?: number + * }} [st] + */ + setCrosshairState(st = {}) { + const el = this._crosshair; + if (!el) return; + el.classList.toggle('is-softlock', !!st.softLock); + el.classList.toggle('is-fire', !!st.fire); + el.classList.toggle('is-focus', !!st.focus); + // Animator: gap in px 0..28 from bloom (we map 0..1 → px) + const spread01 = Math.max(0, Math.min(1, Number(st.spread) || 0)); + const gapPx = Math.round(spread01 * 22); + el.style.setProperty('--xh-spread', String(spread01)); + el.style.setProperty('--ch-gap', `${gapPx}px`); + const range = st.rangeState || 'none'; + el.dataset.range = range; + el.classList.toggle('is-range-close', range === 'close'); + el.classList.toggle('is-range-optimal', range === 'optimal'); + el.classList.toggle('is-range-far', range === 'far'); + if (st.hitMarker != null && st.hitMarker > 0) { + const hit = el.querySelector('[data-xh-hit]'); + if (hit) { + hit.classList.remove('is-pulse'); + // reflow restart + void hit.offsetWidth; + hit.classList.add('is-pulse'); + } + } + } + + setCombatHud(cd01Fn, stamina, meleeCd01) { + if (stamina != null) { + this.setPlayerFrame({ sta01: stamina / 100 }); + } + if (!cd01Fn) return; + for (const card of this.cards.values()) { + const slot = Number(card.dataset.slot); + const cd = cd01Fn(slot); + const el = card.querySelector('[data-cd]'); + if (el) el.style.setProperty('--cd', String(cd)); + card.classList.toggle('is-cooling', cd > 0.02); + } + if (this._meleeCd && meleeCd01 != null) { + this._meleeCd.style.setProperty('--cd', String(meleeCd01)); + this.root.querySelector('[data-melee]')?.classList.toggle('is-cooling', meleeCd01 > 0.02); + } } toggleHelp() { @@ -130,12 +517,6 @@ export class HUD { this._toastTimer = setTimeout(() => this.toast.classList.remove('is-visible'), duration); } - /** - * @param {number} dt - * @param {() => {particles:number, calls:number, abilities:number}} collect - * Called only when the readout actually refreshes, so gathering the numbers - * (which means walking the particle pools) stays off the hot path. - */ update(dt, collect) { this._frames++; this._statsAccumulator += dt; @@ -149,7 +530,32 @@ export class HUD { this.stats.fps.textContent = this._fps; this.stats.particles.textContent = info.particles; this.stats.calls.textContent = info.calls; - this.stats.abilities.textContent = info.abilities; + if (info.player) { + this.setPlayerFrame(info.player); + } else if (info.stamina != null || info.mana != null) { + this.setPlayerFrame({ + sta01: info.stamina != null ? info.stamina / 100 : undefined, + mana01: info.mana != null ? info.mana / 100 : undefined + }); + } + if (info.cooldown01 && this._drcSession === 'combat') { + this.setCombatHud(info.cooldown01, info.stamina, info.meleeCd01); + } + if (info.target !== undefined) this.setTargetFrame(info.target); + + // Sync tight bar orbs + CDs every stats tick + if (this.tightBar && (info.stamina != null || info.mana != null)) { + this.tightBar.setState({ + stamina: info.stamina ?? this._sta * 100, + maxStamina: 100, + mana: info.mana ?? (this._mana ?? 1) * 100, + maxMana: 100, + health: (info.player?.hp01 ?? this._hp) * 100, + character: info.player?.name || this.tightBar.state.character, + raceId: info.player?.raceId || this.tightBar.state.raceId, + cd01: info.quickCd01 || (() => 0) + }); + } } } @@ -168,11 +574,11 @@ export class LoadingScreen { hide() { this.setProgress(1); - setTimeout(() => this.element.classList.add('is-hidden'), 220); - } - - fail(message) { - this.status.textContent = message; - this.status.style.color = '#ff7a6a'; + if (!this.element) return; + setTimeout(() => { + this.element.classList.add('is-hidden'); + // Fully remove from layout after fade (opacity alone left a black veil) + this.element.style.display = 'none'; + }, 220); } } diff --git a/src/ui/InventoryPanel.js b/src/ui/InventoryPanel.js new file mode 100644 index 0000000..80a388d --- /dev/null +++ b/src/ui/InventoryPanel.js @@ -0,0 +1,2352 @@ +import { EQUIP_SLOTS, WEAPON_SLOTS, ANIM_PACKS, ANIM_PACK_META, RACES } from '../config/assets.js'; +import { settings } from '../config/settings.js'; +import { + fleetApi, + MAIN_PANEL_URL, + OPEN_LIBRARY_URL, + CHARACTER_FOUNDRY_URL, + GRUDGE_ID_URL, + FLEET_API_DEFAULT, + CRAFT_SSOT_URL +} from '../api/fleetApi.js'; +import { + getActiveSkills, + setActiveSkillTree +} from '../combat/drcSkills.js'; +// setActiveSkillTree / getActiveSkills used by weapon equip only — no free skill trees +import { + animPackForLoadout, + activeWeaponSlot, + packCombatBlurb, + WEAPON_SLOT_TO_PACK +} from '../config/weaponAnimPack.js'; +import { + ensureWeaponCatalog, + listEquippableWeapons, + equipWeaponById, + unequipWeapon, + getEquippedWeapon, + getEquippedSlot3Id, + setEquippedSlot3, + getActiveLoadoutIndex, + downloadEquippedPrefab, + exportEquippedPrefab +} from '../combat/equippedWeaponRuntime.js'; +import './warlords-dev-ui.css'; +import { + loadGameItemCatalog, + queryGameItems, + exportItemPrefabSnapshot, + PREFAB_CATEGORIES, + ITEM_BROWSER_URL, + WEAPON_SKILLS_HTML +} from '../api/gameItemCatalog.js'; +import { + loadPrefabScaffold, + buildItemScaffoldPack, + downloadJson, + SCAFFOLD_ENDPOINTS +} from '../api/prefabScaffold.js'; +import { + exportWarlordsWeaponPrefab, + getEquippableWeaponsCache, + weaponSkillTreeRows, + weaponSkillTooltip, + ensureWeaponSkillIcons +} from '../api/t0WeaponCatalog.js'; +import { + labAdminStatusLine, + labPlayerLevel, + labMaxTier +} from '../config/labAdmin.js'; +import { + FISHING_SKILL_TREE, + canUnlockNode, + loadProfessionState as loadFishingProf, + unlockTreeNode as unlockFishingNode, + resolveProfessionMods, + eatMeal as eatFishingMeal, + MEAL_RECIPES, + ROD_TYPES, + saveProfessionState as saveFishingProf +} from '../fishing/professionState.js'; +import { FISHING_LURES } from '../fishing/fishingCatalog.js'; +import { loadRewardsState } from '../fishing/fishingRewards.js'; +import { DOCK_TIERS, plantOwnClaim, loadOwnClaim, placeDock } from '../fishing/dockBuild.js'; +import { + PAPERDOLL_LEFT, + PAPERDOLL_RIGHT, + BAG_LAYOUT, + BAG_SKINS, + PROFESSION_TREES, + getPaperdollSlots, + loadBag, + saveBag, + loadEquipMap, + saveEquipMap, + loadBagSkin, + saveBagSkin, + loadProfessionProgress, + unlockProfessionNode, + loadSlotAdminOverrides, + saveSlotAdminOverrides, + ensureDemoBag, + bagAdd, + bagRemoveAt, + itemFitsSlot, + ALL_PAPERDOLL_SLOTS, + reenrichAllBagIcons, + enrichBagSlotIcon +} from './mainPanelSlots.js'; +import { + withResolvedIcon, + bagItemFromCatalogRow, + resolveItemIcon +} from './iconResolve.js'; +import './mainPanel.css'; +import './itemContextMenu.css'; +import { + bindItemContextMenu, + openItemContextMenu, + unequipPaperdollSlot, + closeItemContextMenu +} from './itemContextMenu.js'; +import { + applyMainPanelUiVars, + MAIN_PANEL_PROD, + UI_ASSET_CATALOG +} from './uiAssetCatalog.js'; +import { fleetDeploySnapshot } from '../config/fleetEnv.js'; + +/** + * Main Panel — Warlords / TI equipment look · inventory slots · production tester. + * + * Character: paperdoll (LMB slot → inventory picker) + * Equipment / Inventory: bag grid + equip map + * Skills: WCS profession skill trees + combat trees + * Admin: slot accept filters (F1 / this panel) + * + * Refs: tactical-infinity equipment · Sample-InventorySlotsSet · Player-Inventory-System + */ +export class InventoryPanel { + /** + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * onToast?: (msg: string) => void, + * onEquip?: () => void, + * onRace?: (raceId: string) => void|Promise, + * onMountToggle?: (wantRide: boolean) => void, + * onMode?: (mode: 'casting'|'walk') => void, + * getDrc?: () => import('../combat/DrcCombatController.js').DrcCombatController|null, + * onDropWorld?: (item: object, cx: number, cy: number) => void, + * onDepositItem?: (item: object) => void|Promise + * }} opts + */ + constructor(opts) { + this.character = opts.character; + this.onToast = opts.onToast || (() => {}); + this.onEquip = opts.onEquip || (() => {}); + this.onRace = opts.onRace || null; + this.onMountToggle = opts.onMountToggle || null; + this.onMode = opts.onMode || null; + this.getDrc = opts.getDrc || (() => null); + this.onDropWorld = opts.onDropWorld || null; + this.onDepositItem = opts.onDepositItem || null; + applyMainPanelUiVars(); + this.open = false; + this._tab = 'character'; + this._busy = false; + this.api = fleetApi; + /** @type {string} Prefabs category filter */ + this._prefabCat = 't0'; + this._prefabQ = ''; + this._prefabSelected = null; + this._gameItems = null; + /** LMB equip target paperdoll slot id */ + this._equipTarget = null; + /** Bag index currently “picked” (Player-Inventory-System style) */ + this._pickedBagIndex = null; + this._profTab = 'miner'; + ensureDemoBag(); + + this.el = document.createElement('div'); + this.el.id = 'inventory-panel'; + this.el.className = 'inv-panel inv-panel--lab inv-panel--main wl-inv-shell'; + this.el.hidden = true; + document.body.appendChild(this.el); + + this._renderShell(); + } + + _tabs() { + return [ + { id: 'character', label: 'Character' }, + { id: 'equip', label: 'Equipment' }, + { id: 'inventory', label: 'Inventory' }, + { id: 'weapon', label: 'Weapon' }, + { id: 'skills', label: 'Skills' }, + { id: 'professions', label: 'Professions' }, + { id: 'prefabs', label: 'Prefabs' }, + { id: 'race', label: 'Race' }, + { id: 'mesh', label: 'Mesh' }, + { id: 'mount', label: 'Mount' }, + { id: 'anims', label: 'Anims' }, + { id: 'slots', label: 'Slots' }, + { id: 'api', label: 'API' } + ]; + } + + _renderShell() { + const tabs = this._tabs(); + this.el.innerHTML = ` +
+
+

Main Panel

+

MMO equipment · bag · RMB item menus · API · production lab

+
+ +
+
+ +
+
+ + + + + + + + + + + + +
+
+ + `; + + this.el.querySelector('[data-close]').addEventListener('click', () => this.setOpen(false)); + this.el.querySelectorAll('[data-tab]').forEach((btn) => { + btn.addEventListener('click', () => this._setTab(btn.dataset.tab)); + }); + this.el.addEventListener('pointerdown', (e) => e.stopPropagation()); + this.el.addEventListener('wheel', (e) => e.stopPropagation()); + // Allow native context menu only on our item menu (we open custom) + this.el.addEventListener('contextmenu', (e) => { + // Let slot handlers preventDefault; don't block bubbling to them + if (!e.target?.closest?.('[data-bag-i],[data-pd-slot]')) { + e.preventDefault(); + } + }); + } + + /** Shared handlers for bag + paperdoll RMB */ + _itemCtxHandlers() { + return { + onToast: this.onToast, + onEquip: (item, slotDef, bagIndex) => this._equipItemToSlot(item, slotDef, bagIndex), + onUnequip: async (slotId) => { + const it = unequipPaperdollSlot(slotId); + if (it) { + try { + const active = getActiveLoadoutIndex(); + const activeMain = active === 1 ? 'weapon2' : 'mainHand'; + const activeOff = active === 1 ? 'offHand2' : 'offHand'; + // Clear live 3D only when removing the active set main hand + if (slotId === activeMain) { + await unequipWeapon({ character: this.character, onToast: this.onToast }); + } else if (slotId === activeOff) { + // Off-hand paperdoll only for now (shield mesh optional later) + } + } catch { + /* ok */ + } + this.onToast(`Unequipped ${it.name || it.id}`); + this.onEquip(); + } + }, + onUse: (item) => { + this.onToast(`Use ${item.name || item.id} (lab — wire consumable later)`); + }, + onDropWorld: this.onDropWorld + ? (item, cx, cy) => this.onDropWorld(item, cx, cy) + : null, + onDeposit: async (item) => { + // Prefer Railway deposit; fall back to craft SSOT + try { + const r = await this.api.depositItem(withResolvedIcon(item)); + if (r.ok) { + this.onToast(r.message); + return; + } + if (r.authRequired) { + this.onToast(r.message); + window.open(GRUDGE_ID_URL, '_blank', 'noopener'); + return; + } + if (this.onDepositItem) { + await this.onDepositItem(item); + return; + } + if (r.openCraft) { + window.open(MAIN_PANEL_PROD.craft, '_blank', 'noopener'); + } + this.onToast(r.message); + } catch (err) { + this.onToast(err?.message || 'Deposit failed'); + } + }, + onRefresh: () => this.refresh(), + onOpenSlotPicker: (slotId) => { + this._setTab('character'); + // After refresh, open picker + requestAnimationFrame(() => { + const host = this.el.querySelector('[data-panel="character"]'); + if (host) this._onPaperdollSlotClick(slotId, host); + }); + }, + onOpenCatalogPicker: (slotId) => { + this._setTab('character'); + requestAnimationFrame(() => { + const host = this.el.querySelector('[data-panel="character"]'); + if (host) this._openCatalogPicker(slotId, host); + }); + }, + onEditMesh: (item) => { + this._setTab('character'); + requestAnimationFrame(() => this._openMeshEditor(item)); + } + }; + } + + _setTab(tab) { + this._tab = tab || 'character'; + this.el.querySelectorAll('.inv-nav-tab').forEach((b) => { + b.classList.toggle('is-active', b.dataset.tab === this._tab); + }); + this.el.querySelectorAll('.inv-section').forEach((s) => { + s.hidden = s.dataset.panel !== this._tab; + }); + this.refresh(); + } + + /** Open lab panel on a tab (e.g. Admin Hub → Prefabs). */ + openTab(tab) { + this.setOpen(true); + this._setTab(tab || 'character'); + } + + toggle() { + this.setOpen(!this.open); + } + + setOpen(open) { + this.open = !!open; + this.el.hidden = !this.open; + if (this.open) { + reenrichAllBagIcons(); + this.refresh(); + // Pull catalog icons in background (weapons/tools for bag + prefabs) + void this._warmCatalogIcons(); + } + } + + refresh() { + if (!this.open) return; + this._fillCharacter(); + this._fillEquip(); + this._fillInventory(); + this._fillWeapon(); + this._fillPrefabs(); + this._fillRace(); + this._fillMesh(); + this._fillMount(); + this._fillAnims(); + this._fillSkills(); + this._fillProfessions(); + this._fillSlotsAdmin(); + this._fillApi(); + } + + async _warmCatalogIcons() { + try { + const cat = await loadGameItemCatalog(); + this._gameItems = cat; + const all = Object.values(cat?.byCategory || {}).flat(); + const byId = new Map(all.map((r) => [r.id, r])); + // Enrich bag slots from info/objectstore catalog icons + const bag = loadBag(); + let dirty = false; + for (let i = 0; i < bag.slots.length; i++) { + const s = bag.slots[i]; + if (!s?.id) continue; + const row = byId.get(s.id); + if (row?.iconUrl) { + bag.slots[i] = { + ...s, + icon: row.iconUrl, + iconUrl: row.iconUrl, + name: s.name || row.name, + tier: s.tier ?? row.tier + }; + dirty = true; + } else { + const en = enrichBagSlotIcon(s); + if (en.icon && en.icon !== s.icon) { + bag.slots[i] = en; + dirty = true; + } + } + } + if (dirty) { + saveBag(bag); + if (this._tab === 'inventory' || this._tab === 'character') this.refresh(); + } + } catch (err) { + console.warn('[MainPanel] catalog icons', err); + } + } + + _busyGuard(fn) { + return async (...args) => { + if (this._busy) { + this.onToast('Busy…'); + return; + } + this._busy = true; + try { + await fn(...args); + } catch (err) { + console.error(err); + this.onToast(err?.message || String(err)); + } finally { + this._busy = false; + this.refresh(); + this.onEquip(); + } + }; + } + + /* ── Character paperdoll (TI / Warlords) ───────────────────── */ + + _slotButtonHtml(slot, equipMap, activeSet = 0) { + const item = equipMap[slot.id]; + const isTarget = this._equipTarget === slot.id; + const setIdx = slot.weaponSet; + const isActiveSet = + setIdx === 0 || setIdx === 1 ? setIdx === activeSet : false; + const setCls = + setIdx === 0 || setIdx === 1 + ? `mp-slot--set${setIdx}${isActiveSet ? ' is-active-set' : ''}` + : ''; + return ` + `; + } + + _renderPaperdoll(host) { + const s = this.character.getLabSummary?.() || {}; + const equipMap = loadEquipMap(); + const activeSet = getActiveLoadoutIndex(); + const left = PAPERDOLL_LEFT.map((sl) => + this._slotButtonHtml(sl, equipMap, activeSet) + ).join(''); + const right = PAPERDOLL_RIGHT.map((sl) => + this._slotButtonHtml(sl, equipMap, activeSet) + ).join(''); + const pack = s.animPackId || '—'; + const w1 = equipMap.mainHand?.name || equipMap.mainHand?.id || '—'; + const w2 = equipMap.weapon2?.name || equipMap.weapon2?.id || '—'; + + host.innerHTML = ` +
+
+
Armour · Relic
+ ${left} +
+
+ +
${s.raceLabel || s.raceId || 'Hero'} · ${(s.heightM ?? 1.8).toFixed?.(2) || '1.80'} m · ${pack}
+
Active Weapon ${activeSet + 1} · Q swap · set1 ${w1} · set2 ${w2}
+
+
+
Weapons · Back · Mount
+ ${right} +
+ +
+

RMB slot → T0–T1 catalog · Weapon 1/2 + Off 1/2 dual loadout · combat Q swaps set · Relic/Back (no belt/amulet/ring/cloak). ui equipment ↗

+
+ + + + Fleet Main Panel ↗ + Craft bag ↗ +
+ `; + + const h = this._itemCtxHandlers(); + host.querySelectorAll('[data-pd-slot]').forEach((btn) => { + btn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + this._onPaperdollSlotClick(btn.dataset.pdSlot, host); + }); + bindItemContextMenu( + btn, + () => { + const slotId = btn.dataset.pdSlot; + const equipMap = loadEquipMap(); + return { + source: 'paperdoll', + slotId, + item: equipMap[slotId] || null + }; + }, + h + ); + }); + host.querySelector('[data-open-inv]')?.addEventListener('click', () => this._setTab('inventory')); + host.querySelector('[data-seed-t0]')?.addEventListener('click', async () => { + try { + const { listAllT0T1 } = await import('../api/gameItemCatalog.js'); + const rows = await listAllT0T1({ maxTier: 0 }); + let n = 0; + for (const r of rows.slice(0, 24)) { + bagAdd({ + id: r.id, + name: r.name, + kind: r.kind || r.category, + slotHint: r.slotHint, + icon: r.iconUrl, + iconUrl: r.iconUrl, + tier: r.tier, + qty: 1 + }); + n++; + } + // Back mobility always + for (const id of ['windsurf', 'holy_wings', 'traveler_wings', 'shark_fin']) { + bagAdd({ + id, + name: id === 'shark_fin' ? 'Shark Fin' : id, + kind: 'back', + slotHint: 'back', + qty: 1 + }); + n++; + } + this.onToast(`Seeded ${n} catalog items into bag`); + this.refresh(); + } catch (e) { + this.onToast(e?.message || 'Seed failed'); + } + }); + host.querySelector('[data-unequip-all]')?.addEventListener('click', () => { + saveEquipMap({}); + this.onToast('Paperdoll cleared (local map)'); + this.refresh(); + }); + } + + _onPaperdollSlotClick(slotId, host) { + const slotDef = ALL_PAPERDOLL_SLOTS.find((s) => s.id === slotId); + if (!slotDef) return; + + // If bag item picked → try equip + if (this._pickedBagIndex != null) { + const bag = loadBag(); + const item = bag.slots[this._pickedBagIndex]; + if (item && itemFitsSlot(item, slotDef)) { + this._equipItemToSlot(item, slotDef, this._pickedBagIndex); + this._pickedBagIndex = null; + this._equipTarget = null; + this.refresh(); + return; + } + this.onToast('Item does not fit this slot'); + } + + this._equipTarget = slotId; + const bag = loadBag(); + const fits = bag.slots + .map((it, i) => (it && itemFitsSlot(it, slotDef) ? { it, i } : null)) + .filter(Boolean); + + const picker = host.querySelector('[data-picker]'); + if (!picker) return; + picker.hidden = false; + picker.innerHTML = ` +

${slotDef.label} — bag options

+ ${ + fits.length + ? fits + .map( + ({ it, i }) => ` + ` + ) + .join('') + : `

No bag items for this slot.

` + } + + + `; + picker.querySelectorAll('[data-pick-i]').forEach((b) => { + b.addEventListener('click', () => { + const i = Number(b.dataset.pickI); + const bag2 = loadBag(); + const item = bag2.slots[i]; + if (item) this._equipItemToSlot(item, slotDef, i); + this._equipTarget = null; + this.refresh(); + }); + }); + picker.querySelector('[data-open-catalog]')?.addEventListener('click', () => { + this._openCatalogPicker(slotId, host); + }); + picker.querySelector('[data-picker-close]')?.addEventListener('click', () => { + picker.hidden = true; + this._equipTarget = null; + this.refresh(); + }); + } + + async _equipItemToSlot(item, slotDef, bagIndex) { + const map = loadEquipMap(); + // Return previous to bag + if (map[slotDef.id]) bagAdd(map[slotDef.id]); + const equipped = { + ...item, + qty: 1, + kind: item.kind || item.category || 'item', + icon: item.icon || item.iconUrl, + iconUrl: item.iconUrl || item.icon + }; + map[slotDef.id] = equipped; + saveEquipMap(map); + // Remove one from bag (catalog equip may have no bag index) + const bag = loadBag(); + if (bagIndex != null && bag.slots[bagIndex]) { + const q = (bag.slots[bagIndex].qty || 1) - 1; + if (q <= 0) bag.slots[bagIndex] = null; + else bag.slots[bagIndex].qty = q; + saveBag(bag); + } + + // Live 3D: weapons · armour mesh_ids · back mobility · appearance + try { + if (slotDef.kind === 'hand' && item.id) { + await ensureWeaponCatalog(); + const setIdx = slotDef.weaponSet === 1 ? 1 : 0; + const isMain = slotDef.hand === 'main' || !slotDef.hand; + try { + if (isMain) { + const active = getActiveLoadoutIndex(); + const otherMainId = setIdx === 0 ? map.weapon2?.id : map.mainHand?.id; + // Apply live mesh/skills only for active set (or sole weapon equip) + const applyLive = setIdx === active || !otherMainId; + if (applyLive) { + await equipWeaponById(item.id, { + character: this.character, + onToast: this.onToast, + weaponSet: setIdx + }); + setActiveSkillTree('equipped'); + } else { + this.onToast( + `Weapon ${setIdx + 1} ready · combat Q swaps to it (${item.name})` + ); + } + } else { + // Off hand paperdoll — stored for set; 3D shield attach later + this.onToast(`Off ${setIdx + 1} · ${item.name}`); + } + } catch { + this.onToast(`Paperdoll ${item.name} (3D if model URL later)`); + } + const { applyWeaponAppearance } = await import('../equipment/meshAppearance.js'); + applyWeaponAppearance(this.character, item.id); + } else if (slotDef.kind === 'mesh' && slotDef.meshSlot) { + const cat = this.character.equipment?.getCatalogSummary?.() || {}; + const variants = cat[slotDef.meshSlot]?.variants || []; + // Prefer A / item variant hint + const pick = + variants.find((v) => v === item.variant || v === 'A' || v === '_default') || + variants[0] || + 'A'; + this.character.equipment?.setSlot?.(slotDef.meshSlot, pick); + this.character._reGroundAfterEquip?.(); + const { applyArmorAppearance } = await import('../equipment/meshAppearance.js'); + applyArmorAppearance(this.character, slotDef.meshSlot, item.id); + } else if (slotDef.kind === 'back' || slotDef.id === 'back') { + await this.character.equipBackSlot?.(item.id, { + modelUrl: item.modelUrl || item.deployModelUrl || undefined + }); + this.onToast( + item.domain === 'water' + ? `${item.name} stowed · water deploy only` + : item.domain === 'air' + ? `${item.name} stowed · flight next` + : `Back · ${item.name}` + ); + } else if (slotDef.id === 'relic' || slotDef.id === 'mount') { + // HUD / passive lab — paperdoll map only for now + this.onToast(`${slotDef.label} · ${item.name} (catalog)`); + } + } catch (e) { + this.onToast(e?.message || 'Equip 3D skipped'); + } + this.onToast(`Equipped ${item.name} → ${slotDef.label}`); + this.onEquip(); + } + + /** + * RMB empty/filled slot → browse live T0–T1 catalog with icons. + * @param {string} slotId + * @param {HTMLElement} host + */ + async _openCatalogPicker(slotId, host) { + const slotDef = ALL_PAPERDOLL_SLOTS.find((s) => s.id === slotId); + if (!slotDef) return; + const picker = host.querySelector('[data-picker]'); + if (!picker) return; + picker.hidden = false; + picker.innerHTML = `

${slotDef.label} — T0–T1 catalog…

Loading…

`; + + try { + const { listT0T1ForSlot } = await import('../api/gameItemCatalog.js'); + const rows = await listT0T1ForSlot(slotId, { maxTier: 1 }); + const list = rows.slice(0, 80); + picker.innerHTML = ` +

${slotDef.label} — T0–T1 (${list.length}${rows.length > 80 ? '+' : ''})

+ +
+ ${ + list.length + ? list + .map( + (r) => ` + ` + ) + .join('') + : `

No T0–T1 items for this slot (catalog empty / offline).

` + } +
+ + `; + + const bindRows = (items) => { + const grid = picker.querySelector('[data-cat-grid]'); + if (!grid) return; + grid.innerHTML = items + .map( + (r) => ` + ` + ) + .join(''); + grid.querySelectorAll('[data-cat-id]').forEach((b) => { + b.addEventListener('click', async () => { + const id = b.dataset.catId; + const row = rows.find((x) => x.id === id); + if (!row) return; + // Equip from catalog without requiring bag first + await this._equipItemToSlot( + { + id: row.id, + name: row.name, + kind: row.kind || row.category, + slotHint: row.slotHint || slotDef.id, + icon: row.iconUrl, + iconUrl: row.iconUrl, + modelUrl: row.modelUrl, + tier: row.tier, + domain: row.domain, + category: row.category + }, + slotDef, + null + ); + this._equipTarget = null; + this.refresh(); + }); + }); + }; + bindRows(list); + + picker.querySelector('[data-cat-q]')?.addEventListener('input', (e) => { + const q = String(e.target.value || '').toLowerCase(); + bindRows( + rows + .filter((r) => !q || `${r.name} ${r.id}`.toLowerCase().includes(q)) + .slice(0, 80) + ); + }); + picker.querySelector('[data-picker-close]')?.addEventListener('click', () => { + picker.hidden = true; + this.refresh(); + }); + } catch (err) { + picker.innerHTML = `

${err?.message || 'Catalog load failed'}

+ `; + picker.querySelector('[data-picker-close]')?.addEventListener('click', () => { + picker.hidden = true; + }); + } + } + + /** + * Mesh appearance editor (color · scale · rotate · offset). + * @param {object} item + */ + _openMeshEditor(item) { + if (!item?.id) return; + const host = this.el.querySelector('[data-panel="character"]'); + if (!host) return; + let panel = host.querySelector('[data-mesh-app]'); + if (!panel) { + panel = document.createElement('div'); + panel.className = 'inv-card mp-mesh-app'; + panel.dataset.meshApp = '1'; + host.appendChild(panel); + } + import('../equipment/meshAppearance.js').then( + ({ getAppearance, setAppearance, applyWeaponAppearance, applyArmorAppearance }) => { + const app = getAppearance(item.id); + panel.innerHTML = ` +

Mesh · ${item.name || item.id}

+

Color / scale / rotate / offset — saved per item id for casting lab develop.

+ + +

SI length: measure after apply (hand mesh)

+ + + + +
+ + +
+ `; + const read = () => ({ + color: panel.querySelector('[data-app-color]')?.value, + scale: Number(panel.querySelector('[data-app-scale]')?.value) || 1, + eulerDeg: [ + Number(panel.querySelector('[data-app-pitch]')?.value) || 0, + Number(panel.querySelector('[data-app-yaw]')?.value) || 0, + Number(panel.querySelector('[data-app-roll]')?.value) || 0 + ], + emissive: Number(panel.querySelector('[data-app-em]')?.value) || 0 + }); + const apply = () => { + const next = setAppearance(item.id, read()); + const eq = loadEquipMap(); + // Apply to weapon if main hand + if ( + eq.mainHand?.id === item.id || + eq.offHand?.id === item.id || + eq.weapon2?.id === item.id || + eq.offHand2?.id === item.id + ) { + applyWeaponAppearance(this.character, item.id); + } else { + // Try armour slots + for (const sid of ['head', 'body', 'arms', 'legs', 'shoulders']) { + if (eq[sid]?.id === item.id) { + const def = ALL_PAPERDOLL_SLOTS.find((s) => s.id === sid); + applyArmorAppearance(this.character, def?.meshSlot || sid, item.id); + } + } + } + this.onToast(`Mesh · ${item.name} · scale ${next.scale?.toFixed?.(2) ?? 1}`); + }; + panel.querySelector('[data-app-apply]')?.addEventListener('click', apply); + panel.querySelectorAll('input').forEach((inp) => { + inp.addEventListener('input', () => { + // Live preview + const next = setAppearance(item.id, read()); + applyWeaponAppearance(this.character, item.id); + const sv = panel.querySelector('[data-app-scale-val]'); + if (sv) sv.textContent = `×${Number(next.scale ?? 1).toFixed(2)}`; + import('../equipment/weaponPrefabLab.js').then(({ measureWeaponScale, formatScaleReadout }) => { + const el = panel.querySelector('[data-app-len]'); + if (el) el.textContent = formatScaleReadout(measureWeaponScale(this.character)); + }); + }); + }); + panel.querySelector('[data-app-reset]')?.addEventListener('click', () => { + setAppearance(item.id, { + color: '#c8a070', + scale: 1, + eulerDeg: [0, 0, 0], + emissive: 0 + }); + applyWeaponAppearance(this.character, item.id); + this._openMeshEditor(item); + }); + } + ); + } + + _fillCharacter() { + const host = this.el.querySelector('[data-panel="character"]'); + if (!host || this._tab !== 'character') return; + this._renderPaperdoll(host); + const s = this.character.getLabSummary?.() || {}; + const presets = this.character.presets || []; + const presetOpts = presets + .map( + (p) => + `` + ) + .join(''); + host.insertAdjacentHTML( + 'beforeend', + ` +
+
Kit${(s.kitUrl || '').split('/').pop() || 'Toon RTS'}
+
Clips${(s.clips || []).length}
+
+ + + ` + ); + host.querySelector('[data-preset]')?.addEventListener( + 'change', + this._busyGuard(async (e) => { + const id = e.target.value; + this.character.applyPreset(id); + await this.character.setAnimPack?.(this.character.animPackId); + this.onToast(`Preset ${id}`); + }) + ); + } + + /* ── Equipment (paperdoll + kit mesh selects) ─────────────── */ + + _fillEquip() { + const host = this.el.querySelector('[data-panel="equip"]'); + if (!host || this._tab !== 'equip') return; + this._renderPaperdoll(host); + + const c = this.character; + const catalog = c.equipment?.getCatalogSummary?.() || {}; + const slotRows = EQUIP_SLOTS.map((slot) => { + const info = catalog[slot]; + if (!info) return ''; + const opts = [ + ``, + ...info.variants.map( + (v) => + `` + ) + ].join(''); + return ` + `; + }).join(''); + + host.insertAdjacentHTML( + 'beforeend', + ` +

Kit mesh_ids (live 3D) — never body GLB swap

+
${slotRows || '

No mesh slots

'}
+ + ` + ); + host.querySelectorAll('[data-slot]').forEach((sel) => { + sel.addEventListener('change', () => { + const slot = sel.dataset.slot; + const variant = sel.value; + c.equipment?.setSlot(slot, variant === 'none' ? null : variant); + c._reGroundAfterEquip?.(); + c.ik?.setBones(c.equipment.findBones()); + this.onToast(`${slot} → ${variant}`); + this.onEquip(); + }); + }); + host.querySelector('[data-attack]')?.addEventListener('click', () => { + if (c.playWeaponAttack?.()) this.onToast('Weapon attack'); + else this.onToast('No attack clip'); + }); + } + + /* ── Inventory bag (Sample-InventorySlotsSet layout) ──────── */ + + _fillInventory() { + const host = this.el.querySelector('[data-panel="inventory"]'); + if (!host || this._tab !== 'inventory') return; + const bag = loadBag(); + const skin = loadBagSkin(); + const mainN = BAG_LAYOUT.mainCols * BAG_LAYOUT.mainRows; + const mainSlots = bag.slots.slice(0, mainN); + const utilSlots = bag.slots.slice(mainN); + + const cell = (item, i) => ` + `; + + host.innerHTML = ` +
+
+

Inventory

+ +
+
${mainSlots.map((it, i) => cell(it, i)).join('')}
+
${utilSlots.map((it, j) => cell(it, mainN + j)).join('')}
+

LMB pick · RMB Equip / Split / Drop / Deposit / Inspect · then Character paperdoll.

+
+ + Warlords Craft bag ↗ + ui Inventory ↗ +
+
+ `; + + const h = this._itemCtxHandlers(); + host.querySelectorAll('[data-bag-i]').forEach((btn) => { + btn.addEventListener('click', () => { + const i = Number(btn.dataset.bagI); + if (this._pickedBagIndex === i) { + this._pickedBagIndex = null; + } else { + this._pickedBagIndex = bag.slots[i] ? i : null; + if (this._pickedBagIndex != null) { + this.onToast(`Picked ${bag.slots[i].name} — Character tab + LMB slot, or RMB → Equip`); + } + } + this._fillInventory(); + }); + bindItemContextMenu( + btn, + () => { + const i = Number(btn.dataset.bagI); + const bag2 = loadBag(); + return { + source: 'bag', + bagIndex: i, + item: bag2.slots[i] || null + }; + }, + h + ); + }); + host.querySelector('[data-bag-skin]')?.addEventListener('change', (e) => { + saveBagSkin(e.target.value); + this.onToast(`Bag skin · ${e.target.value}`); + }); + host.querySelector('[data-seed-bag]')?.addEventListener('click', () => { + localStorage.removeItem('casting.mainPanel.bag.v2'); + localStorage.removeItem('casting.mainPanel.bag.v1'); + ensureDemoBag(); + this.refresh(); + }); + } + + _fillProfessions() { + const host = this.el.querySelector('[data-panel="professions"]'); + if (!host || this._tab !== 'professions') return; + if (!this._profTab) this._profTab = 'fisher'; + const prog = loadProfessionProgress(); + const tree = PROFESSION_TREES.find((t) => t.id === this._profTab) || PROFESSION_TREES[0]; + + // —— Live Fishing harvest tree (rods · nautical · SWG meals) —— + if (tree.liveTree && tree.id === 'fisher') { + this._fillFishingProfession(host, tree); + return; + } + + const p = prog[tree.id] || { level: 1, unlocked: [] }; + + host.innerHTML = ` +

WCS profession skill trees — production craft SSOT + grudgewarlords.com/craft/ + · Fishing tab uses live lab harvest tree (nautical + RGB meals). +

+
+
+ ${PROFESSION_TREES.map( + (t) => ` + ` + ).join('')} +
+
+
${tree.label}Lv ${p.level} · ${p.unlocked?.length || 0} nodes
+ ${tree.nodes + .map((n) => { + const on = p.unlocked?.includes(n.id); + return ` +
+ T${n.tier} +
+ ${n.name} +
${n.desc}
+
+ +
`; + }) + .join('')} +
+
+ `; + host.querySelectorAll('[data-prof]').forEach((b) => { + b.addEventListener('click', () => { + this._profTab = b.dataset.prof; + this._fillProfessions(); + }); + }); + host.querySelectorAll('[data-unlock]').forEach((b) => { + b.addEventListener('click', () => { + unlockProfessionNode(tree.id, b.dataset.unlock); + this.onToast(`${tree.label} · unlocked`); + this._fillProfessions(); + }); + }); + } + + /** Live Fishing profession: skill tree · rod select · SWG RGB meals */ + _fillFishingProfession(host, tree) { + const st = loadFishingProf(); + const mods = resolveProfessionMods(st, 0); + const meals = st.meals || {}; + + host.innerHTML = ` +

+ Fishing harvest profession (ObjectStore gathering.Fishing) · rods like Grudge Angler · + skill tree grants nautical speed · meals = SWG one per Red/Green/Blue + (same as Main Panel chef foodSystem). + api/v1/fishing-profession.json +

+
+
+ ${PROFESSION_TREES.map( + (t) => ` + ` + ).join('')} +
+ +
+ Level ${st.level} · XP ${Math.floor(st.xp)} · SP ${st.skillPoints} + Nautical ×${mods.nauticalSpeedMul.toFixed(2)} · size≤${mods.maxSizeRank} · qty ${mods.catchQty} +
+ +
+ ${ROD_TYPES.map( + (r) => ` + ` + ).join('')} +
+ +
Luressize match · rod lure tier
+
+ ${FISHING_LURES.map((l) => { + const rod = ROD_TYPES.find((r) => r.id === st.poleId) || ROD_TYPES[0]; + const ok = (l.tier || 0) <= (rod.lureSlotTier ?? 0); + return ` + `; + }).join('')} +
+ +
+ ${['red', 'green', 'blue'] + .map((c) => { + const active = meals[c]; + const list = MEAL_RECIPES.filter((m) => m.color === c); + return ` +
+
${c.toUpperCase()} + ${active ? active.label : 'empty'}
+ ${list + .map( + (m) => ` + ` + ) + .join('')} +
`; + }) + .join('')} +
+ +
+
Harvest skill tree${st.unlocked.length} / ${FISHING_SKILL_TREE.length}
+ ${FISHING_SKILL_TREE.map((n) => { + const on = st.unlocked.includes(n.id); + const can = canUnlockNode(n, st.level, st.unlocked) && st.skillPoints >= n.cost; + const bonus = Object.entries(n.bonuses || {}) + .map(([k, v]) => `${k}:${v}`) + .join(' · '); + return ` +
+ L${n.requiresLevel} +
+ ${n.label} +
${n.blurb}
+
${bonus}
+
+ +
`; + }).join('')} +
+
Catch rewardsforms · mounts · recipes
+
+ ${(() => { + const rw = loadRewardsState(); + const bits = []; + if (rw.forms.length) bits.push(`Forms: ${rw.forms.join(', ')}`); + if (rw.mounts.length) bits.push(`Mounts: ${rw.mounts.join(', ')}`); + if (rw.recipes.length) bits.push(`Recipes: ${rw.recipes.slice(0, 6).join(', ')}${rw.recipes.length > 6 ? '…' : ''}`); + if (rw.caught.length) bits.push(`Caught: ${rw.caught.join(', ')}`); + return bits.length ? bits.join(' · ') : 'None yet · land Aetherwing Turtle for form/mount · rare deep fish for craft recipes'; + })()} +
+ +
Docks (boat berth)claim for T2/T3
+

T1 placeable as building anywhere. T2/T3 only inside your own claim flag.

+
+ + ${DOCK_TIERS.map( + (d) => ` + ` + ).join('')} +
+
${loadOwnClaim() ? `Claim planted · r=${loadOwnClaim().radiusM}m` : 'No claim planted yet'}
+ +

Shift+F fish · freeride nautical · super-rares: ocean creature / glow whale / aetherwing · pulbo deep.

+
+ `; + + host.querySelectorAll('[data-prof]').forEach((b) => { + b.addEventListener('click', () => { + this._profTab = b.dataset.prof; + this._fillProfessions(); + }); + }); + host.querySelectorAll('[data-rod]').forEach((b) => { + b.addEventListener('click', () => { + st.poleId = b.dataset.rod; + saveFishingProf(st); + try { + window.__castingApp?.fishing?.setPoleId?.(st.poleId); + } catch { + /* optional */ + } + this.onToast(`Rod · ${st.poleId}`); + this._fillProfessions(); + }); + }); + host.querySelectorAll('[data-lure]').forEach((b) => { + b.addEventListener('click', () => { + const live = window.__castingApp?.fishing; + if (live?.setLureId) { + live.setLureId(b.dataset.lure); + } else { + st.lureId = b.dataset.lure; + saveFishingProf(st); + } + this._fillProfessions(); + }); + }); + host.querySelectorAll('[data-eat]').forEach((b) => { + b.addEventListener('click', () => { + // Prefer live controller (same localStorage key) so freeride nautical updates immediately + const live = window.__castingApp?.fishing; + if (live?.eatMeal) live.eatMeal(b.dataset.eat); + else eatFishingMeal(st, b.dataset.eat, 0); + this.onToast(`Meal · ${b.dataset.eat}`); + this._fillProfessions(); + }); + }); + host.querySelectorAll('[data-fish-unlock]').forEach((b) => { + b.addEventListener('click', () => { + const live = window.__castingApp?.fishing; + const ok = live?.unlockNode + ? live.unlockNode(b.dataset.fishUnlock) + : unlockFishingNode(st, b.dataset.fishUnlock); + this.onToast(ok ? `Unlocked · ${b.dataset.fishUnlock}` : 'Cannot unlock (level / SP / prereq)'); + this._fillProfessions(); + }); + }); + host.querySelector('[data-plant-claim]')?.addEventListener('click', () => { + const app = window.__castingApp; + const p = app?.character?.position || { x: 0, z: 0 }; + plantOwnClaim(p.x || 0, p.z || 0, 45); + this.onToast('Claim flag planted (lab) · T2/T3 docks allowed in radius'); + this._fillProfessions(); + }); + host.querySelectorAll('[data-place-dock]').forEach((b) => { + b.addEventListener('click', () => { + const app = window.__castingApp; + const p = app?.character?.position || { x: 0, y: 0, z: 0 }; + const r = placeDock( + { dockId: b.dataset.placeDock, x: p.x || 0, y: p.y || 0, z: p.z || 0 }, + {} + ); + if (!r.ok) { + this.onToast(r.reason || 'Cannot place dock'); + } else { + this.onToast(`Placed ${r.dock?.label} · boats ×${r.dock?.boatSlots || 1}`); + // Optional: spawn mesh via training deploy if available + try { + app?.trainingRoom?.placeMesh?.(r.dock.meshUrl, p); + } catch { + /* visual place later */ + } + } + this._fillProfessions(); + }); + }); + } + + _fillSlotsAdmin() { + const host = this.el.querySelector('[data-panel="slots"]'); + if (!host || this._tab !== 'slots') return; + const ovr = loadSlotAdminOverrides(); + host.innerHTML = ` +

Admin · paperdoll slot labels & accept filters (local). Used by LMB inventory picker.

+
+ ${ALL_PAPERDOLL_SLOTS.map((s) => { + const o = ovr[s.id] || {}; + return ` + `; + }).join('')} +
+
+ + +
+

Dev tool: also under Admin F1 · Main Panel slots. Bag art: /ui/inventory/inventory-slots-set.png

+ `; + host.querySelector('[data-save-slots]')?.addEventListener('click', () => { + const next = {}; + host.querySelectorAll('[data-slot-label]').forEach((inp) => { + const id = inp.dataset.slotLabel; + const acc = host.querySelector(`[data-slot-accept="${id}"]`)?.value || ''; + next[id] = { + label: inp.value.trim(), + accepts: acc + .split(',') + .map((x) => x.trim()) + .filter(Boolean), + }; + }); + saveSlotAdminOverrides(next); + this.onToast('Slot system saved'); + }); + host.querySelector('[data-reset-slots]')?.addEventListener('click', () => { + saveSlotAdminOverrides({}); + this.refresh(); + }); + } + + /* ── Weapon equipped (catalog prefab + skills + icon + 3D) ─── */ + + async _fillWeapon() { + const host = this.el.querySelector('[data-panel="weapon"]'); + if (!host || this._tab !== 'weapon') return; + + const c = this.character; + host.innerHTML = `

Loading weapon prefabs + T0 skills…

`; + + try { + await ensureWeaponCatalog(); + } catch (err) { + host.innerHTML = `

Catalog failed: ${err?.message || err}

`; + return; + } + if (this._tab !== 'weapon') return; + + const weapons = listEquippableWeapons(); + const equipped = getEquippedWeapon(); + const slot3Id = getEquippedSlot3Id(); + const loadout = c.equipment?.loadout || {}; + const kitSlot = WEAPON_SLOTS.find((s) => loadout[s] && loadout[s] !== 'none') || null; + + const equipCards = weapons + .map((w) => { + const on = equipped?.id === w.id; + const skills = [w.slot1?.name, w.slot2?.name, w.slot3Options?.[0]?.name] + .filter(Boolean) + .join(' · '); + return ` + `; + }) + .join(''); + + const skillRows = equipped + ? [ + equipped.slot1, + equipped.slot2, + ...(equipped.slot3Options || []) + ] + .filter(Boolean) + .map((sk) => { + const isS3 = (equipped.slot3Options || []).some((o) => o.id === sk.id); + const active = !isS3 || sk.id === slot3Id; + return ` + `; + }) + .join('') + : '

Equip a weapon to load its 3-slot skills.

'; + + host.innerHTML = ` +

Import from WEAPON_SKILLS.html · t0-weapons.json — no local skill invent

+

Starters: Apprentice Wand (t0-wand) · Sapling Staff (t0-nature-staff). Mage Wand = later class item.

+

Equipped: ${equipped ? equipped.name : 'none'} · kit mesh: ${kitSlot || '—'} · pack: ${c.animPackId}

+ ${ + equipped + ? `
+ +
+
${equipped.name} · ${equipped.weaponType}
+
model: ${(equipped.modelUrl || '—').split('/').pop()}
+
icon: ${(equipped.iconUrl || '—').split('/').pop()}
+
+
` + : '' + } +
${equipCards || '

No T0 weapons

'}
+
+ + + +
+

Skills on this weapon (1–2 fixed · 3 choose)

+
${skillRows}
+ +

Combat Q · 1–3 weapon skills · mesh_ids kit + CDN GLB hand attach

+ `; + + host.querySelectorAll('[data-equip-id]').forEach((btn) => { + btn.addEventListener( + 'click', + this._busyGuard(async () => { + const id = btn.dataset.equipId; + try { + const result = await equipWeaponById(id, { + character: c, + onToast: (m) => this.onToast(m) + }); + setActiveSkillTree('equipped'); + const drc = this.getDrc?.(); + if (drc) drc.skills = getActiveSkills(); + this.onEquip?.(); + this.onToast( + `Try skills 1–3 · ${result.hotbar.map((s) => s.label).join(' · ')}` + ); + this.refresh(); + } catch (err) { + this.onToast(err?.message || 'Equip failed'); + } + }) + ); + }); + + host.querySelector('[data-unequip]')?.addEventListener('click', () => { + unequipWeapon({ character: c, onToast: (m) => this.onToast(m) }); + // Stay on equipped tree (empty) — do not silently jump to free kit tree + setActiveSkillTree('equipped'); + const drc = this.getDrc?.(); + if (drc) drc.skills = getActiveSkills(); + this.refresh(); + }); + + host.querySelector('[data-export-prefab]')?.addEventListener('click', () => { + if (downloadEquippedPrefab()) this.onToast('Downloaded Warlords weapon prefab JSON'); + else this.onToast('Equip a weapon first'); + }); + + host.querySelector('[data-copy-prefab]')?.addEventListener('click', async () => { + const data = exportEquippedPrefab(); + if (!data) { + this.onToast('Equip a weapon first'); + return; + } + try { + await navigator.clipboard.writeText(JSON.stringify(data, null, 2)); + this.onToast('Prefab JSON copied'); + } catch { + this.onToast('Copy failed — use Export download'); + } + }); + + host.querySelectorAll('[data-wskill]').forEach((btn) => { + btn.addEventListener('click', () => { + const id = btn.dataset.wskill; + const choice = btn.dataset.choice === '1'; + if (choice) { + setEquippedSlot3(id); + setActiveSkillTree('equipped'); + const drc = this.getDrc?.(); + if (drc) drc.skills = getActiveSkills(); + this.onToast(`Slot 3 · ${btn.querySelector('span:nth-child(3)')?.textContent || id}`); + this.refresh(); + return; + } + // Fire skill by bar slot + const bar = getActiveSkills(); + const idx = bar.findIndex((s) => s.id === id); + if (idx >= 0) this.getDrc?.()?.useSkill?.(idx); + }); + }); + + host.querySelector('[data-strike]')?.addEventListener('click', () => { + const drc = this.getDrc?.(); + if (drc?.useMeleeStrike) drc.useMeleeStrike(); + else if (c.playWeaponAttack?.()) this.onToast('Attack'); + }); + } + + /* ── Race (all 6 Toon RTS) ──────────────────────────────────── */ + + _fillRace() { + const host = this.el.querySelector('[data-panel="race"]'); + if (!host || this._tab !== 'race') return; + + const current = this.character.raceId; + const cards = Object.values(RACES) + .map((r) => { + const file = (r.kitUrl || '').split('/').pop(); + return ` + `; + }) + .join(''); + + host.innerHTML = ` +

Toon RTS GLB · assets…/asset-packs/toon-rts-characters/glb/characters/

+
${cards}
+

Atlas rebind optional — embeds preferred. No races-bake / FBX play path.

+ `; + + host.querySelectorAll('[data-race]').forEach((btn) => { + btn.addEventListener( + 'click', + this._busyGuard(async () => { + const id = btn.dataset.race; + this.onToast(`Loading ${id}…`); + if (this.onRace) await this.onRace(id); + else await this.character.setRace?.(id); + this.onToast(`Race · ${id}`); + }) + ); + }); + } + + /* ── Mesh catalog ──────────────────────────────────────────── */ + + _fillMesh() { + const host = this.el.querySelector('[data-panel="mesh"]'); + if (!host || this._tab !== 'mesh') return; + + const catalog = this.character.equipment?.getCatalogSummary?.() || {}; + const rows = Object.entries(catalog) + .map(([slot, info]) => { + const vis = info.selected && info.selected !== 'none' ? info.selected : '—'; + return `${slot}${vis}${(info.variants || []).length}`; + }) + .join(''); + + let meshList = ''; + this.character.model?.traverse((o) => { + if (!o.isMesh && !o.isSkinnedMesh) return; + if (!o.name) return; + meshList += `
${o.visible ? '●' : '○'} ${o.name}
`; + }); + + host.innerHTML = ` +

Slot catalog + live kit mesh visibility

+ + + ${rows || ''} +
SlotOnVars
empty
+
${meshList || '

No model

'}
+ `; + } + + /* ── Mount ─────────────────────────────────────────────────── */ + + _fillMount() { + const host = this.el.querySelector('[data-panel="mount"]'); + if (!host || this._tab !== 'mount') return; + + const ride = !!this.character._rideActive; + const mode = settings.mode; + + host.innerHTML = ` +

Windsurf / hoverboard ride — Walk mode path draw. RideIK feet + hands only while mounted.

+
+
Mode${mode}
+
Ride active${ride ? 'yes' : 'no'}
+
+
+ + +
+

Draw path in Walk mode → board ride. Dismount at path end. Not a second mount engine.

+ `; + + host.querySelector('[data-walk]')?.addEventListener('click', () => { + this.onMode?.('walk'); + this.onMountToggle?.(true); + this.onToast('Walk / mount mode — draw a path'); + this.refresh(); + }); + host.querySelector('[data-cast]')?.addEventListener('click', () => { + this.onMode?.('casting'); + this.onMountToggle?.(false); + this.onToast('Cast mode'); + this.refresh(); + }); + } + + /* ── Animations library ────────────────────────────────────── */ + + _fillAnims() { + const host = this.el.querySelector('[data-panel="anims"]'); + if (!host || this._tab !== 'anims') return; + + const c = this.character; + const roles = c.listAnimRoles?.() || [...(c.actions?.keys?.() || [])]; + const packMeta = ANIM_PACK_META[c.animPackId] || {}; + + host.innerHTML = ` + +

Skills: ${packMeta.skills || '—'} · Loco: ${packMeta.locomotion || '—'}

+
+ ${roles + .map((r) => ``) + .join('') || '

No clips bound

'} +
+

Baked Bip001 · open/prod anims · one mixer · rotation-only rematch

+ `; + + host.querySelector('[data-pack]')?.addEventListener( + 'change', + this._busyGuard(async (e) => { + const id = await c.setAnimPack?.(e.target.value); + this.onToast(`Pack · ${id}`); + }) + ); + + host.querySelectorAll('[data-clip]').forEach((btn) => { + btn.addEventListener('click', () => { + const role = btn.dataset.clip; + if (c.playLibraryClip?.(role) || c.play?.(role, 0.15)) { + this.onToast(`Play · ${role}`); + } + }); + }); + } + + /* ── Prefabs (full game items · production SSOT) ───────────── */ + + async _fillPrefabs() { + const host = this.el.querySelector('[data-panel="prefabs"]'); + if (!host || this._tab !== 'prefabs') return; + + host.innerHTML = `

Loading game-library + master catalogs…

`; + try { + this._gameItems = await loadGameItemCatalog(); + } catch (err) { + host.innerHTML = `

Catalog load failed: ${err?.message || err}

`; + return; + } + if (this._tab !== 'prefabs') return; + + const cat = this._gameItems; + const counts = cat.counts || {}; + const rows = queryGameItems(cat, { + category: this._prefabCat, + q: this._prefabQ, + limit: 60 + }); + const sel = this._prefabSelected; + + const catBtns = PREFAB_CATEGORIES.map( + (c) => + `` + ).join(''); + + const list = rows + .map((r) => { + const on = sel?.id === r.id; + return ` + `; + }) + .join(''); + + // Scaffold pack for selected equippable (async fill below if needed) + const eqCache = getEquippableWeaponsCache(); + const eqWeapon = sel && eqCache?.byId?.get?.(sel.id); + + const detail = sel + ? `
+ +
+
${sel.name} · ${sel.category}
+
${(sel.description || '').slice(0, 160)}
+
id: ${sel.id}${sel.uuid ? ' · ' + sel.uuid : ''}
+
model: ${(sel.modelUrl || '—').split('/').pop()}
+
stats: ${sel.stats ? JSON.stringify(sel.stats).slice(0, 120) : '—'}
+
+
+
+ ${sel.equippable ? `` : ''} + + + +
+ +
${eqWeapon ? 'T0/equip row linked — open Scaffold pack' : 'Scaffold works best on T0 / weapon equip rows'}
+
`
+      : '

Select a row to inspect · scaffold · export. UUID graph + craft + gen briefs.

'; + + host.innerHTML = ` +

Prefab scaffold control — access catalogs · validate · generate briefs · export

+

+ Item Database ↗ · + WEAPON_SKILLS ↗ · + Hub ↗ · + API docs ↗ +

+

No invented ITEM-*/SKIL-* · gen = drafts · mint in ObjectStore pipelines

+
${catBtns}
+ + ${detail} +
${list || '

No rows (try another category)

'}
+

Docs: PREFAB_SCAFFOLD_CONTROL_SSOT · WEAPON_PREFAB_UUID_SSOT · GAME_ITEM_PREFAB_PRODUCTION

+ `; + + host.querySelectorAll('[data-pcat]').forEach((btn) => { + btn.addEventListener('click', () => { + this._prefabCat = btn.dataset.pcat; + this._prefabSelected = null; + this._fillPrefabs(); + }); + }); + + const search = host.querySelector('[data-pq]'); + search?.addEventListener('change', () => { + this._prefabQ = search.value || ''; + this._fillPrefabs(); + }); + search?.addEventListener('keydown', (e) => { + if (e.key === 'Enter') { + this._prefabQ = search.value || ''; + this._fillPrefabs(); + } + }); + + host.querySelectorAll('[data-pitem]').forEach((btn) => { + btn.addEventListener('click', () => { + const id = btn.dataset.pitem; + const pool = queryGameItems(cat, { + category: this._prefabCat, + q: this._prefabQ, + limit: 200 + }); + this._prefabSelected = pool.find((r) => r.id === id) || null; + this._fillPrefabs(); + }); + }); + + host.querySelector('[data-pequip]')?.addEventListener('click', async () => { + if (!sel?.equippable) return; + try { + await equipWeaponById(sel.id, { + character: this.character, + onToast: (m) => this.onToast(m) + }); + setActiveSkillTree('equipped'); + const drc = this.getDrc?.(); + if (drc) drc.skills = getActiveSkills(); + this.onToast(`Equipped ${sel.name} · combat 1–3`); + this.onEquip?.(); + } catch (err) { + this.onToast(err?.message || 'Equip failed (not a T0/weapon id?)'); + } + }); + + host.querySelector('[data-pexport]')?.addEventListener('click', () => { + const snap = exportItemPrefabSnapshot(sel); + if (!snap) return; + const blob = new Blob([JSON.stringify(snap, null, 2)], { type: 'application/json' }); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = `${snap.id}.game-item-prefab.json`; + a.click(); + URL.revokeObjectURL(a.href); + this.onToast('Downloaded prefab snapshot'); + }); + + host.querySelector('[data-pcopy]')?.addEventListener('click', async () => { + const snap = exportItemPrefabSnapshot(sel); + if (!snap) return; + try { + await navigator.clipboard.writeText(JSON.stringify(snap, null, 2)); + this.onToast('Prefab JSON copied'); + } catch { + this.onToast('Copy failed'); + } + }); + + const statusEl = host.querySelector('[data-scaffold-status]'); + const outEl = host.querySelector('[data-scaffold-out]'); + const genRow = host.querySelector('[data-scaffold-gen]'); + + const ensureScaffold = async () => { + if (!sel) return null; + if (this._scaffoldPack?.identity?.id === sel.id) return this._scaffoldPack; + statusEl && (statusEl.textContent = 'Building scaffold pack…'); + await loadPrefabScaffold(); + const eq = getEquippableWeaponsCache()?.byId?.get?.(sel.id); + const weapon = + eq || + ({ + id: sel.id, + uuid: sel.uuid, + name: sel.name, + tier: sel.tier, + weaponType: sel.weaponType, + stats: sel.stats, + iconUrl: sel.iconUrl, + modelUrl: sel.modelUrl, + meshSlot: sel.slot || 'sword', + animPack: 'sword_shield', + labStyle: 'melee', + slot1: null, + slot2: null, + slot3Options: [], + rawPrefab: sel.raw, + present: null + }); + const pack = await buildItemScaffoldPack(weapon); + this._scaffoldPack = pack; + const v = pack.validation; + statusEl && + (statusEl.textContent = `Scaffold · ${v?.score ?? '?'}/${v?.max ?? 6} layers · ${ + v?.ok ? 'OK' : 'gaps: ' + (v?.missing || []).join(', ') + } · craft: ${pack.generation?.craft?.source || '—'}`); + genRow?.removeAttribute('hidden'); + if (outEl) { + outEl.textContent = JSON.stringify( + { + uuids: pack.exportPrefab?.uuids || pack.contract?.uuids, + validation: pack.validation, + craft: pack.generation?.craft, + use: pack.use + }, + null, + 2 + ); + } + return pack; + }; + + host.querySelector('[data-pscaffold]')?.addEventListener('click', async () => { + try { + await ensureScaffold(); + this.onToast('Scaffold ready · gen briefs below'); + } catch (err) { + this.onToast(err?.message || 'Scaffold failed'); + } + }); + + host.querySelectorAll('[data-pgen]').forEach((btn) => { + btn.addEventListener('click', async () => { + try { + const pack = await ensureScaffold(); + if (!pack) return; + const kind = btn.dataset.pgen; + const map = { + icon: pack.generation?.icon, + sprite3d: pack.generation?.sprite3d, + script: pack.generation?.itemScript, + craft: pack.generation?.craft, + full: pack + }; + const data = map[kind]; + if (!data) { + this.onToast('No data for ' + kind); + return; + } + if (outEl) outEl.textContent = JSON.stringify(data, null, 2); + downloadJson(data, `${sel.id}.${kind}.json`); + this.onToast(`Downloaded ${kind} · ${sel.id}`); + } catch (err) { + this.onToast(err?.message || 'Gen failed'); + } + }); + }); + } + + /* ── Weapon skills only (item owns skills — no free skill trees) ── */ + + async _fillSkills() { + const host = this.el.querySelector('[data-panel="skills"]'); + if (!host || this._tab !== 'skills') return; + + host.innerHTML = `

Loading weapon skills catalog…

`; + try { + await ensureWeaponCatalog(); + } catch (err) { + host.innerHTML = `

Catalog failed: ${err?.message || err}

`; + return; + } + if (this._tab !== 'skills') return; + + const equipped = getEquippedWeapon(); + const slot3Id = getEquippedSlot3Id(); + const bar = getActiveSkills(); + const adminLine = labAdminStatusLine(); + const weapons = listEquippableWeapons(); + const maxT = labMaxTier(); + const lv = labPlayerLevel(); + + // Always force production path + setActiveSkillTree('equipped'); + + let skillRows = ''; + if (equipped) { + ensureWeaponSkillIcons(equipped); + const rows = weaponSkillTreeRows(equipped, slot3Id); + skillRows = rows + .map((sk) => { + const tip = weaponSkillTooltip(sk, equipped).replace(/"/g, '"'); + const icon = sk.iconUrl || equipped.iconUrl || ''; + const activeCls = sk.active ? '' : 'is-dim'; + const key = sk.keyLabel || '?'; + const meta = [ + sk.damageType || '', + sk.damage != null ? `${sk.damage} dmg` : '', + sk.cooldown != null ? `CD ${sk.cooldown}s` : '', + sk.castTime > 0 ? `cast ${sk.castTime}s` : 'Instant', + sk.choice ? (sk.active ? 'slot 3 · on' : 'slot 3 · choice') : sk.slotType || '' + ] + .filter(Boolean) + .join(' · '); + return ` + `; + }) + .join(''); + } else { + skillRows = `

No weapon equipped — pick one below. Item owns skills (free skill trees removed).

`; + } + + const equipCards = weapons + .slice(0, 48) + .map((w) => { + const on = equipped?.id === w.id; + const stub = + w.slot1?.source === 'stub-fallback' || w.slot2?.source === 'stub-fallback' + ? ' · ⚠ stub' + : ''; + return ` + `; + }) + .join(''); + + const hotbarHtml = bar.length + ? bar + .map((s) => { + const tip = (s.tooltip || `${s.label} · ${s.cooldown}s`).replace(/"/g, '"'); + return ` + `; + }) + .join('') + : '

Hotbar empty — equip a weapon.

'; + + host.innerHTML = ` +

Weapon skills · ${adminLine}

+

SSOT: WEAPON_SKILLS · equip owns skills · L${lv} max T${maxT} · no free trees

+ ${ + equipped + ? `
+ +
+
${equipped.name} · T${equipped.tier} · ${equipped.weaponType}
+
${equipped.animPack} · ${equipped.meshSlot}
+
+
` + : '' + } +

This weapon (icons + tooltips · 1–2 fixed · 3 choice)

+
${skillRows}
+

Combat hotbar (keys 1–3)

+
${hotbarHtml}
+
+ + +
+

Equip catalog (${weapons.length} · T0–T${maxT})

+
${equipCards || '

No weapons loaded

'}
+

Combat: 1–3 weapon skills · F interact · Tap Q dual set · harvest trees = world nodes not skill trees

+ `; + + host.querySelectorAll('[data-equip-id]').forEach((btn) => { + btn.addEventListener( + 'click', + this._busyGuard(async () => { + try { + const result = await equipWeaponById(btn.dataset.equipId, { + character: this.character, + onToast: (m) => this.onToast(m) + }); + setActiveSkillTree('equipped'); + const drc = this.getDrc?.(); + if (drc) drc.skills = getActiveSkills(); + this.onEquip?.(); + this.onToast( + `${result.weapon?.name} · ${(result.hotbar || []).map((s) => s.label).join(' · ')}` + ); + this.refresh(); + } catch (err) { + this.onToast(err?.message || 'Equip failed'); + } + }) + ); + }); + + host.querySelectorAll('[data-wskill]').forEach((btn) => { + btn.addEventListener('click', () => { + const isChoice = btn.dataset.choice === '1'; + const id = btn.dataset.wskill; + if (isChoice && id) { + setEquippedSlot3(id); + setActiveSkillTree('equipped'); + const drc = this.getDrc?.(); + if (drc) drc.skills = getActiveSkills(); + this.onToast(`Slot 3 · ${btn.querySelector('.inv-skill__name')?.textContent || id}`); + this.refresh(); + return; + } + const slot = Number(btn.dataset.bar); + if (!Number.isNaN(slot)) this.getDrc?.()?.useSkill?.(slot); + }); + }); + + host.querySelectorAll('[data-slot]').forEach((btn) => { + btn.addEventListener('click', () => { + this.getDrc?.()?.useSkill?.(Number(btn.dataset.slot)); + }); + }); + + host.querySelector('[data-open-weapon]')?.addEventListener('click', () => { + this.openTab?.('weapon'); + }); + + host.querySelector('[data-export-prefab]')?.addEventListener('click', () => { + if (downloadEquippedPrefab()) this.onToast('Downloaded Warlords weapon prefab JSON'); + else this.onToast('Equip a weapon first'); + }); + } + + /* ── Fleet API ─────────────────────────────────────────────── */ + + _fillApi() { + const host = this.el.querySelector('[data-panel="api"]'); + if (!host || this._tab !== 'api') return; + + const health = this.api.lastHealth; + const fleet = fleetDeploySnapshot(); + const assets = UI_ASSET_CATALOG.map( + (a) => + `
${a.id}${a.role}
` + ).join(''); + const catCounts = this._gameItems?.counts + ? Object.entries(this._gameItems.counts) + .map(([k, v]) => `${k}:${v}`) + .join(' · ') + : 'not loaded'; + + host.innerHTML = ` +

Dev + production — icons CDN 496 + lab minerals; catalogs info/objectstore; player bag Railway.

+
+
API base${(FLEET_API_DEFAULT || 'same-origin').replace('https://', '')}
+
Health${health ? (health.ok ? `OK ${health.latencyMs}ms` : health.message) : 'not checked'}
+
Token
+
Catalog${catCounts}
+
Player${fleet.authority.player}
+
Binaries${fleet.authority.binaries}
+
+
+ + + + +
+
+ + + +
+ +
+ +
+

UI assets (${UI_ASSET_CATALOG.length}) · harvest → Main bag + DropBag with icons

+
${assets}
+
+ + `; + + const out = host.querySelector('[data-api-out]'); + host.querySelector('[data-token]').textContent = this.api.getToken() ? 'present' : 'none'; + + host.querySelector('[data-fleet-bundle]')?.addEventListener('click', async () => { + out.textContent = 'loading…'; + const b = await this.api.fleetStatusBundle(); + host.querySelector('[data-health]').textContent = b.health?.ok + ? `OK ${b.health.latencyMs}ms` + : b.health?.message || 'fail'; + host.querySelector('[data-token]').textContent = b.hasToken ? 'present' : 'none'; + out.textContent = JSON.stringify(b, null, 2); + this.onToast( + b.health?.ok + ? `Fleet · chars ${b.characters?.characters?.length ?? 0} · inv ${b.inventory?.items?.length ?? 0}` + : b.health?.message || 'done' + ); + }); + + host.querySelector('[data-ping]')?.addEventListener('click', async () => { + out.textContent = 'ping…'; + const st = await this.api.health(); + host.querySelector('[data-health]').textContent = st.ok ? `OK ${st.latencyMs}ms` : st.message; + out.textContent = JSON.stringify({ health: st, fleet: fleet.authority }, null, 2); + this.onToast(st.ok ? 'API healthy' : 'API unreachable'); + }); + + host.querySelector('[data-chars]')?.addEventListener('click', async () => { + out.textContent = 'fetch…'; + const r = await this.api.listCharacters(); + out.textContent = JSON.stringify( + { + message: r.message, + count: r.characters.length, + sample: r.characters.slice(0, 5).map((c) => ({ + id: c.id || c.characterId, + name: c.name || c.displayName, + race: c.race || c.raceId + })) + }, + null, + 2 + ); + this.onToast(r.message); + }); + + host.querySelector('[data-inv]')?.addEventListener('click', async () => { + out.textContent = 'inventory…'; + const r = await this.api.listInventory(); + out.textContent = JSON.stringify(r, null, 2); + this.onToast(r.message); + }); + + host.querySelector('[data-load-cat]')?.addEventListener('click', async () => { + out.textContent = 'catalog…'; + await this._warmCatalogIcons(); + const c = this._gameItems; + host.querySelector('[data-cat]').textContent = c?.counts + ? Object.entries(c.counts) + .map(([k, v]) => `${k}:${v}`) + .join(' · ') + : 'fail'; + out.textContent = JSON.stringify({ counts: c?.counts, urls: c?.urls }, null, 2); + this.onToast('Catalog loaded · bag icons refreshed'); + this.refresh(); + }); + + host.querySelector('[data-seed-t0]')?.addEventListener('click', async () => { + try { + const cat = this._gameItems || (await loadGameItemCatalog()); + this._gameItems = cat; + const t0 = cat?.byCategory?.t0 || []; + let n = 0; + for (const row of t0.slice(0, 8)) { + const bi = bagItemFromCatalogRow(row); + if (bi) { + bagAdd(bi); + n++; + } + } + for (const id of ['t0-sword', 't0-wand', 't0-tool']) { + const row = t0.find((r) => r.id === id); + if (row) bagAdd(bagItemFromCatalogRow(row)); + else + bagAdd( + enrichBagSlotIcon({ + id, + name: id, + kind: 'weapon', + qty: 1, + slotHint: 'mainHand' + }) + ); + } + this.onToast(`Imported ${n} T0 rows → bag`); + this._setTab('inventory'); + } catch (err) { + this.onToast(err?.message || 'T0 import fail'); + } + }); + + host.querySelector('[data-open-equip]')?.addEventListener('click', () => { + this.openTab('character'); + this.onToast('Character paperdoll · RMB equip menu'); + }); + } + + dispose() { + closeItemContextMenu(); + this.el.remove(); + } +} diff --git a/src/ui/ModeRadial.js b/src/ui/ModeRadial.js new file mode 100644 index 0000000..8f30ae8 --- /dev/null +++ b/src/ui/ModeRadial.js @@ -0,0 +1,144 @@ +/** + * Hold-Q mode radial + Hold-R tool radial (DOM, Open ModeRadial / RadialMenu parity). + */ +import { MODE_SWITCH_RADIAL, HARVEST_TOOL_RADIAL, MODE_LABEL } from '../combat/playerActivity.js'; +import './modeRadial.css'; + +export class ModeRadial { + constructor() { + this.el = document.createElement('div'); + this.el.className = 'mode-radial'; + this.el.hidden = true; + this.el.setAttribute('role', 'dialog'); + document.body.appendChild(this.el); + + /** @type {'none'|'mode'|'tool'} */ + this.kind = 'none'; + /** @type {'combat'|'harvest'} */ + this.current = 'combat'; + /** @type {string|null} */ + this.aimId = null; + this._toolId = 'pick'; + } + + /** + * @param {{ + * kind: 'mode'|'tool', + * current: 'combat'|'harvest', + * aimId?: string|null, + * toolId?: string + * }} state + */ + show(state) { + this.kind = state.kind; + this.current = state.current || 'combat'; + this.aimId = state.aimId ?? null; + if (state.toolId) this._toolId = state.toolId; + this.el.hidden = false; + this._render(); + } + + hide() { + this.kind = 'none'; + this.el.hidden = true; + this.el.innerHTML = ''; + } + + /** + * Pointer position → aim id (mode: up/down · tool: angle wedges). + * @param {number} clientX + * @param {number} clientY + * @returns {string|null} + */ + aimFromPointer(clientX, clientY) { + const cx = window.innerWidth * 0.5; + const cy = window.innerHeight * 0.5; + if (this.kind === 'mode') { + const dy = clientY - cy; + // mouse up (negative dy) = combat + this.aimId = dy < -12 ? 'mode_combat' : dy > 12 ? 'mode_harvest' : this.aimId; + if (!this.aimId) { + this.aimId = this.current === 'harvest' ? 'mode_harvest' : 'mode_combat'; + } + this._render(); + return this.aimId; + } + if (this.kind === 'tool') { + const opts = HARVEST_TOOL_RADIAL; + const n = opts.length; + const ang = (Math.atan2(clientY - cy, clientX - cx) * 180) / Math.PI; + const slice = 360 / n; + let a = (ang + 90 + 360) % 360; + const idx = Math.floor(a / slice) % n; + this.aimId = opts[idx]?.id || null; + this._render(); + return this.aimId; + } + return null; + } + + getAimId() { + return this.aimId; + } + + _render() { + if (this.kind === 'mode') { + const aim = this.aimId || (this.current === 'harvest' ? 'mode_harvest' : 'mode_combat'); + this.el.innerHTML = ` +
+
+ + Combat + mouse up +
+
+ Q hold + ${MODE_LABEL[this.current] || this.current} +
+
+ 🪓 + Harvest + mouse down +
+
+

Release Q to confirm · tap Q toggles mode

+ `; + return; + } + + // Tool wheel + const opts = HARVEST_TOOL_RADIAL; + const n = opts.length; + const wedges = opts + .map((o, i) => { + const rot = (360 / n) * i - 90; + const hot = this.aimId === o.id || (!this.aimId && o.id === this._toolId); + return ` +
+ + ${o.glyph} + ${o.label} + +
`; + }) + .join(''); + this.el.innerHTML = ` +
+ ${wedges} +
+ R hold + Tools +
+
+

Release R to equip · F harvests nearest for tool

+ `; + } + + dispose() { + this.el.remove(); + } +} diff --git a/src/ui/OverheadNameplates.js b/src/ui/OverheadNameplates.js new file mode 100644 index 0000000..73afb09 --- /dev/null +++ b/src/ui/OverheadNameplates.js @@ -0,0 +1,188 @@ +/** + * World-space overhead HP bars (Bars HUD pack). + * + * Enemy → overhead_health_003 · Ally → overhead_health_001 · fillers inside. + * Screen-projects anchors each frame (no CSS2D dependency). + * + * @see barsHudUi.js · craftpix-rpg-mmo-ui § Bars pack + */ + +import { Vector3 } from 'three'; +import { + createOverheadBarEl, + setOverheadBarHp, + applyBarsHudCssVars +} from './barsHudUi.js'; +import './bars-hud.css'; + +const _v = new Vector3(); + +/** + * @typedef {{ + * id: string, + * kind: 'enemy'|'ally', + * object: import('three').Object3D, + * el: HTMLElement, + * offsetY: number, + * getHp01?: () => number + * }} OhEntry + */ + +export class OverheadNameplates { + /** + * @param {{ host?: HTMLElement, maxDist?: number }} [opts] + */ + constructor(opts = {}) { + this.maxDist = opts.maxDist ?? 42; + /** @type {HTMLElement} */ + this.root = document.createElement('div'); + this.root.className = 'hud-overheads'; + this.root.setAttribute('aria-hidden', 'true'); + const host = opts.host || document.body; + host.appendChild(this.root); + applyBarsHudCssVars(document.documentElement); + + /** @type {Map} */ + this._byId = new Map(); + this._seq = 0; + } + + /** + * @param {import('three').Object3D} object + * @param {{ + * kind?: 'enemy'|'ally', + * name?: string, + * id?: string, + * offsetY?: number, + * getHp01?: () => number + * }} [opts] + */ + attach(object, opts = {}) { + if (!object) return null; + const kind = opts.kind === 'ally' ? 'ally' : 'enemy'; + const id = opts.id || object.uuid || `oh_${++this._seq}`; + this.detach(id); + + const el = createOverheadBarEl(kind, { name: opts.name || object.userData?.displayName }); + this.root.appendChild(el); + + const getHp01 = + opts.getHp01 || + (() => { + const h = object.userData?.hp01; + return Number.isFinite(h) ? h : 1; + }); + + /** @type {OhEntry} */ + const entry = { + id, + kind, + object, + el, + offsetY: opts.offsetY ?? (kind === 'ally' ? 2.05 : 2.15), + getHp01 + }; + this._byId.set(id, entry); + object.userData.overheadBarId = id; + setOverheadBarHp(el, getHp01()); + return id; + } + + /** + * @param {string|import('three').Object3D} idOrObject + */ + detach(idOrObject) { + let id = typeof idOrObject === 'string' ? idOrObject : idOrObject?.userData?.overheadBarId; + if (!id && idOrObject && typeof idOrObject === 'object') { + for (const [k, e] of this._byId) { + if (e.object === idOrObject) { + id = k; + break; + } + } + } + if (!id) return; + const e = this._byId.get(id); + if (!e) return; + e.el.remove(); + this._byId.delete(id); + if (e.object?.userData) delete e.object.userData.overheadBarId; + } + + clear() { + for (const id of [...this._byId.keys()]) this.detach(id); + } + + /** + * Rebind all training dummies (call after spawn / respawn). + * @param {import('three').Object3D[]} dummies + */ + bindDummies(dummies = []) { + // Drop stale enemy plates not in list + for (const [id, e] of [...this._byId]) { + if (e.kind !== 'enemy') continue; + if (!dummies.includes(e.object)) this.detach(id); + } + for (const d of dummies) { + if (!d) continue; + if (d.userData?.overheadBarId && this._byId.has(d.userData.overheadBarId)) continue; + this.attach(d, { + kind: 'enemy', + name: d.userData?.displayName || d.name || 'Enemy', + offsetY: 2.2 + }); + } + } + + /** + * @param {import('three').Camera} camera + * @param {number} viewW + * @param {number} viewH + * @param {import('three').Vector3} [playerPos] for distance fade + */ + update(camera, viewW, viewH, playerPos) { + if (!camera || !viewW || !viewH) return; + const maxD2 = this.maxDist * this.maxDist; + + for (const e of this._byId.values()) { + const obj = e.object; + if (!obj || !obj.visible || !obj.parent) { + e.el.classList.add('is-hidden'); + continue; + } + + // World head position + obj.getWorldPosition(_v); + _v.y += e.offsetY; + + if (playerPos) { + const dx = _v.x - playerPos.x; + const dy = _v.y - playerPos.y; + const dz = _v.z - playerPos.z; + if (dx * dx + dy * dy + dz * dz > maxD2) { + e.el.classList.add('is-hidden'); + continue; + } + } + + _v.project(camera); + if (_v.z > 1 || _v.z < -1 || _v.x < -1.15 || _v.x > 1.15 || _v.y < -1.2 || _v.y > 1.2) { + e.el.classList.add('is-hidden'); + continue; + } + + const x = (_v.x * 0.5 + 0.5) * viewW; + const y = (-_v.y * 0.5 + 0.5) * viewH; + e.el.style.transform = `translate(${x}px, ${y}px) translate(-50%, -100%)`; + e.el.classList.remove('is-hidden'); + + const hp = e.getHp01?.() ?? 1; + setOverheadBarHp(e.el, hp); + } + } + + dispose() { + this.clear(); + this.root.remove(); + } +} diff --git a/src/ui/ShowcasePanel.js b/src/ui/ShowcasePanel.js new file mode 100644 index 0000000..eb8fa85 --- /dev/null +++ b/src/ui/ShowcasePanel.js @@ -0,0 +1,574 @@ +/** + * Showcase — review races, weapons, meshes, anims in the live environment + * and bind master weapon skills (ObjectStore) to hotkeys 1–4 / F. + * + * Catalog SSOT: info.grudge-studio.com/api/v1/master-weaponSkills.json + * UI ref: info.grudge-studio.com/WEAPON_SKILLS.html + */ + +import { Vector3 } from 'three'; +import { RACES, ANIM_PACKS, ANIM_PACK_META, WEAPON_SLOTS } from '../config/assets.js'; +import { + loadWeaponSkillsCatalog, + skillsForWeaponType, + defaultHotbarForWeaponType, + animRoleForSkill, + vfxIdForSkill, + WEAPON_SKILLS_HTML, + INFO_DOCS_URL, + iconCdnUrl, + normalizeWeaponTypeId +} from '../api/weaponSkillsCatalog.js'; +import { + loadSkillBindings, + setSkillBinding, + bindHotbarFromSkills, + getSkillBinding +} from '../combat/skillBindings.js'; +import { packCombatBlurb } from '../config/weaponAnimPack.js'; +import { + groupRolesByFamily, + classifyRole, + MOBILITY_BINDINGS, + roleBlurb +} from '../config/animLibrary.js'; + +export class ShowcasePanel { + /** + * @param {{ + * character: import('../animation/CharacterController.js').CharacterController, + * getDrc?: () => import('../combat/DrcCombatController.js').DrcCombatController|null, + * onToast?: (msg: string) => void, + * onRace?: (id: string) => void|Promise, + * onShowcaseMode?: (on: boolean) => void, + * onBindingsChanged?: () => void, + * playSkillPreview?: (skill: object) => void + * }} opts + */ + constructor(opts) { + this.character = opts.character; + this.getDrc = opts.getDrc || (() => null); + this.onToast = opts.onToast || (() => {}); + this.onRace = opts.onRace || null; + this.onShowcaseMode = opts.onShowcaseMode || null; + this.onBindingsChanged = opts.onBindingsChanged || null; + this.playSkillPreview = opts.playSkillPreview || null; + + this.open = false; + this._busy = false; + this._cat = null; + this._weaponTypeId = 'SWORD'; + this._filter = ''; + this._selectedSkillId = null; + this._bindSlot = '0'; + + this.el = document.createElement('div'); + this.el.id = 'showcase-panel'; + this.el.className = 'showcase-panel'; + this.el.hidden = true; + document.body.appendChild(this.el); + this._renderShell(); + } + + _renderShell() { + this.el.innerHTML = ` +
+
+

Showcase

+

Race · mesh · weapon · anim · bind real skills (master catalog)

+
+ +
+
+
+
+
+
+
+
+ + `; + this.el.querySelector('[data-close]').addEventListener('click', () => this.setOpen(false)); + this.el.addEventListener('pointerdown', (e) => e.stopPropagation()); + this.el.addEventListener('wheel', (e) => e.stopPropagation()); + } + + toggle() { + this.setOpen(!this.open); + } + + setOpen(open) { + this.open = !!open; + this.el.hidden = !this.open; + this.onShowcaseMode?.(this.open); + if (this.open) { + this.refresh(); + this._ensureCatalog(); + } + } + + async _ensureCatalog() { + if (this._cat) return this._cat; + try { + this.onToast('Loading master-weaponSkills…'); + this._cat = await loadWeaponSkillsCatalog(); + this.onToast(`Catalog v${this._cat.version} · ${this._cat.totalSkills} skills`); + this.refresh(); + return this._cat; + } catch (err) { + this.onToast(err?.message || 'Catalog load failed'); + return null; + } + } + + refresh() { + if (!this.open) return; + this._fillEnv(); + this._fillWeapon(); + this._fillAnims(); + this._fillSkills(); + this._fillBind(); + } + + _busyGuard(fn) { + return async (...args) => { + if (this._busy) return; + this._busy = true; + try { + await fn(...args); + } catch (e) { + console.error(e); + this.onToast(e?.message || String(e)); + } finally { + this._busy = false; + this.refresh(); + } + }; + } + + /* ── Environment: race + mesh ─────────────────────────────── */ + + _fillEnv() { + const host = this.el.querySelector('[data-sec="env"]'); + if (!host) return; + const c = this.character; + const raceOpts = Object.values(RACES) + .map( + (r) => + `` + ) + .join(''); + + host.innerHTML = ` +

Environment

+ + +

Pack: ${c.animPackId} · ${packCombatBlurb(c.animPackId)}

+
+ + + + +
+ `; + + host.querySelector('[data-race]')?.addEventListener( + 'change', + this._busyGuard(async (e) => { + if (this.onRace) await this.onRace(e.target.value); + else await c.setRace?.(e.target.value); + this.onToast(`Race · ${e.target.value}`); + }) + ); + host.querySelector('[data-preset]')?.addEventListener( + 'change', + this._busyGuard(async (e) => { + c.applyPreset(e.target.value); + await c.syncAnimPackFromLoadout?.({ packHint: c.animPackId }); + this.onToast(`Preset · ${e.target.value}`); + }) + ); + host.querySelector('[data-idle]')?.addEventListener('click', () => c.play?.('idle', 0.15)); + host.querySelector('[data-walk]')?.addEventListener('click', () => c.play?.('walk', 0.15) || c.play?.('run', 0.15)); + host.querySelector('[data-run]')?.addEventListener('click', () => c.play?.('run', 0.15)); + host.querySelector('[data-attack]')?.addEventListener('click', () => { + c.playWeaponCombat?.('attack') || c.playWeaponCombat?.('cast'); + }); + } + + /* ── Weapon type (catalog) ────────────────────────────────── */ + + _fillWeapon() { + const host = this.el.querySelector('[data-sec="weapon"]'); + if (!host) return; + const types = this._cat?.weaponTypes || []; + const opts = types.length + ? types + .map((wt) => { + const id = normalizeWeaponTypeId(wt.id || wt.name); + return ``; + }) + .join('') + : ``; + + host.innerHTML = ` +

Weapon type (master catalog)

+ +
+ + +
+

Maps to lab mesh_ids + anim pack (staff→magic, bow→longbow, sword→sword_shield)

+ `; + + host.querySelector('[data-wtype]')?.addEventListener('change', (e) => { + this._weaponTypeId = e.target.value; + this.refresh(); + }); + + host.querySelector('[data-apply-weapon]')?.addEventListener( + 'click', + this._busyGuard(async () => { + await this._applyWeaponType(this._weaponTypeId); + }) + ); + + host.querySelector('[data-t0-bar]')?.addEventListener( + 'click', + this._busyGuard(async () => { + const cat = await this._ensureCatalog(); + if (!cat) return; + const bar = defaultHotbarForWeaponType(cat, this._weaponTypeId); + bindHotbarFromSkills(bar); + this.onBindingsChanged?.(); + this.onToast(`Bound ${bar.length} skills → 1–4`); + }) + ); + } + + async _applyWeaponType(typeId) { + const cat = await this._ensureCatalog(); + const wt = cat?.weaponTypes?.find( + (w) => normalizeWeaponTypeId(w.id || w.name) === normalizeWeaponTypeId(typeId) + ); + const skills = cat ? skillsForWeaponType(cat, typeId) : []; + const lab = skills[0] + ? { pack: skills[0].labPack, slot: skills[0].labSlot } + : { pack: 'sword_shield', slot: 'sword' }; + + const c = this.character; + // Clear weapons then equip A variant of lab slot + for (const w of WEAPON_SLOTS) c.equipment?.setSlot?.(w, null); + if (lab.slot && lab.slot !== 'shield') { + c.equipment?.setSlot?.(lab.slot, 'A'); + } + await c.setAnimPack?.(lab.pack); + await c._bindPack?.('combat_mobility'); + this.onToast(`Equipped ${typeId} · pack ${lab.pack} · ${skills.length} skills in catalog`); + } + + /* ── Anim library ─────────────────────────────────────────── */ + + _fillAnims() { + const host = this.el.querySelector('[data-sec="anims"]'); + if (!host) return; + const c = this.character; + const lib = c.getAnimLibrary?.() || null; + const roles = lib?.roles || c.listAnimRoles?.() || []; + const byFamily = lib?.byFamily || groupRolesByFamily(roles); + const packOpts = Object.keys(ANIM_PACKS) + .map( + (id) => + `` + ) + .join(''); + + const familyOrder = ['gait', 'combat', 'mobility', 'utility']; + const familyLabels = { + gait: 'Gait (setGait)', + combat: 'Combat (one-shot)', + mobility: 'Mobility (dodge · roll · slide)', + utility: 'Other' + }; + const sections = familyOrder + .map((fam) => { + const list = byFamily[fam] || []; + if (!list.length) return ''; + const buttons = list + .map((r) => { + const meta = classifyRole(r); + const title = roleBlurb(r).replace(/"/g, '"'); + return ``; + }) + .join(''); + return `
+

${familyLabels[fam] || fam}

+
${buttons}
+
`; + }) + .join(''); + + const mobilityHint = Object.values(MOBILITY_BINDINGS) + .map((m) => `
  • ${m.label} — ${m.input}
  • `) + .join(''); + + const mm = lib?.dodgeMm; + const mmLine = mm + ? `MM dodge: L/R ${mm.lateralM} m · F/B ${mm.forwardM}/${mm.backM} m (${mm.units})` + : ''; + + host.innerHTML = ` +

    Animation library

    +

    Packs + roles SSOT · animLibrary.js · watch in scene

    + +

    ${packCombatBlurb(c.animPackId || 'magic')}

    +

    ${mmLine}

    +
      ${mobilityHint}
    + ${sections || '

    No clips — load character first

    '} + `; + + host.querySelector('[data-pack]')?.addEventListener( + 'change', + this._busyGuard(async (e) => { + await c.setAnimPack?.(e.target.value); + await c._bindPack?.('combat_mobility'); + this.onToast(`Pack · ${e.target.value} · mobility rebound`); + this._fillAnims(); + }) + ); + host.querySelectorAll('[data-clip]').forEach((btn) => { + btn.addEventListener('click', () => { + const role = btn.dataset.clip; + const ok = + c.playLibraryClip?.(role) || c.play?.(role, 0.12); + this.onToast(ok ? roleBlurb(role) : `Missing · ${role}`); + }); + }); + } + + /* ── Master skills list ───────────────────────────────────── */ + + _fillSkills() { + const host = this.el.querySelector('[data-sec="skills"]'); + if (!host) return; + + if (!this._cat) { + host.innerHTML = ` +

    Weapon skills (catalog)

    +

    Loading master-weaponSkills.json…

    + + `; + host.querySelector('[data-reload]')?.addEventListener('click', () => this._ensureCatalog()); + return; + } + + let list = skillsForWeaponType(this._cat, this._weaponTypeId); + const q = this._filter.trim().toLowerCase(); + if (q) { + list = list.filter( + (s) => + s.name.toLowerCase().includes(q) || + s.id.toLowerCase().includes(q) || + s.slotType.toLowerCase().includes(q) + ); + } + list = list.slice(0, 80); + + host.innerHTML = ` +

    Skills · ${this._weaponTypeId} (${list.length} shown)

    + +
    + ${list + .map((s) => { + const sel = s.id === this._selectedSkillId ? 'is-selected' : ''; + const ic = s.iconUrl + ? `` + : ``; + return ` + `; + }) + .join('') || '

    No skills for this type

    '} +
    +
    + +
    + `; + + host.querySelector('[data-filter]')?.addEventListener('input', (e) => { + this._filter = e.target.value; + this._fillSkills(); + }); + + host.querySelectorAll('[data-sid]').forEach((btn) => { + btn.addEventListener('click', () => { + this._selectedSkillId = btn.dataset.sid; + this._fillSkills(); + this._fillBind(); + }); + }); + + host.querySelector('[data-preview]')?.addEventListener('click', () => { + const sk = this._cat.byId.get(this._selectedSkillId); + if (!sk) { + this.onToast('Select a skill'); + return; + } + this._previewSkill(sk); + }); + } + + _previewSkill(sk) { + const c = this.character; + // Ensure pack matches skill weapon + if (sk.labPack && sk.labPack !== c.animPackId) { + c.setAnimPack?.(sk.labPack).then(() => { + c._bindPack?.('combat_mobility'); + this._playSkillAnim(sk); + }); + } else { + this._playSkillAnim(sk); + } + this.playSkillPreview?.(sk); + this.onToast(`Preview · ${sk.name}`); + } + + _playSkillAnim(sk) { + const role = animRoleForSkill(sk); + const c = this.character; + if (role === 'cast') c.playWeaponCombat?.('cast') || c.requestOneShot?.('cast'); + else if (role === 'block') c.playParry?.() || c.requestOneShot?.('block'); + else c.playWeaponCombat?.('attack') || c.requestOneShot?.('attack'); + + const drc = this.getDrc?.(); + const vfx = vfxIdForSkill(sk); + if (drc?.vfx?.deploy) { + const yaw = c.facing; + drc.vfx.deploy(vfx, { + origin: c.position.clone(), + forward: new Vector3(Math.sin(yaw), 0, Math.cos(yaw)) + }); + } + } + + /* ── Bind to hotkey ───────────────────────────────────────── */ + + _fillBind() { + const host = this.el.querySelector('[data-sec="bind"]'); + if (!host) return; + const binds = loadSkillBindings(); + const sk = this._cat?.byId?.get(this._selectedSkillId); + + const row = (slot, label) => { + const b = binds[String(slot)]; + return `
    + ${label} + ${b?.name || '—'} + +
    `; + }; + + host.innerHTML = ` +

    Save to hotkey / weapon skill

    +

    Selected: ${sk?.name || 'none'} · true catalog id for combat bar

    + +
    + + +
    +
    + ${row(0, '1')}${row(1, '2')}${row(2, '3')}${row(3, '4')}${row('f', 'F')} +
    + `; + + host.querySelector('[data-bind-slot]')?.addEventListener('change', (e) => { + this._bindSlot = e.target.value; + }); + + host.querySelector('[data-bind]')?.addEventListener('click', () => { + if (!sk) { + this.onToast('Select a skill first'); + return; + } + setSkillBinding(this._bindSlot, { + skillId: sk.id, + name: sk.name, + weaponTypeId: sk.weaponTypeId, + labPack: sk.labPack, + labSlot: sk.labSlot, + iconUrl: sk.iconUrl, + cooldown: sk.cooldown, + damageType: sk.damageType + }); + this.onBindingsChanged?.(); + this.onToast(`Bound ${sk.name} → ${this._bindSlot === 'f' ? 'F' : Number(this._bindSlot) + 1}`); + this._fillBind(); + }); + + host.querySelector('[data-fire-bind]')?.addEventListener('click', () => { + const b = getSkillBinding(this._bindSlot); + if (!b) { + this.onToast('Empty slot'); + return; + } + const full = this._cat?.byId?.get(b.skillId) || b; + this._previewSkill(full); + const drc = this.getDrc?.(); + if (this._bindSlot === 'f') drc?.useMeleeStrike?.(); + else drc?.useSkill?.(Number(this._bindSlot)); + }); + + host.querySelectorAll('[data-clear-slot]').forEach((btn) => { + btn.addEventListener('click', () => { + setSkillBinding(btn.dataset.clearSlot, null); + this.onBindingsChanged?.(); + this._fillBind(); + }); + }); + } + + dispose() { + this.el.remove(); + } +} diff --git a/src/ui/TightBar.js b/src/ui/TightBar.js new file mode 100644 index 0000000..c9cb956 --- /dev/null +++ b/src/ui/TightBar.js @@ -0,0 +1,347 @@ +/** + * HUD Tight bottom bar — threejs-rapier / Danger Room production chrome. + * Port of gameopen TightBar.tsx + tightBar.css geometry (HUD.psd 3800×726). + * + * Layout: HP orb | 6 left slots | avatar arch | 6 right slots | STA orb + */ + +import { + QUICK_ACTIONS, + QUICK_SLOTS_PER_SIDE, + defaultQuickSlots +} from './quickActions.js'; +import { getActiveSkills, DRC_MELEE_STRIKE } from '../combat/drcSkills.js'; +import { getSkillBinding } from '../combat/skillBindings.js'; +import { settings } from '../config/settings.js'; + +const TB_W = 3800; +const TB_H = 726; +const tbX = (px) => `${((px / TB_W) * 100).toFixed(3)}%`; +const tbY = (px) => `${((px / TB_H) * 100).toFixed(3)}%`; + +const TB_CELL_W = 230; +const TB_CELL_H = 132; +const TB_COLS = [776, 1028, 1274, 2276, 2526, 2772]; +const TB_ROWS = [378, 548]; +/** + * Orb geometry measured off hud-tight-bar.png (1900×363 → design 3800×726). + * Centers = glass centroid; R covers full chrome sphere (incl. tan lower + * reflection) so Island-Crusade globe discs replace baked placeholders. + */ +const TB_ORB_R = 176; +const TB_ORB_HP = { cx: 351, cy: 400 }; +const TB_ORB_MP = { cx: 3444, cy: 392 }; + +/** Prefer same-origin public art; fallback to Open CDN. */ +export const TIGHT_BAR_ART = './hud-tight-bar.png'; +export const TIGHT_BAR_ART_FALLBACK = 'https://open.grudge-studio.com/hud-tight-bar.png'; +/** Island-Crusade Danger Room globe fills (~164² RGBA, ~14px transparent pad). */ +export const ORB_HEALTH_GLOBE = './hud/health_globe.png'; +export const ORB_MANA_GLOBE = './hud/mana_globe.png'; + +function tbSlotStyle(i) { + const grid = i < QUICK_SLOTS_PER_SIDE ? 0 : 1; + const j = i % QUICK_SLOTS_PER_SIDE; + const col = grid * 3 + (j % 3); + const row = Math.floor(j / 3); + return { + left: tbX(TB_COLS[col]), + top: tbY(TB_ROWS[row]), + width: tbX(TB_CELL_W), + height: tbY(TB_CELL_H) + }; +} + +function tbOrbStyle(orb) { + return { + left: tbX(orb.cx - TB_ORB_R), + top: tbY(orb.cy - TB_ORB_R), + width: tbX(TB_ORB_R * 2), + height: tbY(TB_ORB_R * 2) + }; +} + +/** + * @typedef {object} TightBarState + * @property {number} health + * @property {number} maxHealth + * @property {number} stamina + * @property {number} maxStamina + * @property {number} [mana] + * @property {number} [maxMana] + * @property {number} poise + * @property {number} maxPoise + * @property {string} character + * @property {string} [raceId] + * @property {string} [portraitUrl] + * @property {(actionId: string) => number} [cd01] 0..1 remaining + */ + +export class TightBar { + /** + * @param {{ + * host: HTMLElement, + * onAction?: (actionId: string) => void, + * onMenu?: (menuId: string) => void + * }} opts + */ + constructor(opts) { + this.host = opts.host; + this.onAction = opts.onAction || (() => {}); + this.onMenu = opts.onMenu || (() => {}); + this.slots = defaultQuickSlots(); + /** @type {TightBarState} */ + this.state = { + health: 100, + maxHealth: 100, + stamina: 100, + maxStamina: 100, + mana: 100, + maxMana: 100, + poise: 100, + maxPoise: 100, + character: 'Warlord', + raceId: 'WK' + }; + this.el = document.createElement('div'); + this.el.className = 'tightbar-root'; + this.host.appendChild(this.el); + this._render(); + } + + _skillLabels() { + const bar = getActiveSkills(); + const b0 = getSkillBinding(0); + const b1 = getSkillBinding(1); + const b2 = getSkillBinding(2); + const b3 = getSkillBinding(3); + const bf = getSkillBinding('f'); + // F = weapon skill (primary / bind), not residual + let fName = bf?.name; + try { + // dynamic import avoided — labels from bar primary + fName = fName || bar.find((s) => s.slot === 0)?.label || bar[0]?.label || 'Weapon'; + } catch { + fName = fName || 'Weapon'; + } + return { + fskill: fName, + interact: fName, + sig1: b0?.name || bar.find((s) => s.slot === 0)?.label || 'Skill 1', + sig2: b1?.name || bar.find((s) => s.slot === 1)?.label || 'Skill 2', + sig3: b2?.name || bar.find((s) => s.slot === 2)?.label || 'Skill 3', + sig4: b3?.name || bar.find((s) => s.slot === 3)?.label || 'Wind Tempest' + }; + } + + _resolve(id) { + const def = QUICK_ACTIONS[id]; + if (!def) return null; + const labels = this._skillLabels(); + const name = + id === 'fskill' || id === 'interact' + ? labels.interact || labels.fskill + : id.startsWith('sig') + ? labels[id] || def.label + : def.label; + const cd01 = this.state.cd01?.(id) ?? 0; + return { + id, + keyLabel: def.key, + name, + glyph: def.glyph, + accent: id === 'heavy' || id === 'parry', + cd01, + kind: def.kind + }; + } + + _render() { + const s = this.state; + const hpPct = s.maxHealth > 0 ? Math.max(0, Math.min(100, (s.health / s.maxHealth) * 100)) : 0; + // Casting lab: right orb = mana; stamina falls back if mana unset + const manaVal = s.mana != null ? s.mana : s.stamina; + const manaMax = s.maxMana != null ? s.maxMana : s.maxStamina; + const mpPct = manaMax > 0 ? Math.max(0, Math.min(100, (manaVal / manaMax) * 100)) : 0; + const poisePct = s.maxPoise > 0 ? Math.max(0, Math.min(100, (s.poise / s.maxPoise) * 100)) : 0; + + const slotHtml = this.slots + .map((id, i) => { + const style = tbSlotStyle(i); + const styleAttr = `left:${style.left};top:${style.top};width:${style.width};height:${style.height}`; + if (!id) { + return `
    ·
    `; + } + const r = this._resolve(id); + const onCd = r.cd01 > 0.02; + const frac = onCd ? r.cd01 : 0; + const sweep = onCd + ? `
    ` + : ''; + const cdTxt = onCd ? `${(frac * 10).toFixed(1)}` : ''; + return ` + `; + }) + .join(''); + + const hpOrb = tbOrbStyle(TB_ORB_HP); + const mpOrb = tbOrbStyle(TB_ORB_MP); + + this.el.innerHTML = ` +
    +
    +
    + +
    +
    +
    + ${Math.round(s.health)} +
    +
    +
    + +
    +
    +
    + ${Math.round(manaVal)} +
    + ${slotHtml} +
    + ${(s.raceId || s.character || '?').slice(0, 2)} + ${s.character} +
    +
    +
    +
    +
    + +
    + `; + + // Art fallback + const bar = this.el.querySelector('[data-tightbar]'); + const img = new Image(); + img.onerror = () => { + if (bar) bar.style.backgroundImage = `url('${TIGHT_BAR_ART_FALLBACK}')`; + }; + img.src = TIGHT_BAR_ART; + + this.el.querySelectorAll('[data-action]').forEach((btn) => { + btn.addEventListener('pointerdown', (e) => { + e.stopPropagation(); + e.preventDefault(); + this.onAction(btn.dataset.action); + }); + }); + this.el.querySelectorAll('[data-menu]').forEach((btn) => { + btn.addEventListener('pointerdown', (e) => { + e.stopPropagation(); + e.preventDefault(); + this.onMenu(btn.dataset.menu); + }); + }); + + // Chips + const chips = this.el.querySelector('[data-chips]'); + if (chips) { + chips.innerHTML = ['1–4', 'F', 'X', 'C', 'E', 'R', 'Q', 'Space'] + .map((k) => `${k}`) + .join(''); + } + + this._nodes = { + hpDrain: this.el.querySelector('[data-hp-drain]'), + mpDrain: this.el.querySelector('[data-mp-drain]'), + hpFill: this.el.querySelector('[data-hp-fill]'), + mpFill: this.el.querySelector('[data-mp-fill]'), + hpVal: this.el.querySelector('[data-hp-val]'), + mpVal: this.el.querySelector('[data-mp-val]'), + poise: this.el.querySelector('[data-poise]'), + avatarName: this.el.querySelector('[data-avatar-name]'), + avatarLetter: this.el.querySelector('[data-avatar-letter]') + }; + } + + /** + * @param {Partial} patch + */ + setState(patch) { + Object.assign(this.state, patch); + const s = this.state; + const hpPct = s.maxHealth > 0 ? Math.max(0, Math.min(100, (s.health / s.maxHealth) * 100)) : 0; + const manaVal = s.mana != null ? s.mana : s.stamina; + const manaMax = s.maxMana != null ? s.maxMana : s.maxStamina; + const mpPct = manaMax > 0 ? Math.max(0, Math.min(100, (manaVal / manaMax) * 100)) : 0; + const poisePct = s.maxPoise > 0 ? Math.max(0, Math.min(100, (s.poise / s.maxPoise) * 100)) : 0; + if (this._nodes?.hpDrain) this._nodes.hpDrain.style.height = `${100 - hpPct}%`; + if (this._nodes?.mpDrain) this._nodes.mpDrain.style.height = `${100 - mpPct}%`; + if (this._nodes?.hpFill) this._nodes.hpFill.style.setProperty('--orb-pct', `${hpPct}%`); + if (this._nodes?.mpFill) this._nodes.mpFill.style.setProperty('--orb-pct', `${mpPct}%`); + if (this._nodes?.hpVal) this._nodes.hpVal.textContent = String(Math.round(s.health)); + if (this._nodes?.mpVal) this._nodes.mpVal.textContent = String(Math.round(manaVal)); + if (this._nodes?.poise) this._nodes.poise.style.width = `${poisePct}%`; + if (this._nodes?.avatarName) this._nodes.avatarName.textContent = s.character; + if (this._nodes?.avatarLetter) { + this._nodes.avatarLetter.textContent = (s.raceId || s.character || '?').slice(0, 2); + } + // Controls: show/hide hotkey chips + const chips = this.el.querySelector('[data-chips]'); + chips?.classList.toggle('is-hidden', settings.controls?.showHotkeyChips === false); + // Refresh CD overlays on slots without full rebuild when possible + this.el.querySelectorAll('[data-action]').forEach((btn) => { + const id = btn.dataset.action; + const cd01 = s.cd01?.(id) ?? 0; + const onCd = cd01 > 0.02; + btn.classList.toggle('on-cd', onCd); + btn.classList.toggle('ready', !onCd); + let sweep = btn.querySelector('.tb-sweep'); + let cdEl = btn.querySelector('.tb-cd'); + if (onCd) { + if (!sweep) { + sweep = document.createElement('div'); + sweep.className = 'tb-sweep'; + btn.appendChild(sweep); + } + sweep.style.background = `conic-gradient(rgba(4,10,20,0.78) ${cd01 * 360}deg, transparent 0deg)`; + if (!cdEl) { + cdEl = document.createElement('span'); + cdEl.className = 'tb-cd'; + btn.appendChild(cdEl); + } + cdEl.textContent = (cd01 * 10).toFixed(1); + } else { + sweep?.remove(); + cdEl?.remove(); + } + }); + } + + /** Rebuild labels when skill tree changes. */ + refreshLabels() { + this._render(); + this.setState(this.state); + } + + setVisible(visible) { + this.el.classList.toggle('is-hidden', !visible); + } + + dispose() { + this.el.remove(); + } +} diff --git a/src/ui/adminHub.css b/src/ui/adminHub.css new file mode 100644 index 0000000..413d386 --- /dev/null +++ b/src/ui/adminHub.css @@ -0,0 +1,351 @@ +/* ------------------------------------------------------------------ * + * Admin Hub F1–F5 — deployable tools / editors + * ------------------------------------------------------------------ */ + +.admin-hub { + position: fixed; + top: 56px; + right: 16px; + width: min(460px, calc(100vw - 24px)); + max-height: calc(100vh - 88px); + z-index: 45; + display: flex; + flex-direction: column; + pointer-events: auto; + background: var(--ui-bg-solid); + border: 1px solid var(--ui-border); + border-radius: var(--ui-radius); + box-shadow: var(--ui-shadow); + backdrop-filter: blur(12px); + color: var(--ui-text); + overflow: hidden; +} + +.admin-hub[hidden] { + display: none !important; +} + +.admin-hub__head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 10px; + padding: 12px 14px 8px; + border-bottom: 1px solid var(--ui-border); +} + +.admin-hub__head h2 { + margin: 0; + font-size: 14px; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.admin-hub__sub { + margin: 3px 0 0; + font-size: 11px; + color: var(--ui-text-dim); + line-height: 1.35; +} + +.admin-hub__close { + border: 0; + background: transparent; + color: var(--ui-text-dim); + font-size: 22px; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.admin-hub__tabs { + display: flex; + flex-wrap: wrap; + gap: 4px; + padding: 8px 10px; + border-bottom: 1px solid var(--ui-border); + background: rgba(0, 0, 0, 0.2); +} + +.admin-hub__tab { + border: 1px solid var(--ui-border); + background: rgba(255, 255, 255, 0.04); + color: var(--ui-text-dim); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.03em; + padding: 5px 8px; + border-radius: 8px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 5px; +} + +.admin-hub__tab kbd { + font-size: 9px; + opacity: 0.75; + font-family: inherit; + background: rgba(0, 0, 0, 0.35); + padding: 1px 4px; + border-radius: 4px; +} + +.admin-hub__tab.is-active { + color: var(--ui-text); + border-color: var(--ui-accent); + background: rgba(127, 214, 255, 0.12); + box-shadow: 0 0 0 1px rgba(127, 214, 255, 0.2); +} + +.admin-hub__body { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 10px 12px 14px; +} + +.admin-hub__foot { + padding: 8px 12px; + border-top: 1px solid var(--ui-border); + font-size: 10px; + color: var(--ui-text-dim); + display: flex; + flex-wrap: wrap; + gap: 8px; + align-items: center; +} + +.admin-hub__foot kbd { + font-size: 10px; + background: rgba(0, 0, 0, 0.35); + padding: 1px 5px; + border-radius: 4px; +} + +.admin-card { + background: rgba(255, 255, 255, 0.03); + border: 1px solid var(--ui-border); + border-radius: 10px; + padding: 10px 12px; + margin-bottom: 10px; +} + +.admin-card h3 { + margin: 0 0 6px; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.04em; + color: var(--ui-accent); +} + +.admin-card p, +.admin-hint { + margin: 0 0 6px; + font-size: 11px; + color: var(--ui-text-dim); + line-height: 1.4; +} + +.admin-row { + display: flex; + justify-content: space-between; + gap: 8px; + font-size: 11px; + padding: 3px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.04); +} + +.admin-row span { + color: var(--ui-text-dim); +} + +.admin-row b { + font-weight: 600; + text-align: right; + word-break: break-word; +} + +.admin-label { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 8px; + font-size: 11px; + color: var(--ui-text-dim); +} + +.admin-label input, +.admin-label select, +.admin-label textarea { + width: 100%; + background: rgba(0, 0, 0, 0.35); + border: 1px solid var(--ui-border); + border-radius: 8px; + color: var(--ui-text); + font-size: 12px; + padding: 6px 8px; + font-family: inherit; +} + +.admin-label textarea { + min-height: 56px; + resize: vertical; +} + +.admin-btn-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 8px 0; +} + +.admin-btn { + border: 1px solid var(--ui-border); + background: rgba(255, 255, 255, 0.06); + color: var(--ui-text); + font-size: 11px; + font-weight: 600; + padding: 6px 10px; + border-radius: 8px; + cursor: pointer; +} + +.admin-btn:hover { + border-color: var(--ui-accent); + color: var(--ui-accent); +} + +.admin-btn--primary { + background: rgba(127, 214, 255, 0.15); + border-color: rgba(127, 214, 255, 0.45); +} + +.admin-btn--danger { + border-color: rgba(255, 100, 100, 0.4); + color: #ffb0b0; +} + +.admin-btn--ghost { + background: transparent; +} + +.admin-list { + display: flex; + flex-direction: column; + gap: 4px; + max-height: 220px; + overflow-y: auto; +} + +.admin-list__item { + display: grid; + grid-template-columns: 1fr auto; + gap: 6px; + align-items: center; + padding: 7px 8px; + border: 1px solid var(--ui-border); + border-radius: 8px; + background: rgba(0, 0, 0, 0.2); + cursor: pointer; + text-align: left; + color: inherit; + font: inherit; +} + +.admin-list__item.is-selected { + border-color: var(--ui-accent); + background: rgba(127, 214, 255, 0.1); +} + +.admin-list__item strong { + display: block; + font-size: 12px; +} + +.admin-list__item small { + display: block; + font-size: 10px; + color: var(--ui-text-dim); +} + +.admin-score { + font-size: 11px; + font-weight: 700; + color: var(--ui-accent); +} + +.admin-warn { + color: #f0c674; + font-size: 10px; + margin: 2px 0; +} + +.admin-chips { + display: flex; + flex-wrap: wrap; + gap: 4px; + margin: 6px 0; +} + +.admin-chip { + font-size: 10px; + padding: 2px 7px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.06); + border: 1px solid var(--ui-border); + color: var(--ui-text-dim); +} + +.admin-code { + font-family: ui-monospace, Consolas, monospace; + font-size: 10px; + word-break: break-all; + background: rgba(0, 0, 0, 0.3); + padding: 6px 8px; + border-radius: 6px; + max-height: 120px; + overflow: auto; + white-space: pre-wrap; +} + +.admin-hub-chips { + position: fixed; + top: 12px; + left: 50%; + transform: translateX(-50%); + z-index: 44; + display: flex; + gap: 4px; + pointer-events: auto; +} + +.admin-hub-chips[hidden] { + display: none !important; +} + +.admin-hub-chip { + border: 1px solid var(--ui-border); + background: rgba(12, 16, 22, 0.85); + color: var(--ui-text-dim); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.04em; + padding: 4px 8px; + border-radius: 6px; + cursor: pointer; + backdrop-filter: blur(8px); +} + +.admin-hub-chip.is-active { + color: var(--ui-text); + border-color: var(--ui-accent); + background: rgba(127, 214, 255, 0.18); +} + +.admin-hub-chip kbd { + opacity: 0.7; + font-weight: 600; + margin-right: 3px; +} diff --git a/src/ui/bars-hud.css b/src/ui/bars-hud.css new file mode 100644 index 0000000..8accc24 --- /dev/null +++ b/src/ui/bars-hud.css @@ -0,0 +1,260 @@ +/** + * Bars HUD pack chrome — player unit_frame_009 + overhead 001/003 + fillers. + * Vars from barsHudUi.js (url("./hud/bars/...")). + */ + +/* ── Player unit frame (unit_frame_009) ─────────────────────── */ + +.hud-frame.bars-frame--player { + min-width: 272px; + max-width: 320px; + padding: 0; + gap: 0; + background: transparent; + border: none; + border-radius: 0; + box-shadow: none; + backdrop-filter: none; + overflow: visible; +} + +.bars-frame__plate { + position: relative; + display: flex; + align-items: stretch; + gap: 8px; + min-height: 120px; + padding: 10px 14px 12px 10px; + /* Scale product 136×60 chrome as 9-stretch plate */ + background-image: var(--bars-player-frame); + background-size: 100% 100%; + background-repeat: no-repeat; + image-rendering: auto; + filter: drop-shadow(0 8px 16px rgba(0, 0, 0, 0.55)); +} + +.bars-frame--player .cp-frame__avatar { + width: 52px; + height: 52px; + margin-top: 4px; + flex-shrink: 0; +} + +.bars-frame--player .cp-frame__body { + gap: 4px; + padding-top: 2px; + justify-content: flex-start; +} + +.bars-frame--player .hud-frame__name { + font-size: 11px; + font-weight: 800; + letter-spacing: 0.05em; + text-shadow: 0 1px 3px #000, 0 0 6px rgba(0, 0, 0, 0.8); + margin-bottom: 1px; +} + +/* Fillers replace CraftPix grayscale bars */ +.bars-frame--player .cp-bar { + height: 12px; +} + +.bars-frame--player .cp-bar__track { + background-image: none; + background: rgba(0, 0, 0, 0.55); + border-radius: 2px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.65); +} + +.bars-frame--player .cp-bar__fill-wrap { + left: 2px; + right: 2px; + top: 2px; + bottom: 2px; +} + +.bars-frame--player .cp-bar__fill { + background-image: var(--bars-health-fill); + background-size: 100% 100%; + background-repeat: no-repeat; + filter: none; + border-radius: 1px; +} + +.bars-frame--player .cp-bar--mp .cp-bar__fill, +.bars-frame--player .cp-bar__fill--mp { + background-image: var(--bars-mana-fill); + filter: none; +} + +.bars-frame--player .cp-bar--sta .cp-bar__fill, +.bars-frame--player .cp-bar__fill--sta { + background-image: var(--bars-stamina-fill); + filter: none; +} + +/* Target frame keeps CraftPix hostile shell; use bars health fill */ +.hud-frame--target .cp-bar--hostile .cp-bar__fill, +.hud-frame--target .cp-bar__fill--hostile { + background-image: var(--bars-health-fill); + filter: hue-rotate(-8deg) saturate(1.15) brightness(1.05); +} + +/* ── Ally strip (overhead_health_001 chrome) ────────────────── */ + +.hud-allies:not(.is-empty) { + display: flex; + flex-direction: column; + gap: 4px; + position: fixed; + top: 190px; + left: 16px; + z-index: 22; + pointer-events: none; +} + +.hud-ally.bars-ally { + position: relative; + width: 140px; + height: 28px; + padding: 0 6px 0 8px; + display: flex; + flex-direction: column; + justify-content: center; + background-image: var(--bars-overhead-ally); + background-size: 100% 100%; + background-repeat: no-repeat; + filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.5)); +} + +.hud-ally.bars-ally > span { + position: relative; + z-index: 2; + font-size: 9px; + font-weight: 700; + color: #e8fff0; + text-shadow: 0 1px 2px #000; + line-height: 1; + margin-bottom: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.hud-ally.bars-ally > i { + display: block; + height: 6px; + max-width: 100%; + border-radius: 1px; + background-image: var(--bars-health-fill-thin); + background-size: 100% 100%; + background-repeat: no-repeat; + box-shadow: 0 0 4px rgba(80, 220, 120, 0.35); +} + +/* ── World overhead nameplates ──────────────────────────────── */ + +.hud-overheads { + position: fixed; + inset: 0; + z-index: 18; + pointer-events: none; + overflow: hidden; +} + +.oh-bar { + position: absolute; + left: 0; + top: 0; + width: 92px; + height: 22px; + transform: translate(-50%, -100%); + will-change: transform, opacity; + opacity: 0.95; + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.65)); +} + +.oh-bar--ally { + width: 120px; + height: 24px; +} + +.oh-bar__chrome { + position: absolute; + inset: 0; + background-size: 100% 100%; + background-repeat: no-repeat; + pointer-events: none; + z-index: 0; +} + +.oh-bar--enemy .oh-bar__chrome { + background-image: var(--bars-overhead-enemy); +} + +.oh-bar--ally .oh-bar__chrome { + background-image: var(--bars-overhead-ally); +} + +.oh-bar__track { + position: absolute; + left: 10%; + right: 10%; + top: 38%; + bottom: 28%; + overflow: hidden; + z-index: 1; + border-radius: 1px; +} + +.oh-bar--ally .oh-bar__track { + left: 12%; + right: 10%; + top: 42%; + bottom: 30%; +} + +.oh-bar__fill { + height: 100%; + width: 100%; + background-image: var(--bars-overhead-fill); + background-size: 100% 100%; + background-repeat: no-repeat; + transform-origin: left center; + transition: width 0.1s ease-out; +} + +.oh-bar--enemy .oh-bar__fill { + filter: hue-rotate(-12deg) saturate(1.2); +} + +.oh-bar--ally .oh-bar__fill { + filter: hue-rotate(40deg) saturate(1.15) brightness(1.05); +} + +.oh-bar__name { + position: absolute; + left: 50%; + bottom: calc(100% + 2px); + transform: translateX(-50%); + font-size: 9px; + font-weight: 700; + white-space: nowrap; + color: #fff; + text-shadow: 0 1px 2px #000; + z-index: 2; + pointer-events: none; +} + +.oh-bar.is-dead { + opacity: 0.25; +} + +.oh-bar.is-low .oh-bar__fill { + filter: hue-rotate(-25deg) saturate(1.4) brightness(1.1); +} + +.oh-bar.is-hidden { + visibility: hidden; + opacity: 0; +} diff --git a/src/ui/barsHudUi.js b/src/ui/barsHudUi.js new file mode 100644 index 0000000..60f05cd --- /dev/null +++ b/src/ui/barsHudUi.js @@ -0,0 +1,132 @@ +/** + * Bars HUD pack — Casting product picks (Warlords overhead + player frame). + * + * Disk SSOT: D:\Games\Models\bars-hud-pack + * Shipped: public/hud/bars/* + * Skill: craftpix-rpg-mmo-ui § Bars pack complement + * + * Product lock (owner): + * - Enemy overhead → overhead/overhead_health_003.png + * - Ally overhead → overhead/overhead_health_001.png + * - Player frame → unit-frames/unit_frame_009.png + * - Bar fills → fillers/health|mana|stamina_fill_*.png + */ + +/** + * Bars pack URLs must be absolute — CSS vars resolve relative to /assets/*.css + * (not the document), which turned `./hud/bars` into `/assets/hud/bars` → 404. + */ +const BARS_CDN = 'https://assets.grudge-studio.com/hud/bars'; + +/** + * Locked product asset map (do not invent alternate frames without owner pick). + */ +export const BARS_HUD_PICKS = Object.freeze({ + playerUnitFrame: 'unit-frames/unit_frame_009.png', + overheadEnemy: 'overhead/overhead_health_003.png', + overheadAlly: 'overhead/overhead_health_001.png', + /** Primary bar interiors — thin colorful fillers */ + healthFill: 'fillers/health_fill_010.png', + healthFillThin: 'fillers/health_fill_001.png', + manaFill: 'fillers/mana_fill_001.png', + staminaFill: 'fillers/stamina_fill_001.png', + /** Overhead fill (sits inside overhead chrome) */ + overheadFill: 'fillers/health_fill_015.png' +}); + +/** + * @param {string} rel path under hud/bars + */ +export function barsHudUrl(rel) { + const clean = String(rel || '').replace(/^\/+/, ''); + // Must be absolute https://host/hud/bars/... — relative urls in CSS vars + // resolve against /assets/main-*.css → /assets/hud/bars → 404. + const origin = + typeof window !== 'undefined' && window.location?.origin + ? window.location.origin + : 'https://casting.grudge.studio'; + return `${origin}/hud/bars/${clean}`; +} + +/** + * CSS custom properties for Bars pack chrome. + * @param {HTMLElement} [el] + */ +export function applyBarsHudCssVars(el = document.documentElement) { + if (!el) return; + const map = { + '--bars-player-frame': BARS_HUD_PICKS.playerUnitFrame, + '--bars-overhead-enemy': BARS_HUD_PICKS.overheadEnemy, + '--bars-overhead-ally': BARS_HUD_PICKS.overheadAlly, + '--bars-health-fill': BARS_HUD_PICKS.healthFill, + '--bars-health-fill-thin': BARS_HUD_PICKS.healthFillThin, + '--bars-mana-fill': BARS_HUD_PICKS.manaFill, + '--bars-stamina-fill': BARS_HUD_PICKS.staminaFill, + '--bars-overhead-fill': BARS_HUD_PICKS.overheadFill + }; + for (const [k, rel] of Object.entries(map)) { + el.style.setProperty(k, `url("${barsHudUrl(rel)}")`); + } + el.classList.add('bars-hud-ui'); +} + +/** + * Preload product picks so first paint is not empty. + * @returns {Promise} + */ +export function preloadBarsHudUi() { + const keys = Object.values(BARS_HUD_PICKS); + return Promise.all( + keys.map( + (rel) => + new Promise((resolve) => { + const img = new Image(); + img.onload = () => resolve(); + img.onerror = () => resolve(); + img.src = barsHudUrl(rel); + }) + ) + ).then(() => undefined); +} + +/** + * Build overhead nameplate DOM (enemy or ally). + * @param {'enemy'|'ally'} kind + * @param {{ name?: string }} [opts] + * @returns {HTMLElement} + */ +export function createOverheadBarEl(kind = 'enemy', opts = {}) { + const isAlly = kind === 'ally'; + const el = document.createElement('div'); + el.className = `oh-bar oh-bar--${isAlly ? 'ally' : 'enemy'}`; + el.setAttribute('data-oh-kind', isAlly ? 'ally' : 'enemy'); + el.innerHTML = ` + +
    +
    +
    + ${opts.name ? `${escapeHtml(opts.name)}` : ''} + `; + return el; +} + +/** + * @param {HTMLElement} el + * @param {number} hp01 0..1 + */ +export function setOverheadBarHp(el, hp01) { + if (!el) return; + const pct = Math.round(Math.max(0, Math.min(1, hp01)) * 100); + const fill = el.querySelector('[data-oh-fill]'); + if (fill) fill.style.width = `${pct}%`; + el.classList.toggle('is-dead', pct <= 0); + el.classList.toggle('is-low', pct > 0 && pct <= 25); +} + +function escapeHtml(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); +} diff --git a/src/ui/castbar.css b/src/ui/castbar.css new file mode 100644 index 0000000..847229e --- /dev/null +++ b/src/ui/castbar.css @@ -0,0 +1,186 @@ +/** + * CraftPix cast bar — center under reticle. + * Vars: --cp-cast-bg, --cp-cast-fill, --cp-cast-icon (or local url()). + */ + +.castbar { + position: fixed; + left: 50%; + top: calc(50% + 48px); + transform: translateX(-50%); + z-index: 28; + display: flex; + align-items: center; + gap: 8px; + min-width: 220px; + max-width: min(360px, 70vw); + padding: 6px 10px 6px 6px; + pointer-events: none; + opacity: 1; + transition: opacity 0.12s ease, transform 0.12s ease; + filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.55)); +} + +.castbar.is-hidden { + opacity: 0; + transform: translateX(-50%) translateY(6px); + pointer-events: none; +} + +.castbar.is-interrupt { + filter: hue-rotate(-20deg) saturate(0.5) brightness(0.85); +} + +.castbar__icon-wrap { + position: relative; + width: 40px; + height: 40px; + flex-shrink: 0; +} + +.castbar__icon-frame { + position: absolute; + inset: 0; + background-image: var(--cp-cast-icon); + background-size: 100% 100%; + background-repeat: no-repeat; + z-index: 2; +} + +.castbar__icon { + position: absolute; + inset: 7px; + border-radius: 3px; + z-index: 1; + background: radial-gradient(circle at 40% 30%, rgba(255, 255, 255, 0.25), transparent 55%), + linear-gradient(145deg, rgba(80, 140, 255, 0.55), rgba(20, 30, 50, 0.9)); +} + +/* Element tint on icon gem */ +.castbar[data-element='fire'] .castbar__icon, +.castbar__icon[data-element='fire'] { + background: radial-gradient(circle at 40% 30%, #ffb070, #c03010 70%); +} +.castbar[data-element='ice'] .castbar__icon, +.castbar__icon[data-element='ice'] { + background: radial-gradient(circle at 40% 30%, #c8f0ff, #2080c0 70%); +} +.castbar[data-element='storm'] .castbar__icon, +.castbar__icon[data-element='storm'] { + background: radial-gradient(circle at 40% 30%, #e0d0ff, #6040c0 70%); +} +.castbar[data-element='nature'] .castbar__icon, +.castbar__icon[data-element='nature'] { + background: radial-gradient(circle at 40% 30%, #c0f0a0, #308020 70%); +} +.castbar[data-element='holy'] .castbar__icon, +.castbar__icon[data-element='holy'] { + background: radial-gradient(circle at 40% 30%, #fff6c0, #c0a020 70%); +} +.castbar[data-element='arcane'] .castbar__icon, +.castbar__icon[data-element='arcane'] { + background: radial-gradient(circle at 40% 30%, #e8c0ff, #8020c0 70%); +} +.castbar[data-element='water'] .castbar__icon { + background: radial-gradient(circle at 40% 30%, #a0d8ff, #1060a0 70%); +} +.castbar[data-element='earth'] .castbar__icon { + background: radial-gradient(circle at 40% 30%, #d0b080, #705020 70%); +} + +.castbar__body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 2px; +} + +.castbar__name { + font-size: 11px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #f4f8ff; + text-shadow: 0 1px 2px #000; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.castbar__track { + position: relative; + height: 18px; + width: 100%; +} + +.castbar__bg { + position: absolute; + inset: 0; + background-image: var(--cp-cast-bg); + background-size: 100% 100%; + background-repeat: no-repeat; + z-index: 0; +} + +.castbar__fill-wrap { + position: absolute; + left: 5px; + right: 5px; + top: 4px; + bottom: 4px; + overflow: hidden; + z-index: 1; + border-radius: 1px; +} + +.castbar__fill { + height: 100%; + width: 0%; + background-image: var(--cp-cast-fill); + background-size: 100% 100%; + background-repeat: no-repeat; + filter: hue-rotate(195deg) saturate(1.5) brightness(1.1); + transition: width 0.05s linear; + transform-origin: left center; +} + +.castbar[data-element='fire'] .castbar__fill { + filter: hue-rotate(0deg) saturate(1.7) brightness(1.05); +} +.castbar[data-element='ice'] .castbar__fill, +.castbar[data-element='water'] .castbar__fill { + filter: hue-rotate(175deg) saturate(1.6) brightness(1.1); +} +.castbar[data-element='nature'] .castbar__fill, +.castbar[data-element='earth'] .castbar__fill { + filter: hue-rotate(85deg) saturate(1.5) brightness(1.05); +} +.castbar[data-element='storm'] .castbar__fill, +.castbar[data-element='arcane'] .castbar__fill { + filter: hue-rotate(250deg) saturate(1.6) brightness(1.1); +} +.castbar[data-element='holy'] .castbar__fill { + filter: hue-rotate(40deg) saturate(1.4) brightness(1.15); +} + +.castbar__meta { + display: flex; + justify-content: space-between; + font-size: 9px; + font-weight: 700; + color: rgba(230, 240, 255, 0.85); + text-shadow: 0 1px 2px #000; + letter-spacing: 0.04em; +} + +@media (max-width: 600px) { + .castbar { + min-width: 180px; + top: calc(50% + 40px); + } + .castbar__icon-wrap { + width: 32px; + height: 32px; + } +} diff --git a/src/ui/craftpix-hud.css b/src/ui/craftpix-hud.css new file mode 100644 index 0000000..59fabcb --- /dev/null +++ b/src/ui/craftpix-hud.css @@ -0,0 +1,359 @@ +/** + * CraftPix RPG MMO unit frames + hotbar — production chrome. + * Textures via CSS vars from craftpixUi.js (url(/ui/craftpix/...)). + */ + +/* ── Unit frames (player / target) ─────────────────────────── */ + +.hud-frame.cp-frame { + min-width: 240px; + max-width: 300px; + padding: 10px 12px 10px 10px; + gap: 10px; + /* Soft plate under CraftPix layers — not a flat glass card */ + background: + linear-gradient(180deg, rgba(8, 12, 18, 0.55), rgba(4, 6, 10, 0.72)); + border: none; + border-radius: 4px; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.55), + 0 12px 28px rgba(0, 0, 0, 0.5); + backdrop-filter: none; + image-rendering: auto; +} + +.hud-frame.cp-frame--hostile { + background: + linear-gradient(180deg, rgba(28, 10, 10, 0.55), rgba(12, 4, 4, 0.75)); +} + +.hud-frame--target.cp-frame.is-empty { + opacity: 0.35; + filter: grayscale(0.4); +} + +.cp-frame__avatar { + position: relative; + width: 56px; + height: 56px; + flex-shrink: 0; +} + +.cp-frame__avatar-bg, +.cp-frame__avatar-border, +.cp-frame__avatar-overlay { + position: absolute; + inset: 0; + background-size: 100% 100%; + background-repeat: no-repeat; + pointer-events: none; +} + +.cp-frame__avatar-bg { + background-image: var(--cp-avatar-bg); + z-index: 0; +} + +.cp-frame__portrait-img { + position: absolute; + inset: 8px; + width: calc(100% - 16px); + height: calc(100% - 16px); + object-fit: cover; + object-position: top center; + border-radius: 4px; + z-index: 1; + opacity: 0; + transition: opacity 0.2s ease; +} + +.cp-frame__portrait-img.is-loaded { + opacity: 1; +} + +.cp-frame__avatar-border { + background-image: var(--cp-avatar-border); + z-index: 2; +} + +.cp-frame__avatar-overlay { + background-image: var(--cp-avatar-overlay); + z-index: 3; + mix-blend-mode: soft-light; + opacity: 0.85; +} + +.cp-frame__glyph { + position: absolute; + inset: 0; + z-index: 1; + display: grid; + place-items: center; + font-size: 11px; + font-weight: 800; + letter-spacing: 0.06em; + color: rgba(230, 240, 255, 0.9); + text-shadow: 0 1px 3px #000; + pointer-events: none; +} + +.cp-frame__portrait-img.is-loaded + .cp-frame__avatar-border ~ .cp-frame__glyph, +.cp-frame__avatar:has(.cp-frame__portrait-img.is-loaded) .cp-frame__glyph { + opacity: 0; +} + +.cp-frame__body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 3px; + justify-content: center; +} + +.cp-frame .hud-frame__name { + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + text-shadow: 0 1px 2px #000; + margin-bottom: 2px; +} + +/* ── Bars (CraftPix PB / SB plate + tinted fill) ────────────── */ + +.cp-bar { + position: relative; + height: 14px; + display: grid; + align-items: center; +} + +.cp-bar__track { + position: absolute; + inset: 0; + background-image: var(--cp-pb-bg); + background-size: 100% 100%; + background-repeat: no-repeat; + z-index: 0; +} + +.cp-bar__track--sb { + background-image: var(--cp-sb-bg); +} + +.cp-bar__fill-wrap { + position: absolute; + left: 4px; + right: 4px; + top: 3px; + bottom: 3px; + overflow: hidden; + z-index: 1; + border-radius: 1px; +} + +.cp-bar__fill { + height: 100%; + width: 100%; + /* Grayscale CraftPix fill → tinted production bars */ + background-image: var(--cp-pb-fill); + background-size: 100% 100%; + background-repeat: no-repeat; + filter: hue-rotate(85deg) saturate(1.6) brightness(1.05); + transition: width 0.12s ease-out; + transform-origin: left center; +} + +.cp-bar--mp .cp-bar__fill, +.cp-bar__fill--mp { + background-image: var(--cp-sb-fill); + filter: hue-rotate(190deg) saturate(1.7) brightness(1.1); +} + +.cp-bar--sta .cp-bar__fill, +.cp-bar__fill--sta { + background-image: var(--cp-sb-fill); + filter: hue-rotate(25deg) saturate(1.8) brightness(1.05); +} + +.cp-bar--hostile .cp-bar__fill, +.cp-bar__fill--hostile { + background-image: var(--cp-pb-fill); + filter: hue-rotate(320deg) saturate(1.9) brightness(1.05); +} + +.cp-bar .hud-frame__val { + position: relative; + z-index: 2; + justify-self: end; + padding-right: 6px; + font-size: 9px; + font-weight: 700; + line-height: 14px; + color: #fff; + text-shadow: 0 0 3px #000, 0 1px 2px #000; + pointer-events: none; +} + +/* ── Hotbar slots (CraftPix Action Bar) ─────────────────────── */ + +.hud__actionbar { + bottom: 22px; + gap: 6px; + padding: 6px 8px; + border-radius: 6px; + background: rgba(4, 6, 10, 0.35); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4); +} + +.action-slot.cp-slot { + width: 72px; + height: 78px; + padding: 0; + border: none; + border-radius: 0; + background: transparent; + backdrop-filter: none; + box-shadow: none; + overflow: visible; +} + +.action-slot.cp-slot:hover { + transform: translateY(-2px); + border-color: transparent; + background: transparent; +} + +.action-slot.cp-slot.is-active { + background: transparent; + border-color: transparent; + box-shadow: 0 0 18px -2px var(--accent, #7fd6ff); +} + +.cp-slot__bg, +.cp-slot__border, +.cp-slot__press, +.cp-slot__cd { + position: absolute; + inset: 0; + background-size: 100% 100%; + background-repeat: no-repeat; + pointer-events: none; +} + +.cp-slot__bg { + background-image: var(--cp-slot-bg); + z-index: 0; +} + +.cp-slot__border { + background-image: var(--cp-slot-border); + z-index: 3; +} + +.cp-slot__press { + background-image: var(--cp-slot-press); + z-index: 4; + opacity: 0; + transition: opacity 0.08s ease; +} + +.action-slot.cp-slot:active .cp-slot__press { + opacity: 0.85; +} + +/* Cooldown: keep production scaleY sweep (styles.css) + CraftPix dim plate */ +.action-slot.cp-slot .action-slot__cd, +.cp-slot__cd { + z-index: 2; + inset: 8px; + border-radius: 3px; + background: rgba(0, 0, 0, 0.62); + transform-origin: bottom; + transform: scaleY(var(--cd, 0)); + transition: transform 0.12s linear; +} + +.action-slot.cp-slot .action-slot__key { + z-index: 5; + top: 4px; + left: 6px; + right: auto; + font-size: 10px; + background: rgba(0, 0, 0, 0.55); + border-radius: 3px; + padding: 1px 4px; +} + +.action-slot.cp-slot .action-slot__glyph { + z-index: 2; + position: absolute; + left: 50%; + top: 42%; + transform: translate(-50%, -50%); + font-size: 22px; + filter: drop-shadow(0 1px 2px #000); +} + +.action-slot.cp-slot .action-slot__label { + z-index: 5; + position: absolute; + left: 2px; + right: 2px; + bottom: 6px; + font-size: 8px; + font-weight: 700; + letter-spacing: 0.04em; + text-transform: uppercase; + color: #f0f6ff; + text-shadow: 0 1px 2px #000; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.action-slot.cp-slot.is-cooling .action-slot__glyph { + opacity: 0.55; +} + +/* Title chip — light panel plate */ +.hud__panel.cp-panel { + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + background: + linear-gradient(180deg, rgba(14, 18, 26, 0.88), rgba(6, 8, 12, 0.92)); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.45); +} + +/* Modes pill sits above craftpix hotbar */ +.hud__modes { + bottom: 112px; +} + +/* Focus: target frame brightens */ +.hud--focus .hud-frame--target.cp-frame:not(.is-empty), +.hud--softlock .hud-frame--target.cp-frame:not(.is-empty) { + box-shadow: + 0 0 0 1px rgba(255, 100, 70, 0.55), + 0 12px 28px rgba(0, 0, 0, 0.5), + 0 0 22px rgba(255, 80, 50, 0.25); +} + +@media (max-width: 720px) { + .hud-frame.cp-frame { + min-width: 180px; + max-width: 48vw; + padding: 6px; + } + .cp-frame__avatar { + width: 44px; + height: 44px; + } + .action-slot.cp-slot { + width: 56px; + height: 62px; + } + .action-slot.cp-slot .action-slot__label { + display: none; + } +} diff --git a/src/ui/craftpixUi.js b/src/ui/craftpixUi.js new file mode 100644 index 0000000..7b5e69e --- /dev/null +++ b/src/ui/craftpixUi.js @@ -0,0 +1,141 @@ +/** + * CraftPix RPG MMO UI — production chrome for casting / Warlords player HUD. + * + * Local SSOT: D:\Games\Models\craftpix-rpg-mmo-ui + * Shipped: public/ui/craftpix/* + CDN assets.grudge-studio.com/ui/craftpix/* + * + * CRITICAL: Never return relative urls for CSS custom properties. + * When CSS in /assets/main-*.css does background-image: var(--cp-*), + * relative urls resolve against the *stylesheet* path → /assets/ui/craftpix → 404. + * Always use absolute https:// URLs. + */ + +import { sameOriginFleetUrl } from '../config/fleetEnv.js'; + +const CDN = 'https://assets.grudge-studio.com/ui/craftpix'; + +/** @type {Record} */ +export const CRAFTPIX = { + avatarBg: 'unit/avatar_bg.png', + avatarBorder: 'unit/avatar_border.png', + avatarOverlay: 'unit/avatar_overlay.png', + pbBg: 'unit/pb_bg.png', + pbFill: 'unit/pb_fill.png', + sbBg: 'unit/sb_bg.png', + sbFill: 'unit/sb_fill.png', + levelBg: 'unit/level_bg.png', + levelBorder: 'unit/level_border.png', + skull: 'unit/skull.png', + slotBg: 'hotbar/slot_bg.png', + slotBorder: 'hotbar/slot_border.png', + slotCd: 'hotbar/slot_cd.png', + slotPress: 'hotbar/slot_press.png', + trackBg: 'fill/track_bg.png', + trackFill: 'fill/track_fill.png', + panelBg: 'panel/bg.png', + castBg: 'cast/bg.png', + castFill: 'cast/fill.png', + castIconFrame: 'cast/icon_frame.png', +}; + +/** + * Absolute CraftPix texture URL. + * **Same-origin first** (`/ui/craftpix` shipped in public/) — CDN lacks CORS for + * crossOrigin Image() preload and caused console noise + missing chrome on casting.grudge.studio. + * Never return relative paths (CSS vars resolve against /assets/*.css → 404). + * + * @param {string} rel path under ui/craftpix + * @param {{ preferCdn?: boolean, localOnly?: boolean }} [opts] + */ +export function craftpixUrl(rel, opts = {}) { + const clean = String(rel || '').replace(/^\/+/, ''); + if (!clean) { + return typeof window !== 'undefined' && window.location?.origin + ? `${window.location.origin}/ui/craftpix` + : CDN; + } + if (opts.preferCdn) return sameOriginFleetUrl(`${CDN}/${clean}`); + // Default: same-origin ship (public/ui/craftpix) — production lab / client handoff + if (typeof window !== 'undefined' && window.location?.origin) { + return `${window.location.origin}/ui/craftpix/${clean}`; + } + return sameOriginFleetUrl(`${CDN}/${clean}`); +} + +/** + * CSS custom properties — full absolute url("https://host/ui/craftpix/...") only. + * @param {HTMLElement} [el] + */ +export function applyCraftpixCssVars(el = document.documentElement) { + if (!el) return; + const map = { + '--cp-avatar-bg': CRAFTPIX.avatarBg, + '--cp-avatar-border': CRAFTPIX.avatarBorder, + '--cp-avatar-overlay': CRAFTPIX.avatarOverlay, + '--cp-pb-bg': CRAFTPIX.pbBg, + '--cp-pb-fill': CRAFTPIX.pbFill, + '--cp-sb-bg': CRAFTPIX.sbBg, + '--cp-sb-fill': CRAFTPIX.sbFill, + '--cp-level-bg': CRAFTPIX.levelBg, + '--cp-level-border': CRAFTPIX.levelBorder, + '--cp-skull': CRAFTPIX.skull, + '--cp-slot-bg': CRAFTPIX.slotBg, + '--cp-slot-border': CRAFTPIX.slotBorder, + '--cp-slot-cd': CRAFTPIX.slotCd, + '--cp-slot-press': CRAFTPIX.slotPress, + '--cp-track-bg': CRAFTPIX.trackBg, + '--cp-track-fill': CRAFTPIX.trackFill, + '--cp-panel-bg': CRAFTPIX.panelBg, + '--cp-cast-bg': CRAFTPIX.castBg, + '--cp-cast-fill': CRAFTPIX.castFill, + '--cp-cast-icon': CRAFTPIX.castIconFrame, + }; + for (const [k, rel] of Object.entries(map)) { + const abs = craftpixUrl(rel); // same-origin + el.style.setProperty(k, `url("${abs}")`); + } + el.classList.add('craftpix-ui'); +} + +/** + * Race portrait CDN (Foundry / client fleet). + * @param {string} raceId WK|ELF|… + */ +export function racePortraitUrl(raceId) { + const map = { + WK: 'human', + ELF: 'elf', + BRB: 'barbarian', + ORC: 'orc', + UD: 'undead', + DWF: 'dwarf', + human: 'human', + elf: 'elf', + barbarian: 'barbarian', + orc: 'orc', + undead: 'undead', + dwarf: 'dwarf', + }; + const key = map[String(raceId || 'WK')] || 'human'; + return `https://client.grudge-studio.com/images/portraits/${key}.png`; +} + +/** + * Preload unit-frame + hotbar sheets. + * @returns {Promise} + */ +export function preloadCraftpixUi() { + const keys = Object.values(CRAFTPIX); + return Promise.all( + keys.map( + (rel) => + new Promise((resolve) => { + const img = new Image(); + img.onload = () => resolve(); + img.onerror = () => resolve(); + // Same-origin → no CORS; crossOrigin only needed for CDN canvas taint + img.src = craftpixUrl(rel); + }), + ), + ).then(() => undefined); +} diff --git a/src/ui/dropBag.css b/src/ui/dropBag.css new file mode 100644 index 0000000..aefdf18 --- /dev/null +++ b/src/ui/dropBag.css @@ -0,0 +1,110 @@ +.drop-bag { + position: fixed; + bottom: 140px; + left: 16px; + width: min(280px, calc(100vw - 32px)); + max-height: 320px; + z-index: 41; + display: flex; + flex-direction: column; + background: var(--ui-bg-solid); + border: 1px solid var(--ui-border); + border-radius: var(--ui-radius); + box-shadow: var(--ui-shadow); + backdrop-filter: blur(12px); + pointer-events: auto; + overflow: hidden; +} + +.drop-bag[hidden] { + display: none !important; +} + +.drop-bag__head { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 12px 6px; + border-bottom: 1px solid var(--ui-border); +} + +.drop-bag__head h3 { + margin: 0; + font-size: 13px; +} + +.drop-bag__close { + border: 0; + background: transparent; + color: var(--ui-text-dim); + font-size: 18px; + cursor: pointer; +} + +.drop-bag__hint { + margin: 6px 12px; + font-size: 10px; + color: var(--ui-text-dim); +} + +.drop-bag__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; + padding: 8px 12px 12px; + overflow: auto; +} + +.drop-bag__slot { + position: relative; + border: 2px solid var(--tier, #9aa3ad); + border-radius: 10px; + background: rgba(0, 0, 0, 0.4); + padding: 6px; + cursor: grab; + display: flex; + flex-direction: column; + align-items: center; + gap: 4px; + min-height: 72px; + box-shadow: 0 0 12px color-mix(in srgb, var(--tier) 35%, transparent); +} + +.drop-bag__slot.is-dragging { + opacity: 0.5; +} + +.drop-bag__slot img { + width: 36px; + height: 36px; + object-fit: contain; + pointer-events: none; +} + +.drop-bag__qty { + position: absolute; + top: 2px; + right: 4px; + font-size: 10px; + font-weight: 700; + color: #fff; + text-shadow: 0 1px 2px #000; +} + +.drop-bag__name { + font-size: 9px; + color: var(--ui-text-dim); + text-align: center; + line-height: 1.2; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.drop-bag__empty { + grid-column: 1 / -1; + font-size: 11px; + color: var(--ui-text-dim); + margin: 0; +} diff --git a/src/ui/fishing.css b/src/ui/fishing.css new file mode 100644 index 0000000..081138c --- /dev/null +++ b/src/ui/fishing.css @@ -0,0 +1,157 @@ +/* Fishing profession HUD — Palworld / Angler bar language */ + +.fishing-hud { + --fh-bar: url('/ui/fishing/FishingBar.png'); + --fh-target: url('/ui/fishing/FishingTarget.png'); + --fh-pointer: url('/ui/fishing/FishingPointer.png'); + position: fixed; + left: 50%; + bottom: 18%; + transform: translateX(-50%); + z-index: 45; + width: min(420px, 88vw); + pointer-events: none; + font-family: 'Inter', system-ui, sans-serif; + color: #e8f4ff; + display: none; +} + +.fishing-hud.is-active { + display: block; +} + +.fishing-hud__prof { + text-align: center; + font-size: 0.68rem; + letter-spacing: 0.04em; + opacity: 0.9; + margin-bottom: 0.25rem; + text-shadow: 0 1px 4px #000; + color: #9fd4ff; +} + +.fishing-hud__meals { + display: flex; + justify-content: center; + gap: 0.35rem; + margin-bottom: 0.3rem; +} + +.meal-slot { + width: 22px; + height: 22px; + border-radius: 50%; + font-size: 0.65rem; + font-weight: 700; + display: inline-flex; + align-items: center; + justify-content: center; + opacity: 0.35; + border: 1px solid rgba(255, 255, 255, 0.25); + text-shadow: 0 1px 2px #000; +} +.meal-slot.is-on { + opacity: 1; + box-shadow: 0 0 8px currentColor; +} +.meal-slot--red { + color: #ff6b6b; + background: rgba(80, 20, 20, 0.75); +} +.meal-slot--green { + color: #6bff9a; + background: rgba(20, 60, 30, 0.75); +} +.meal-slot--blue { + color: #6bb8ff; + background: rgba(15, 35, 70, 0.75); +} + +.fishing-hud__phase { + text-align: center; + font-size: 0.75rem; + letter-spacing: 0.08em; + text-transform: uppercase; + opacity: 0.85; + margin-bottom: 0.35rem; + text-shadow: 0 1px 4px #000; +} + +.fishing-hud__bar { + position: relative; + height: 36px; + border-radius: 8px; + background: + linear-gradient(180deg, rgba(20, 40, 55, 0.9), rgba(8, 14, 20, 0.95)), + var(--fh-bar) center / cover no-repeat; + border: 1px solid rgba(120, 180, 220, 0.35); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); + overflow: hidden; +} + +.fishing-hud__zone { + position: absolute; + top: 4px; + bottom: 4px; + border-radius: 6px; + background: + linear-gradient(180deg, rgba(120, 220, 160, 0.55), rgba(40, 140, 90, 0.45)), + var(--fh-target) center / contain no-repeat; + border: 1px solid rgba(180, 255, 200, 0.5); + box-shadow: inset 0 0 12px rgba(100, 255, 160, 0.25); + transition: left 0.05s linear, width 0.05s linear; +} + +.fishing-hud__fish { + position: absolute; + top: 50%; + width: 22px; + height: 22px; + margin: -11px 0 0 -11px; + background: var(--fh-pointer) center / contain no-repeat; + filter: drop-shadow(0 0 4px #7fd6ff); + transition: left 0.04s linear; +} + +.fishing-hud__meters { + display: flex; + gap: 0.5rem; + margin-top: 0.4rem; +} + +.fishing-hud__meter { + flex: 1; + height: 8px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.45); + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.08); +} + +.fishing-hud__meter > i { + display: block; + height: 100%; + width: 0%; + background: linear-gradient(90deg, #3d8bff, #7fd6ff); +} + +.fishing-hud__meter--tension > i { + background: linear-gradient(90deg, #f0c040, #ff4a2a); +} + +.fishing-hud__hint { + margin-top: 0.45rem; + text-align: center; + font-size: 0.68rem; + opacity: 0.7; + text-shadow: 0 1px 3px #000; +} + +.fishing-hud__catch { + margin-top: 0.5rem; + text-align: center; + font-size: 0.9rem; + font-weight: 650; + color: #9ff0c0; + text-shadow: 0 0 12px rgba(80, 200, 140, 0.6); +} diff --git a/src/ui/fishingUi.js b/src/ui/fishingUi.js new file mode 100644 index 0000000..f505562 --- /dev/null +++ b/src/ui/fishingUi.js @@ -0,0 +1,125 @@ +/** + * Fishing fight HUD — bar / zone / fish pointer + progress + tension + * + profession strip (level · rod · nautical · SWG RGB meals). + */ +import './fishing.css'; + +export class FishingUi { + constructor(host = document.body) { + this.root = document.createElement('div'); + this.root.className = 'fishing-hud'; + this.root.innerHTML = ` + +
    + R + G + B +
    +
    Ready
    +
    +
    +
    +
    +
    +
    +
    +
    +
    Shift+F fish · RMB aim · LMB cast · S snag · wheel reel
    + + `; + host.appendChild(this.root); + this.el = { + phase: this.root.querySelector('[data-phase]'), + zone: this.root.querySelector('[data-zone]'), + fish: this.root.querySelector('[data-fish]'), + progress: this.root.querySelector('[data-progress]'), + tension: this.root.querySelector('[data-tension]'), + hint: this.root.querySelector('[data-hint]'), + catch: this.root.querySelector('[data-catch]'), + prof: this.root.querySelector('[data-prof]'), + mealRed: this.root.querySelector('[data-meal-red]'), + mealGreen: this.root.querySelector('[data-meal-green]'), + mealBlue: this.root.querySelector('[data-meal-blue]') + }; + } + + setActive(on) { + this.root.classList.toggle('is-active', !!on); + if (!on) this.el.catch.hidden = true; + } + + /** + * @param {{ + * phase: string, + * fight?: import('../fishing/fishingFight.js').FightState|null, + * catchLabel?: string|null, + * hint?: string, + * prof?: { + * level?: number, + * xp?: number, + * skillPoints?: number, + * pole?: string, + * nautical?: number, + * meals?: object + * } + * }} state + */ + render(state) { + const phaseLabels = { + idle: 'Fishing · equip pole', + aim: 'Aim · LMB cast lure', + cast: 'Casting…', + waiting: 'Waiting for bite…', + bite: 'BITE! · S or RMB snag', + fight: 'Fight · wheel reel / slack', + won: 'Caught!', + lost: 'Got away…' + }; + this.el.phase.textContent = phaseLabels[state.phase] || state.phase; + if (state.hint) this.el.hint.textContent = state.hint; + + if (state.prof) { + this.el.prof.hidden = false; + const n = state.prof.nautical != null ? ` · ⛵×${Number(state.prof.nautical).toFixed(2)}` : ''; + const lure = state.prof.lure ? ` · ${state.prof.lure}` : ''; + const sz = state.prof.maxSize != null ? ` · size≤${state.prof.maxSize}` : ''; + this.el.prof.textContent = `Lv ${state.prof.level || 1} · ${state.prof.pole || 'rod'}${lure}${sz} · SP ${state.prof.skillPoints ?? 0}${n}`; + this._paintMeals(state.prof.meals); + } + + const f = state.fight; + if (f && (state.phase === 'fight' || state.phase === 'bite' || state.phase === 'waiting')) { + const left = Math.max(0, (f.zoneCenter - f.zoneWidth / 2) * 100); + this.el.zone.style.left = `${left}%`; + this.el.zone.style.width = `${(f.zoneWidth || 0.18) * 100}%`; + this.el.fish.style.left = `${(f.fishPos || 0.5) * 100}%`; + this.el.progress.style.width = `${Math.round((f.progress || 0) * 100)}%`; + this.el.tension.style.width = `${Math.round(Math.min(1, f.tension || 0) * 100)}%`; + } + + if (state.catchLabel) { + this.el.catch.hidden = false; + this.el.catch.textContent = state.catchLabel; + } else if (state.phase !== 'won') { + this.el.catch.hidden = true; + } + } + + _paintMeals(meals) { + if (!meals) return; + for (const [c, el] of [ + ['red', this.el.mealRed], + ['green', this.el.mealGreen], + ['blue', this.el.mealBlue] + ]) { + const m = meals[c]; + el.classList.toggle('is-on', !!m); + el.title = m ? `${m.label} (${c})` : `${c} food empty`; + el.textContent = m ? m.label.slice(0, 1) : c[0].toUpperCase(); + } + } + + dispose() { + this.root.remove(); + } +} diff --git a/src/ui/glyphs.js b/src/ui/glyphs.js index 785d437..8bdfda2 100644 --- a/src/ui/glyphs.js +++ b/src/ui/glyphs.js @@ -51,5 +51,16 @@ const AIR = WRAP(` `); -/** Keyed by the ids in `ELEMENTS`. */ -export const ELEMENT_SIGILS = { fire: FIRE, water: WATER, earth: EARTH, wind: AIR }; +/** Keyed by the ids in `ELEMENTS` (fire · storm · ice · nature · holy · arcane). */ +export const ELEMENT_SIGILS = { + fire: FIRE, + storm: AIR, + ice: WATER, + nature: EARTH, + holy: FIRE, // light tongue reuse until dedicated holy glyph + arcane: AIR, + // legacy aliases + water: WATER, + earth: EARTH, + wind: AIR +}; diff --git a/src/ui/iconResolve.js b/src/ui/iconResolve.js new file mode 100644 index 0000000..0749b14 --- /dev/null +++ b/src/ui/iconResolve.js @@ -0,0 +1,212 @@ +/** + * Unified item icon resolution for Main Panel, DropBag, world drops. + * + * Priority: + * 1. Absolute / already-resolved URL on the item + * 2. Same-origin lab paths (./icons/dev-island/…) + * 3. Known material / weapon id map → CDN 496_rpg_icons + * 4. Category fallbacks + * + * Never invent AI icons — only CDN pack + lab packs. + * @see assets.grudge-studio.com/icons · public/icons/dev-island + */ + +import { ASSETS_URL, resolveLabAssetUrl, sameOriginFleetUrl } from '../config/fleetEnv.js'; +import { cdnUrl } from '../loot/prefabAssets.js'; + +const I496 = sameOriginFleetUrl(`${ASSETS_URL}/icons/496_rpg_icons`); + +/** Lab mineral / harvest icons (shipped in dist) */ +export const LAB_ICONS = Object.freeze({ + stone: './icons/dev-island/minerals/FD_Minerals_Stones.png', + inventorySlot: './icons/dev-island/inventory/Inventory_Slot_1.png', + inventoryShell: './icons/dev-island/inventory/Inventory.png', + health: './icons/dev-island/inventory/Health_01.png' +}); + +/** + * Stable id → CDN / lab icon (materials, T0, common loot). + * @type {Record} + */ +export const ICON_BY_ID = Object.freeze({ + // Harvest materials + 'mat-stone': LAB_ICONS.stone, + 'mat-ore-chunk': `${I496}/I_Coal.png`, + 'mat-pebble': LAB_ICONS.stone, + 'mat-wood': `${I496}/I_Wood01.png`, + 'mat-herb': `${I496}/I_Leaf.png`, + 'mat-fiber': `${I496}/I_Fiber.png`, + 'mat-hemp': `${I496}/I_Fiber.png`, + t0_stone: LAB_ICONS.stone, + t0_wood: `${I496}/I_Wood01.png`, + // T0 weapons (match t0WeaponCatalog ids) + 't0-sword': `${I496}/W_Sword001.png`, + 't0-wand': `${I496}/W_Wand001.png`, + 't0-tool': `${I496}/W_PickAxe001.png`, + 't0-bow': `${I496}/W_Bow01.png`, + 't0-staff': `${I496}/W_Staff01.png`, + 't0-axe': `${I496}/W_Axe001.png`, + 't0-dagger': `${I496}/W_Dagger001.png`, + // Armour kits + kit_body_a: `${I496}/A_Armour01.png`, + kit_head_a: `${I496}/A_Helmet01.png`, + kit_arms_a: `${I496}/A_Armour02.png`, + kit_legs_a: `${I496}/A_Shoes01.png` +}); + +/** Kind / class → fallback icon */ +const KIND_FALLBACK = Object.freeze({ + weapon: `${I496}/W_Sword001.png`, + tool: `${I496}/W_PickAxe001.png`, + armour: `${I496}/A_Armour01.png`, + armor: `${I496}/A_Armour01.png`, + shield: `${I496}/E_Shield01.png`, + mat: LAB_ICONS.stone, + material: LAB_ICONS.stone, + materials: LAB_ICONS.stone, + herb: `${I496}/I_Leaf.png`, + wood: `${I496}/I_Wood01.png`, + ore: `${I496}/I_Coal.png`, + rock: LAB_ICONS.stone, + consumable: `${I496}/P_Red01.png`, + food: `${I496}/I_C_Meat.png`, + relic: `${I496}/I_Gem01.png`, + mount: `${I496}/S_Buff07.png`, + default: `${I496}/I_Bag.png` +}); + +/** + * @param {object|null|undefined} item + * @returns {string|null} + */ +export function resolveItemIcon(item) { + if (!item) return null; + + // Explicit fields + const direct = + item.iconUrl || + item.icon || + item.assets?.iconUrl || + (item.iconR2Key ? cdnUrl(item.iconR2Key) : null) || + (item.assets?.iconR2Key ? cdnUrl(item.assets.iconR2Key) : null); + if (direct) return normalizeIconUrl(direct); + + const id = String(item.id || item.itemId || item.defId || '').toLowerCase(); + if (id && ICON_BY_ID[id]) return normalizeIconUrl(ICON_BY_ID[id]); + // Partial id match + for (const [key, url] of Object.entries(ICON_BY_ID)) { + if (id.includes(key) || key.includes(id)) return normalizeIconUrl(url); + } + + const kind = String( + item.kind || item.category || item.classId || item.weaponType || '' + ).toLowerCase(); + for (const [k, url] of Object.entries(KIND_FALLBACK)) { + if (k !== 'default' && kind.includes(k)) return normalizeIconUrl(url); + } + + // Name heuristics + const name = String(item.name || '').toLowerCase(); + if (/wood|log|timber/.test(name)) return normalizeIconUrl(KIND_FALLBACK.wood); + if (/ore|iron|copper|coal/.test(name)) return normalizeIconUrl(KIND_FALLBACK.ore); + if (/stone|rock|pebble/.test(name)) return normalizeIconUrl(LAB_ICONS.stone); + if (/herb|leaf|flower/.test(name)) return normalizeIconUrl(KIND_FALLBACK.herb); + if (/sword|blade/.test(name)) return normalizeIconUrl(ICON_BY_ID['t0-sword']); + if (/wand|staff/.test(name)) return normalizeIconUrl(ICON_BY_ID['t0-wand']); + if (/pick|tool|axe/.test(name)) return normalizeIconUrl(ICON_BY_ID['t0-tool']); + + return normalizeIconUrl(KIND_FALLBACK.default); +} + +/** + * Normalize relative lab paths and CDN. + * @param {string} url + */ +export function normalizeIconUrl(url) { + if (!url) return null; + const s = String(url).trim(); + if (!s) return null; + if (/^https?:\/\//i.test(s) || s.startsWith('data:')) return s; + // Prefer same-origin for lab icons + if (s.startsWith('./') || s.startsWith('/')) return s; + if (s.startsWith('icons/') || s.startsWith('ui/')) return `./${s}`; + return resolveLabAssetUrl(s) || cdnUrl(s) || s; +} + +/** + * Attach icon + iconUrl to bag/equip/drop items (mutates copy). + * @param {object} item + */ +export function withResolvedIcon(item) { + if (!item) return item; + const icon = resolveItemIcon(item); + return { + ...item, + icon: icon || item.icon || null, + iconUrl: icon || item.iconUrl || item.icon || null + }; +} + +/** + * Present bag-safe item from harvest loot or world drop. + * @param {object} loot + */ +export function bagItemFromLoot(loot) { + const base = { + id: loot.id || loot.itemId, + name: loot.name || loot.id, + kind: loot.kind || loot.category || 'mat', + category: loot.category || 'materials', + tier: loot.tier ?? 0, + qty: loot.qty || 1, + slotHint: loot.slotHint, + iconUrl: loot.iconUrl, + icon: loot.icon || loot.iconUrl + }; + return withResolvedIcon(base); +} + +/** + * Map catalog GameItemRow → bag slot item. + * @param {import('../api/gameItemCatalog.js').GameItemRow} row + */ +export function bagItemFromCatalogRow(row) { + if (!row) return null; + return withResolvedIcon({ + id: row.id, + name: row.name, + kind: row.equippable + ? row.category === 'tools' + ? 'tool' + : row.category === 'armor' + ? 'armour' + : 'weapon' + : row.category || 'mat', + category: row.category, + tier: row.tier ?? 0, + qty: 1, + slotHint: mapCatalogSlotHint(row), + iconUrl: row.iconUrl, + modelUrl: row.modelUrl, + weaponType: row.weaponType + }); +} + +/** + * @param {{ slot?: string, weaponType?: string, category?: string }} row + */ +function mapCatalogSlotHint(row) { + const s = String(row.slot || row.weaponType || '').toLowerCase(); + if (/head|helm/.test(s)) return 'head'; + if (/body|chest|armor/.test(s) && !/arm/.test(s)) return 'body'; + if (/arm|glove/.test(s)) return 'arms'; + if (/leg|boot|shoe/.test(s)) return 'legs'; + if (/shield|off|tome/.test(s)) return 'offHand'; + if (/back|cape|cloak/.test(s)) return 'back'; + if (/mount/.test(s)) return 'mount'; + if (/relic/.test(s)) return 'relic'; + if (row.category === 'weapons' || row.category === 'tools' || row.category === 't0') { + return 'mainHand'; + } + return undefined; +} diff --git a/src/ui/itemContextMenu.css b/src/ui/itemContextMenu.css new file mode 100644 index 0000000..a087346 --- /dev/null +++ b/src/ui/itemContextMenu.css @@ -0,0 +1,91 @@ +/** + * MMO item RMB context menu — Main Panel / DropBag + * Chrome: CraftPix panel tones + gold Warlords borders + */ + +.item-ctx-menu { + position: fixed; + z-index: 12000; + min-width: 200px; + max-width: min(280px, 90vw); + padding: 0; + margin: 0; + background: + linear-gradient(165deg, rgba(12, 22, 34, 0.97), rgba(8, 14, 22, 0.98)), + var(--mp-panel-bg, none); + background-size: cover; + border: 1px solid rgba(201, 149, 10, 0.35); + border-radius: 8px; + box-shadow: + 0 0 0 1px rgba(0, 0, 0, 0.5), + 0 16px 40px rgba(0, 0, 0, 0.65), + inset 0 1px 0 rgba(255, 220, 140, 0.08); + font-family: system-ui, 'Segoe UI', sans-serif; + color: #e8eef6; + pointer-events: auto; + user-select: none; +} + +.item-ctx-menu__head { + padding: 8px 12px; + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: #c9a227; + border-bottom: 1px solid rgba(201, 149, 10, 0.22); + background: rgba(0, 0, 0, 0.25); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.item-ctx-menu__list { + list-style: none; + margin: 0; + padding: 4px 0; +} + +.item-ctx-menu__item { + display: block; + width: 100%; + text-align: left; + border: 0; + background: transparent; + color: #dce6f0; + padding: 8px 14px; + font-size: 0.82rem; + cursor: pointer; + min-height: 36px; +} + +.item-ctx-menu__item:hover:not(:disabled) { + background: rgba(40, 100, 160, 0.35); + color: #fff; +} + +.item-ctx-menu__item:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.item-ctx-menu__item.is-danger { + color: #f0a0a0; +} + +.item-ctx-menu__item.is-danger:hover:not(:disabled) { + background: rgba(120, 30, 30, 0.45); + color: #ffd0d0; +} + +/* Slot affordance: show RMB is available */ +.mp-bag-slot[data-bag-i], +.mp-slot[data-pd-slot], +.drop-bag__slot[data-id] { + cursor: context-menu; +} + +.mp-bag-slot.is-pick { + outline: 2px solid #5eb0ff; + outline-offset: 1px; +} diff --git a/src/ui/itemContextMenu.js b/src/ui/itemContextMenu.js new file mode 100644 index 0000000..59b11d2 --- /dev/null +++ b/src/ui/itemContextMenu.js @@ -0,0 +1,407 @@ +/** + * MMO item / equipment RMB context menu (Main Panel + DropBag). + * + * WoW/Albion-style: Equip · Use · Split · Drop · Inspect · Deposit. + * Does not steal canvas combat RMB focus — only bound on UI slots. + * + * @see docs/MAIN_PANEL_INVENTORY_SSOT.md · mainPanelSlots.js + */ + +import { + ALL_PAPERDOLL_SLOTS, + bagAdd, + bagRemoveAt, + itemFitsSlot, + loadBag, + loadEquipMap, + saveBag, + saveEquipMap +} from './mainPanelSlots.js'; +import { MAIN_PANEL_PROD } from './uiAssetCatalog.js'; + +/** @typedef {'bag'|'paperdoll'|'dropbag'} ItemContextSource */ + +/** + * @typedef {object} ItemContextTarget + * @property {ItemContextSource} source + * @property {object|null} item + * @property {number} [bagIndex] + * @property {string} [slotId] paperdoll + * @property {string} [dropId] + */ + +/** + * @typedef {object} ItemContextHandlers + * @property {(msg: string) => void} [onToast] + * @property {(item: object, slotDef: object, bagIndex?: number) => void|Promise} [onEquip] + * @property {(slotId: string) => void|Promise} [onUnequip] + * @property {(item: object) => void} [onUse] + * @property {(item: object, clientX: number, clientY: number) => void} [onDropWorld] + * @property {(item: object) => void|Promise} [onDeposit] + * @property {() => void} [onRefresh] + * @property {(slotId: string) => void} [onOpenSlotPicker] + * @property {(slotId: string) => void} [onOpenCatalogPicker] + * @property {(item: object) => void} [onEditMesh] + */ + +let _menuEl = null; +let _open = false; + +function ensureMenu() { + if (_menuEl) return _menuEl; + const el = document.createElement('div'); + el.id = 'item-ctx-menu'; + el.className = 'item-ctx-menu'; + el.hidden = true; + el.setAttribute('role', 'menu'); + document.body.appendChild(el); + _menuEl = el; + + // Close on outside click / Esc + document.addEventListener( + 'pointerdown', + (e) => { + if (!_open) return; + if (el.contains(/** @type {Node} */ (e.target))) return; + closeItemContextMenu(); + }, + true + ); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape') closeItemContextMenu(); + }); + return el; +} + +export function closeItemContextMenu() { + _open = false; + if (_menuEl) { + _menuEl.hidden = true; + _menuEl.innerHTML = ''; + } +} + +/** + * Build actions for target. + * @param {ItemContextTarget} target + * @param {ItemContextHandlers} h + */ +function buildActions(target, h) { + const item = target.item; + /** @type {{ id: string, label: string, danger?: boolean, disabled?: boolean, run: () => void|Promise }[]} */ + const actions = []; + + if (!item) { + if (target.source === 'paperdoll' && target.slotId) { + actions.push({ + id: 'catalog', + label: 'Browse T0–T1 catalog…', + run: () => h.onOpenCatalogPicker?.(target.slotId) + }); + actions.push({ + id: 'pick', + label: 'Choose from bag…', + run: () => h.onOpenSlotPicker?.(target.slotId) + }); + } + return actions; + } + + const name = item.name || item.id || 'Item'; + + // Equip + if (target.source === 'bag' || target.source === 'dropbag') { + const slot = bestSlotForItem(item); + actions.push({ + id: 'equip', + label: slot ? `Equip → ${slot.label}` : 'Equip (no matching slot)', + disabled: !slot, + run: async () => { + if (!slot) return; + if (target.source === 'bag' && target.bagIndex != null) { + await h.onEquip?.(item, slot, target.bagIndex); + } else { + // DropBag: add to main bag first then equip + const r = bagAdd({ ...item, qty: 1 }); + if (r.ok) await h.onEquip?.(item, slot, r.index); + else h.onToast?.('Main bag full'); + } + } + }); + } + + // Unequip paperdoll + if (target.source === 'paperdoll' && target.slotId) { + actions.push({ + id: 'unequip', + label: 'Unequip', + run: async () => { + await h.onUnequip?.(target.slotId); + } + }); + actions.push({ + id: 'catalog', + label: 'Replace from T0–T1 catalog…', + run: () => h.onOpenCatalogPicker?.(target.slotId) + }); + actions.push({ + id: 'swap', + label: 'Replace from bag…', + run: () => h.onOpenSlotPicker?.(target.slotId) + }); + actions.push({ + id: 'mesh', + label: 'Edit mesh color / scale / rotate…', + run: () => h.onEditMesh?.(item) + }); + } + + // Use (mats/consumable heuristics) + const kind = String(item.kind || '').toLowerCase(); + if (/consumable|food|potion|use|mat|material/.test(kind) || item.useable) { + actions.push({ + id: 'use', + label: 'Use', + run: () => h.onUse?.(item) + }); + } + + // Split stack + if ((item.qty || 1) > 1 && target.source === 'bag' && target.bagIndex != null) { + actions.push({ + id: 'split', + label: 'Split stack (½)', + run: () => { + splitBagStack(target.bagIndex); + h.onToast?.(`Split ${name}`); + h.onRefresh?.(); + } + }); + } + + // Drop to world + if (h.onDropWorld && (target.source === 'bag' || target.source === 'dropbag')) { + actions.push({ + id: 'drop', + label: 'Drop to world', + danger: true, + run: () => { + const ix = target.bagIndex; + if (target.source === 'bag' && ix != null) { + const removed = bagRemoveAt(ix, 1); + if (removed) h.onDropWorld(removed, window.innerWidth / 2, window.innerHeight / 2); + } else { + h.onDropWorld({ ...item, qty: 1 }, window.innerWidth / 2, window.innerHeight / 2); + } + h.onRefresh?.(); + } + }); + } + + // Deposit account bag (Railway via craft) + if (h.onDeposit) { + actions.push({ + id: 'deposit', + label: 'Deposit to account bag…', + run: async () => { + await h.onDeposit?.(item); + } + }); + } + + actions.push({ + id: 'inspect', + label: 'Inspect', + run: () => { + const lines = [ + name, + `id: ${item.id || '—'}`, + `kind: ${item.kind || '—'}`, + `tier: ${item.tier ?? '—'}`, + `qty: ${item.qty || 1}`, + item.slotHint ? `slot: ${item.slotHint}` : null + ].filter(Boolean); + h.onToast?.(lines.join(' · ')); + console.info('[ItemInspect]', item); + } + }); + + actions.push({ + id: 'craft', + label: 'Open Craft SSOT ↗', + run: () => { + window.open(MAIN_PANEL_PROD.craft, '_blank', 'noopener'); + } + }); + + return actions; +} + +/** + * Pick paperdoll slot for bag Equip. + * Dual weapons: fill empty Weapon 1 first, then Weapon 2 (was ring/cloak). + * Off hands: Off 1 then Off 2. No belt/amulet/ring/cloak. + * @param {object} item + */ +export function bestSlotForItem(item) { + const slots = ALL_PAPERDOLL_SLOTS; + const equipMap = loadEquipMap(); + // Prefer explicit slotHint (weapon2 / offHand2 / mainHand / …) + const hint = String(item.slotHint || item.equipSlot || item.slot || '').toLowerCase(); + if (hint) { + const exact = slots.find( + (s) => + s.id.toLowerCase() === hint || + s.meshSlot === hint || + (hint === 'mainhand' && s.id === 'mainHand') || + (hint === 'weapon2' && s.id === 'weapon2') || + (hint === 'offhand' && s.id === 'offHand') || + (hint === 'offhand2' && s.id === 'offHand2') + ); + if (exact && itemFitsSlot(item, exact)) return exact; + } + // Category / kind + const kind = String(item.kind || item.category || '').toLowerCase(); + if (/weapon|tool|t0/.test(kind) && !/shield|tome|offhand/.test(kind + hint)) { + // Prefer empty main set slots in order Weapon 1 → Weapon 2 + for (const id of ['mainHand', 'weapon2']) { + const s = slots.find((x) => x.id === id); + if (s && !equipMap[id] && itemFitsSlot(item, s)) return s; + } + // Both filled → active set main (swap replaces that slot on equip path) + return slots.find((s) => s.id === 'mainHand') || null; + } + if (/shield|tome|offhand/.test(kind + hint)) { + for (const id of ['offHand', 'offHand2']) { + const s = slots.find((x) => x.id === id); + if (s && !equipMap[id] && itemFitsSlot(item, s)) return s; + } + return slots.find((s) => s.id === 'offHand') || null; + } + if (/back|cape|wing|windsurf|shell|glider|shark|fin/.test(kind + hint)) { + return slots.find((s) => s.id === 'back') || null; + } + if (/relic|class/.test(kind)) return slots.find((s) => s.id === 'relic') || null; + if (/mount/.test(kind)) return slots.find((s) => s.id === 'mount') || null; + if (/armour|armor|head|body|arms|legs/.test(kind + hint)) { + return slots.find((s) => itemFitsSlot(item, s) || s.id === (item.slotHint || 'body')) || null; + } + return slots.find((s) => itemFitsSlot(item, s)) || null; +} + +/** + * @param {number} bagIndex + */ +function splitBagStack(bagIndex) { + const bag = loadBag(); + const s = bag.slots[bagIndex]; + if (!s || (s.qty || 1) < 2) return; + const half = Math.floor((s.qty || 1) / 2); + s.qty = (s.qty || 1) - half; + const free = bag.slots.findIndex((x) => !x); + if (free < 0) { + s.qty += half; // undo + return; + } + bag.slots[free] = { ...s, qty: half }; + saveBag(bag); +} + +/** + * Open context menu at pointer. + * @param {MouseEvent|PointerEvent} event + * @param {ItemContextTarget} target + * @param {ItemContextHandlers} handlers + */ +export function openItemContextMenu(event, target, handlers = {}) { + event.preventDefault(); + event.stopPropagation(); + + const el = ensureMenu(); + const actions = buildActions(target, handlers); + if (!actions.length) { + closeItemContextMenu(); + return; + } + + const title = target.item?.name || target.slotId || 'Slot'; + el.innerHTML = ` +
    ${escapeHtml(title)}
    +
      + ${actions + .map( + (a) => ` +
    • + +
    • ` + ) + .join('')} +
    + `; + + el.hidden = false; + _open = true; + + // Position — keep on screen + const x = event.clientX; + const y = event.clientY; + el.style.left = '0px'; + el.style.top = '0px'; + const rect = el.getBoundingClientRect(); + const left = Math.min(x, window.innerWidth - rect.width - 8); + const top = Math.min(y, window.innerHeight - rect.height - 8); + el.style.left = `${Math.max(8, left)}px`; + el.style.top = `${Math.max(8, top)}px`; + + el.querySelectorAll('[data-act]').forEach((btn) => { + btn.addEventListener('click', async (e) => { + e.stopPropagation(); + const id = btn.getAttribute('data-act'); + const act = actions.find((a) => a.id === id); + closeItemContextMenu(); + try { + await act?.run?.(); + handlers.onRefresh?.(); + } catch (err) { + handlers.onToast?.(err?.message || String(err)); + } + }); + }); +} + +/** + * Bind RMB on a slot element. + * @param {HTMLElement} el + * @param {() => ItemContextTarget} getTarget + * @param {ItemContextHandlers} handlers + */ +export function bindItemContextMenu(el, getTarget, handlers) { + if (!el) return; + el.addEventListener('contextmenu', (e) => { + openItemContextMenu(e, getTarget(), handlers); + }); +} + +/** + * Unequip paperdoll slot → bag. + * @param {string} slotId + */ +export function unequipPaperdollSlot(slotId) { + const map = loadEquipMap(); + const item = map[slotId]; + if (!item) return null; + bagAdd(item); + delete map[slotId]; + saveEquipMap(map); + return item; +} + +function escapeHtml(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(//g, '>'); +} diff --git a/src/ui/mainPanel.css b/src/ui/mainPanel.css new file mode 100644 index 0000000..27a65de --- /dev/null +++ b/src/ui/mainPanel.css @@ -0,0 +1,427 @@ +/** + * Main Panel — Warlords / Tactical Infinity equipment look + * + Sample-InventorySlotsSet chrome (public/ui/inventory/inventory-slots-set.png) + */ + +/* ── Main Panel chrome ───────────────────────────────────────── */ +.inv-panel--main { + width: min(920px, 96vw); + max-height: min(88vh, 820px); + background: + linear-gradient(165deg, rgba(12, 21, 32, 0.94) 0%, rgba(10, 16, 24, 0.96) 50%, rgba(13, 22, 34, 0.95) 100%), + var(--mp-panel-bg, none); + background-size: cover, auto; + border: 1px solid rgba(201, 149, 10, 0.28); + box-shadow: 0 0 0 1px #0a2035, 0 24px 64px rgba(0, 0, 0, 0.65), + inset 0 0 80px rgba(20, 80, 140, 0.08); +} + +.inv-panel--main .inv-panel__head { + background: linear-gradient(180deg, #152a40 0%, #0e1a28 100%); + border-bottom: 1px solid #1e4a6e; +} + +.inv-panel--main .inv-panel__head h2 { + color: #8ec8ff; + letter-spacing: 0.12em; + font-size: 0.85rem; + text-transform: uppercase; +} + +.inv-panel--main .inv-nav-tab.is-active { + background: #143a5c; + color: #b8e0ff; + border-color: #2a6a9a; +} + +/* ── Character paperdoll (TI style) ─────────────────────────── */ +.mp-doll { + display: grid; + grid-template-columns: 72px 1fr 72px; + gap: 10px; + align-items: stretch; + min-height: 340px; + padding: 12px; + background: + radial-gradient(ellipse 50% 60% at 50% 55%, rgba(40, 120, 200, 0.18), transparent 70%), + linear-gradient(180deg, #0a1420 0%, #081018 100%); + border: 1px solid #1a3a58; + border-radius: 8px; + position: relative; +} + +.mp-doll__col { + display: flex; + flex-direction: column; + gap: 8px; + justify-content: center; +} + +.mp-doll__col-title { + font-size: 0.58rem; + letter-spacing: 0.14em; + color: #4a8ab8; + text-align: center; + text-transform: uppercase; + margin-bottom: 2px; +} + +.mp-doll__center { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; +} + +.mp-doll__silhouette { + width: min(200px, 40vw); + height: min(300px, 48vh); + background: + radial-gradient(ellipse 40% 20% at 50% 92%, rgba(60, 160, 255, 0.35), transparent 70%), + url('/ui/inventory/equipment-reference.png') center 20% / contain no-repeat; + opacity: 0.95; + filter: drop-shadow(0 0 24px rgba(40, 140, 220, 0.35)); + pointer-events: none; +} + +.mp-doll__stats { + position: absolute; + bottom: 8px; + left: 50%; + transform: translateX(-50%); + font-size: 0.7rem; + color: #7ab0d8; + white-space: nowrap; + background: rgba(0, 0, 0, 0.45); + padding: 4px 10px; + border-radius: 4px; + border: 1px solid #1a3a55; +} + +/* Slot frame — Sample-InventorySlotsSet steel skin */ +.mp-slot { + width: 56px; + height: 56px; + border-radius: 4px; + border: 2px solid #2a4a6a; + background: + linear-gradient(145deg, #1a2a3a 0%, #0e1824 100%); + box-shadow: inset 0 0 12px rgba(0, 0, 0, 0.5), 0 0 0 1px #0a1828; + cursor: pointer; + position: relative; + display: flex; + align-items: center; + justify-content: center; + transition: border-color 0.12s, box-shadow 0.12s; + padding: 0; + margin: 0 auto; +} + +.mp-slot:hover, +.mp-slot.is-target { + border-color: #4ec8ff; + box-shadow: 0 0 12px rgba(60, 180, 255, 0.45), inset 0 0 8px rgba(40, 120, 200, 0.2); +} + +/* Dual weapon sets — active set (combat Q) highlighted */ +.mp-slot.is-active-set { + outline: 2px solid rgba(255, 180, 70, 0.85); + box-shadow: 0 0 10px rgba(255, 160, 40, 0.35); +} +.mp-slot.mp-slot--set0:not(.is-active-set), +.mp-slot.mp-slot--set1:not(.is-active-set) { + opacity: 0.78; +} +.mp-doll__loadout { + font-size: 11px; + opacity: 0.85; + margin-top: 6px; + text-align: center; + line-height: 1.35; + max-width: 14rem; +} +.mp-slot.is-filled { + border-color: #3a6a90; +} + +.mp-slot__icon { + width: 40px; + height: 40px; + object-fit: contain; + image-rendering: auto; + pointer-events: none; +} + +.mp-slot__label { + position: absolute; + bottom: -14px; + left: 50%; + transform: translateX(-50%); + font-size: 0.52rem; + color: #5a90b8; + white-space: nowrap; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.mp-slot__empty { + font-size: 1.1rem; + color: #1e3a55; + font-weight: 300; +} + +/* ── Inventory bag grid ─────────────────────────────────────── */ +.mp-bag { + margin-top: 14px; + padding: 12px; + background: #0a121c; + border: 1px solid #1a3550; + border-radius: 8px; +} + +.mp-bag__head { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; + gap: 8px; +} + +.mp-bag__head h3 { + margin: 0; + font-size: 0.75rem; + letter-spacing: 0.12em; + color: #7ab8e0; + text-transform: uppercase; +} + +.mp-bag__grid { + display: grid; + grid-template-columns: repeat(9, minmax(40px, 48px)); + gap: 5px; + justify-content: center; +} + +.mp-bag__util { + display: grid; + grid-template-columns: repeat(9, minmax(40px, 48px)); + gap: 5px; + justify-content: center; + margin-top: 8px; +} + +.mp-bag-slot { + width: 100%; + aspect-ratio: 1; + min-width: 40px; + min-height: 40px; + height: auto; + border-radius: 3px; + border: 2px solid #3a4a58; + background: linear-gradient(160deg, #2a3540 0%, #1a222c 100%); + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.4); + cursor: pointer; + position: relative; + display: flex; + align-items: center; + justify-content: center; + padding: 0; +} + +.mp-bag-slot:hover { + border-color: #6ab0e0; +} + +.mp-bag-slot.is-pick { + outline: 2px solid #ffcc44; + outline-offset: 1px; +} + +.mp-bag-slot .mp-slot__icon { + width: 36px; + height: 36px; +} + +.mp-bag-slot__qty { + position: absolute; + right: 3px; + bottom: 2px; + font-size: 0.62rem; + color: #e8f0ff; + text-shadow: 0 1px 2px #000; + font-weight: 600; +} + +/* Inventory picker overlay (LMB equip slot) */ +.mp-picker { + position: absolute; + z-index: 20; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: min(320px, 90%); + max-height: 60%; + overflow: auto; + background: #0e1a28; + border: 1px solid #2a6a9a; + border-radius: 8px; + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.7); + padding: 10px; +} + +.mp-picker h4 { + margin: 0 0 8px; + font-size: 0.75rem; + color: #9ad0ff; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.mp-picker__item { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + padding: 6px 8px; + border: 1px solid #1a3a55; + background: #0a1420; + color: #c8e0f0; + border-radius: 4px; + cursor: pointer; + margin-bottom: 4px; + text-align: left; +} + +.mp-picker__item:hover { + border-color: #4aa0e0; + background: #102438; +} + +.mp-picker__item img { + width: 32px; + height: 32px; + object-fit: contain; +} + +.mp-picker__empty { + font-size: 0.75rem; + color: #6a90a8; + padding: 8px; +} + +/* ── Profession skill trees ─────────────────────────────────── */ +.mp-prof { + display: flex; + flex-direction: column; + gap: 12px; +} + +.mp-prof__tabs { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.mp-prof__tab { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 10px; + border-radius: 6px; + border: 1px solid #1e3a55; + background: #0c1824; + color: #9ab8d0; + cursor: pointer; + font-size: 0.72rem; +} + +.mp-prof__tab img { + width: 20px; + height: 20px; +} + +.mp-prof__tab.is-on { + border-color: #3a90d0; + background: #143048; + color: #d0ecff; +} + +.mp-prof__tree { + display: flex; + flex-direction: column; + gap: 8px; + padding: 12px; + border: 1px solid #1a3550; + border-radius: 8px; + background: #0a121c; +} + +.mp-prof__node { + display: grid; + grid-template-columns: 28px 1fr auto; + gap: 8px; + align-items: center; + padding: 8px 10px; + border-radius: 6px; + border: 1px solid #1a3048; + background: #0e1a28; +} + +.mp-prof__node.is-unlocked { + border-color: #2a7a4a; + background: #0e2018; +} + +.mp-prof__node .tier { + font-size: 0.65rem; + color: #6a90b0; + font-weight: 700; +} + +.mp-prof__node button { + font-size: 0.65rem; + padding: 4px 8px; + border-radius: 4px; + border: 1px solid #2a6a9a; + background: #143a5c; + color: #c8e8ff; + cursor: pointer; +} + +.mp-prof__node button:disabled { + opacity: 0.4; + cursor: default; +} + +.mp-link-craft { + display: inline-flex; + align-items: center; + gap: 6px; + margin-top: 8px; + font-size: 0.75rem; + color: #6ecbff; +} + +/* Admin slot matrix */ +.mp-admin-slots { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.mp-admin-slots label { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 0.7rem; + color: #8ab0c8; +} + +.mp-admin-slots input { + background: #0a1420; + border: 1px solid #1e3a55; + color: #d0e8f8; + padding: 4px 6px; + border-radius: 4px; +} diff --git a/src/ui/mainPanelSlots.js b/src/ui/mainPanelSlots.js new file mode 100644 index 0000000..a759b86 --- /dev/null +++ b/src/ui/mainPanelSlots.js @@ -0,0 +1,500 @@ +/** + * Main Panel equipment + inventory slot SSOT (Casting production tester). + * + * Look SSOT: Tactical Infinity /equipment (dark paperdoll + dual columns) + * Inventory slot chrome: public/ui/inventory/inventory-slots-set.png + * Logic patterns: Player-Inventory-System (slots, equip, bag, LMB pick/place) + * + * Wire: InventoryPanel · AdminHub · grudge6 mesh_ids + hand attach + */ + +/** @typedef {'mesh'|'hand'|'back'|'hud'|'mount'} SlotKind */ + +/** + * Paperdoll columns — Warlords combat loadout. + * + * No belt / amulet / ring / cloak. + * - Left: armour mesh_ids + * - Right: **Weapon set A** (main+off) · **Weapon set B** (main2+off2) · back · relic + * Combat **Q** swaps active set A ↔ B (skills + loco + mesh). + * + * @see combat/equippedWeaponRuntime.js dual loadout + */ +export const PAPERDOLL_LEFT = Object.freeze([ + { id: 'head', label: 'Head', kind: 'mesh', meshSlot: 'head', accepts: ['armour', 'head'] }, + { id: 'body', label: 'Body', kind: 'mesh', meshSlot: 'body', accepts: ['armour', 'body'] }, + { id: 'arms', label: 'Arms', kind: 'mesh', meshSlot: 'arms', accepts: ['armour', 'arms'] }, + { id: 'legs', label: 'Legs', kind: 'mesh', meshSlot: 'legs', accepts: ['armour', 'legs'] }, + { id: 'shoulders', label: 'Shoulders', kind: 'mesh', meshSlot: 'shoulders', accepts: ['armour'] }, + /** Was belt/amulet area — relic HUD passive */ + { id: 'relic', label: 'Relic', kind: 'hud', meshSlot: null, accepts: ['relic'] } +]); + +export const PAPERDOLL_RIGHT = Object.freeze([ + /** Weapon set A (active when loadoutIndex 0) */ + { + id: 'mainHand', + label: 'Weapon 1', + kind: 'hand', + meshSlot: 'sword', + accepts: ['weapon', 'tool'], + weaponSet: 0, + hand: 'main' + }, + { + id: 'offHand', + label: 'Off 1', + kind: 'hand', + meshSlot: 'shield', + accepts: ['weapon', 'shield', 'tome'], + weaponSet: 0, + hand: 'off' + }, + /** Weapon set B — was ring/cloak · Q combat swap target */ + { + id: 'weapon2', + label: 'Weapon 2', + kind: 'hand', + meshSlot: 'sword', + accepts: ['weapon', 'tool'], + weaponSet: 1, + hand: 'main' + }, + { + id: 'offHand2', + label: 'Off 2', + kind: 'hand', + meshSlot: 'shield', + accepts: ['weapon', 'shield', 'tome'], + weaponSet: 1, + hand: 'off' + }, + /** Back mobility (not cloak) — windsurf / wings / shell */ + { id: 'back', label: 'Back', kind: 'back', meshSlot: 'back', accepts: ['back', 'utility'] }, + { id: 'mount', label: 'Mount', kind: 'mount', meshSlot: null, accepts: ['mount'] } +]); + +export const ALL_PAPERDOLL_SLOTS = Object.freeze([...PAPERDOLL_LEFT, ...PAPERDOLL_RIGHT]); + +/** Paperdoll ids that hold weapon set A / B main hands */ +export const WEAPON_SET_MAIN_SLOTS = Object.freeze(['mainHand', 'weapon2']); +export const WEAPON_SET_OFF_SLOTS = Object.freeze(['offHand', 'offHand2']); + +/** + * Bag grid — inventory.png Warlords layout: 9×3 main + hotbar row. + * (was 4×3 + util; expanded to match full inventory shell.) + */ +export const BAG_LAYOUT = Object.freeze({ + mainCols: 9, + mainRows: 3, + utilSlots: 9, + capacity: 9 * 3 + 9, + /** CSS theme key into inventory-slots-set.png (skin index 0–9) */ + defaultSkin: 'steel', +}); + +export const BAG_SKINS = Object.freeze([ + { id: 'wood', label: 'Wood', row: 0, col: 0 }, + { id: 'steel', label: 'Steel', row: 0, col: 1 }, + { id: 'dark', label: 'Dark', row: 0, col: 2 }, + { id: 'copper', label: 'Copper', row: 0, col: 3 }, + { id: 'silver', label: 'Silver', row: 0, col: 4 }, + { id: 'orange', label: 'Orange', row: 1, col: 0 }, + { id: 'blue', label: 'Blue', row: 1, col: 1 }, + { id: 'purple', label: 'Purple', row: 1, col: 2 }, + { id: 'magenta', label: 'Magenta', row: 1, col: 3 }, + { id: 'green', label: 'Green', row: 1, col: 4 }, +]); + +/** WCS profession skill trees (crafting SSOT surface) + gathering fisher */ +export const PROFESSION_TREES = Object.freeze([ + { + id: 'fisher', + label: 'Fishing', + kind: 'gathering', + /** Live lab tree: src/fishing/fishingSkillTree.js (not stub nodes) */ + liveTree: true, + icon: 'https://grudgewarlords.com/craft/crafting-icons/chef.png', + nodes: [ + { id: 'fish_init', name: "Fisher's Initiation", tier: 1, desc: 'Shore fishing unlock' }, + { id: 'fish_qty_1', name: 'Double Hook', tier: 1, desc: '+1 catch qty' }, + { id: 'fish_bite_1', name: 'Patient Angler', tier: 1, desc: '+15% bite rate' }, + { id: 'nautical_1', name: 'Sea Legs I', tier: 2, desc: '+5% nautical speed' }, + { id: 'fish_zone_1', name: 'Wider Reel', tier: 2, desc: '+8% fight zone' }, + { id: 'fish_line_1', name: 'Braided Line', tier: 2, desc: '+10% line' }, + { id: 'fish_qty_2', name: 'Net Haul', tier: 3, desc: '+1 qty stack' }, + { id: 'nautical_2', name: 'Sea Legs II', tier: 3, desc: '+8% nautical' }, + { id: 'fish_rare_1', name: 'Lucky Tide', tier: 3, desc: '+20% rare+' }, + { id: 'fish_meal_link', name: "Fisher's Kitchen", tier: 4, desc: 'Blue meal crafts' }, + { id: 'nautical_3', name: 'Tide Runner', tier: 4, desc: '+10% nautical freeride' }, + { id: 'fish_master', name: 'Grandmaster Angler', tier: 5, desc: 'Qty · zone · legendary' }, + ], + }, + { + id: 'miner', + label: 'Miner', + icon: 'https://grudgewarlords.com/craft/crafting-icons/miner.png', + nodes: [ + { id: 'm1', name: 'Stone Sense', tier: 1, desc: 'Find ore nodes' }, + { id: 'm2', name: 'Copper Vein', tier: 2, desc: 'Unlock copper' }, + { id: 'm3', name: 'Iron Vein', tier: 3, desc: 'Unlock iron' }, + { id: 'm4', name: 'Deep Core', tier: 4, desc: 'Rare ore chance' }, + { id: 'm5', name: 'Master Miner', tier: 5, desc: 'Double chunk strip' }, + ], + }, + { + id: 'forester', + label: 'Forester', + icon: 'https://grudgewarlords.com/craft/crafting-icons/forester.png', + nodes: [ + { id: 'f1', name: 'Timber Eye', tier: 1, desc: 'Find trees' }, + { id: 'f2', name: 'Hardwood', tier: 2, desc: 'Better wood' }, + { id: 'f3', name: 'Resin Tap', tier: 3, desc: 'Craft resin mats' }, + { id: 'f4', name: 'Grove Ward', tier: 4, desc: 'Faster respawn clear' }, + { id: 'f5', name: 'Master Forester', tier: 5, desc: 'Extra stick drop' }, + ], + }, + { + id: 'chef', + label: 'Chef', + icon: 'https://grudgewarlords.com/craft/crafting-icons/chef.png', + nodes: [ + { id: 'c1', name: 'Campfire', tier: 1, desc: 'Cook basic food' }, + { id: 'c2', name: 'Stew', tier: 2, desc: 'Buff meals (RGB SWG)' }, + { id: 'c3', name: 'Feast', tier: 3, desc: 'Party food' }, + { id: 'c4', name: 'Preserves', tier: 4, desc: 'Long buffs' }, + { id: 'c5', name: 'Master Chef', tier: 5, desc: 'Double cook yield' }, + ], + }, + { + id: 'engineer', + label: 'Engineer', + icon: 'https://grudgewarlords.com/craft/crafting-icons/engineer.png', + nodes: [ + { id: 'e1', name: 'Workbench', tier: 1, desc: 'Basic tools' }, + { id: 'e2', name: 'Gearworks', tier: 2, desc: 'Mechanisms' }, + { id: 'e3', name: 'Siege Parts', tier: 3, desc: 'Catapult parts' }, + { id: 'e4', name: 'Automata', tier: 4, desc: 'Auto-harvest kit' }, + { id: 'e5', name: 'Master Engineer', tier: 5, desc: 'Field repairs' }, + ], + }, + { + id: 'mystic', + label: 'Mystic', + icon: 'https://grudgewarlords.com/craft/crafting-icons/mystic.png', + nodes: [ + { id: 'y1', name: 'Focus Herb', tier: 1, desc: 'Gather reagents' }, + { id: 'y2', name: 'Ink', tier: 2, desc: 'Inscription mats' }, + { id: 'y3', name: 'Charm', tier: 3, desc: 'Relic craft' }, + { id: 'y4', name: 'Ward Stone', tier: 4, desc: 'Defense craft' }, + { id: 'y5', name: 'Master Mystic', tier: 5, desc: 'Spell catalyst' }, + ], + }, +]); + +const LS_BAG = 'casting.mainPanel.bag.v2'; +const LS_EQUIP = 'casting.mainPanel.equip.v1'; +const LS_PROF = 'casting.mainPanel.prof.v1'; +const LS_SKIN = 'casting.mainPanel.bagSkin.v1'; +const LS_SLOT_ADMIN = 'casting.mainPanel.slotAdmin.v1'; + +function safeParse(raw, fallback) { + try { + return raw ? JSON.parse(raw) : fallback; + } catch { + return fallback; + } +} + +export function loadBagSkin() { + return localStorage.getItem(LS_SKIN) || BAG_LAYOUT.defaultSkin; +} + +export function saveBagSkin(id) { + localStorage.setItem(LS_SKIN, id); +} + +/** @returns {{ slots: (object|null)[] }} */ +export function loadBag() { + const data = safeParse(localStorage.getItem(LS_BAG), null); + if (data?.slots?.length === BAG_LAYOUT.capacity) return data; + return { slots: Array(BAG_LAYOUT.capacity).fill(null) }; +} + +export function saveBag(bag) { + localStorage.setItem(LS_BAG, JSON.stringify(bag)); +} + +/** @returns {Record} */ +export function loadEquipMap() { + return safeParse(localStorage.getItem(LS_EQUIP), {}); +} + +export function saveEquipMap(map) { + localStorage.setItem(LS_EQUIP, JSON.stringify(map || {})); +} + +/** @returns {Record} */ +export function loadProfessionProgress() { + const base = {}; + for (const t of PROFESSION_TREES) { + base[t.id] = { level: 1, xp: 0, unlocked: [t.nodes[0]?.id].filter(Boolean) }; + } + return { ...base, ...safeParse(localStorage.getItem(LS_PROF), {}) }; +} + +export function saveProfessionProgress(map) { + localStorage.setItem(LS_PROF, JSON.stringify(map)); +} + +export function unlockProfessionNode(profId, nodeId) { + const map = loadProfessionProgress(); + const p = map[profId] || { level: 1, xp: 0, unlocked: [] }; + if (!p.unlocked.includes(nodeId)) p.unlocked.push(nodeId); + p.level = Math.max(p.level, p.unlocked.length); + map[profId] = p; + saveProfessionProgress(map); + return p; +} + +/** + * Admin overrides for slot accept filters / labels. + * @returns {Record>} + */ +export function loadSlotAdminOverrides() { + return safeParse(localStorage.getItem(LS_SLOT_ADMIN), {}); +} + +export function saveSlotAdminOverrides(map) { + localStorage.setItem(LS_SLOT_ADMIN, JSON.stringify(map || {})); +} + +export function getPaperdollSlots() { + const ovr = loadSlotAdminOverrides(); + return ALL_PAPERDOLL_SLOTS.map((s) => { + const o = ovr[s.id] || {}; + if (o.enabled === false) return null; + return { + ...s, + label: o.label || s.label, + accepts: o.accepts || s.accepts, + }; + }).filter(Boolean); +} + +/** + * Add item to first free bag slot (icons resolved via iconResolve). + * @param {{ id: string, name: string, icon?: string, iconUrl?: string, kind?: string, qty?: number, slotHint?: string }} item + */ +export function bagAdd(item) { + const bag = loadBag(); + const qty = item.qty || 1; + const normalized = enrichBagSlotIcon({ ...item, qty }); + // Stack same id + for (let i = 0; i < bag.slots.length; i++) { + const s = bag.slots[i]; + if (s && s.id === item.id) { + s.qty = (s.qty || 1) + qty; + if (!s.icon && normalized.icon) { + s.icon = normalized.icon; + s.iconUrl = normalized.iconUrl; + } + saveBag(bag); + return { ok: true, index: i, stacked: true }; + } + } + const free = bag.slots.findIndex((s) => !s); + if (free < 0) return { ok: false, full: true }; + bag.slots[free] = normalized; + saveBag(bag); + return { ok: true, index: free }; +} + +export function bagRemoveAt(index, qty = 1) { + const bag = loadBag(); + const s = bag.slots[index]; + if (!s) return null; + s.qty = (s.qty || 1) - qty; + if (s.qty <= 0) bag.slots[index] = null; + saveBag(bag); + return s; +} + +/** Seed demo bag if empty (production lab) — icons from CDN + lab minerals. */ +export function ensureDemoBag() { + const bag = loadBag(); + if (bag.slots.some(Boolean)) { + // Repair missing icons on existing bag + let dirty = false; + for (let i = 0; i < bag.slots.length; i++) { + const s = bag.slots[i]; + if (s && !s.icon && !s.iconUrl) { + bag.slots[i] = enrichBagSlotIcon(s); + dirty = true; + } + } + if (dirty) saveBag(bag); + return bag; + } + const demos = [ + { + id: 't0-sword', + name: 'Training Sword', + kind: 'weapon', + slotHint: 'mainHand', + qty: 1 + }, + { + id: 't0-wand', + name: 'Apprentice Wand', + kind: 'weapon', + slotHint: 'mainHand', + qty: 1 + }, + { + id: 't0-tool', + name: 'Field Pick', + kind: 'tool', + slotHint: 'mainHand', + qty: 1 + }, + { id: 'mat-wood', name: 'Wood', kind: 'mat', qty: 12 }, + { id: 'mat-stone', name: 'Stone', kind: 'mat', qty: 8 }, + { id: 'mat-ore-chunk', name: 'Ore chunk', kind: 'mat', qty: 3 }, + { + id: 'kit_body_a', + name: 'Body kit A', + kind: 'armour', + slotHint: 'body', + qty: 1 + }, + { + id: 't0-gun', + name: 'Flintlock Pistol', + kind: 'weapon', + slotHint: 'mainHand', + qty: 1 + }, + { + id: 'windsurf', + name: 'Windsurf', + kind: 'back', + slotHint: 'back', + qty: 1 + }, + { + id: 'holy_wings', + name: 'Holy Wings', + kind: 'back', + slotHint: 'back', + qty: 1 + }, + { + id: 'traveler_wings', + name: "Traveler's Wings", + kind: 'back', + slotHint: 'back', + qty: 1 + }, + { + id: 'shark_fin', + name: 'Shark Fin', + kind: 'back', + slotHint: 'back', + qty: 1, + blurb: '2× swim · no shark aggro · breathe underwater' + } + ]; + for (const d of demos) bagAdd(enrichBagSlotIcon(d)); + return loadBag(); +} + +/** + * Ensure icon fields on a bag slot (used by ensureDemoBag + refresh). + * @param {object} item + */ +export function enrichBagSlotIcon(item) { + if (!item) return item; + // Inline map (keep mainPanelSlots free of hard import cycles with iconResolve) + const CDN = 'https://assets.grudge-studio.com/icons/496_rpg_icons'; + const LAB_STONE = './icons/dev-island/minerals/FD_Minerals_Stones.png'; + const byId = { + 't0-sword': `${CDN}/W_Sword001.png`, + 't0-wand': `${CDN}/W_Wand001.png`, + 't0-gun': `${CDN}/W_Gun001.png`, + windsurf: `${CDN}/I_Water.png`, + holy_wings: `${CDN}/S_Light_01.png`, + traveler_wings: `${CDN}/S_Buff01.png`, + 't0-tool': `${CDN}/W_PickAxe001.png`, + 'mat-wood': `${CDN}/I_Wood01.png`, + t0_wood: `${CDN}/I_Wood01.png`, + 'mat-stone': LAB_STONE, + t0_stone: LAB_STONE, + 'mat-ore-chunk': `${CDN}/I_Coal.png`, + kit_body_a: `${CDN}/A_Armour01.png` + }; + const icon = + item.iconUrl || + item.icon || + byId[item.id] || + (String(item.kind || '').includes('weapon') + ? `${CDN}/W_Sword001.png` + : String(item.kind || '').includes('tool') + ? `${CDN}/W_PickAxe001.png` + : String(item.kind || '').includes('armour') + ? `${CDN}/A_Armour01.png` + : `${CDN}/I_Bag.png`); + return { ...item, icon, iconUrl: icon }; +} + +/** + * Re-resolve icons for every bag slot (after catalog load). + */ +export function reenrichAllBagIcons() { + const bag = loadBag(); + let n = 0; + for (let i = 0; i < bag.slots.length; i++) { + if (!bag.slots[i]) continue; + const before = bag.slots[i].icon || bag.slots[i].iconUrl; + bag.slots[i] = enrichBagSlotIcon(bag.slots[i]); + if ((bag.slots[i].icon || bag.slots[i].iconUrl) !== before) n++; + } + saveBag(bag); + return n; +} + +export function itemFitsSlot(item, slotDef) { + if (!item || !slotDef) return false; + const kind = String(item.kind || item.slotType || item.category || '').toLowerCase(); + const hint = String(item.slotHint || item.equipSlot || item.slot || '').toLowerCase(); + const id = String(item.id || '').toLowerCase(); + if (slotDef.accepts?.some((a) => kind.includes(a) || hint.includes(a) || a === kind)) return true; + if (hint === slotDef.id.toLowerCase() || hint === slotDef.meshSlot) return true; + // Catalog categories — dual weapon sets (Weapon 1/2 · Off 1/2) + if ( + (slotDef.id === 'mainHand' || slotDef.id === 'weapon2') && + /weapon|tool|t0|gun|sword|wand|staff|bow|axe|rifle|pistol|dagger|spear|hammer/.test( + kind + id + ) + ) { + return true; + } + if ( + (slotDef.id === 'offHand' || slotDef.id === 'offHand2') && + /shield|tome|offhand|off|weapon|dagger/.test(kind + hint + id) + ) { + return true; + } + if (slotDef.id === 'back' && /back|cape|wing|windsurf|shell|glider|shark|fin/.test(kind + id + hint)) { + return true; + } + if (slotDef.id === 'relic' && /relic|class/.test(kind)) return true; + if (slotDef.id === 'mount' && /mount/.test(kind)) return true; + if ( + ['head', 'body', 'arms', 'legs', 'shoulders'].includes(slotDef.id) && + (/armour|armor/.test(kind) || hint === slotDef.id) + ) { + return true; + } + return false; +} diff --git a/src/ui/modeRadial.css b/src/ui/modeRadial.css new file mode 100644 index 0000000..6964e45 --- /dev/null +++ b/src/ui/modeRadial.css @@ -0,0 +1,172 @@ +.mode-radial { + position: fixed; + inset: 0; + z-index: 90; + display: grid; + place-items: center; + pointer-events: none; + background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0.35), transparent 55%); +} + +.mode-radial[hidden] { + display: none !important; +} + +.mode-radial-ring { + position: relative; + width: min(280px, 70vw); + height: min(280px, 70vw); + border-radius: 50%; + border: 2px solid rgba(200, 180, 140, 0.35); + background: rgba(8, 10, 14, 0.72); + box-shadow: 0 0 40px rgba(0, 0, 0, 0.55), inset 0 0 60px rgba(0, 0, 0, 0.4); +} + +.mode-radial-wedge { + position: absolute; + left: 12%; + right: 12%; + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + color: #c8d0dc; + opacity: 0.65; + transition: opacity 0.12s, color 0.12s, transform 0.12s; +} + +.mode-radial-up { + top: 10%; +} +.mode-radial-down { + bottom: 10%; +} + +.mode-radial-wedge.hot, +.mode-radial-wedge.active { + opacity: 1; + color: #fff; + transform: scale(1.06); +} +.mode-radial-up.hot { + color: #ff9a9a; + text-shadow: 0 0 12px rgba(255, 100, 100, 0.5); +} +.mode-radial-down.hot { + color: #9ef0b8; + text-shadow: 0 0 12px rgba(80, 220, 120, 0.45); +} + +.mode-radial-icon { + font-size: 28px; + line-height: 1; +} +.mode-radial-label { + font-size: 13px; + letter-spacing: 0.14em; + text-transform: uppercase; + font-weight: 700; +} +.mode-radial-hint { + font-size: 10px; + opacity: 0.7; +} + +.mode-radial-core { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + width: 88px; + height: 88px; + border-radius: 50%; + background: linear-gradient(160deg, #1a2430, #0a1018); + border: 2px solid #3a5a78; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; +} +.mode-radial-core-title { + font-size: 10px; + letter-spacing: 0.12em; + color: #8ab0d0; + text-transform: uppercase; +} +.mode-radial-core-sub { + font-size: 12px; + font-weight: 700; + color: #e8f0ff; +} + +.mode-radial-foot { + position: absolute; + bottom: 12%; + left: 50%; + transform: translateX(-50%); + margin: 0; + font-size: 11px; + color: #a0a8b8; + text-align: center; + white-space: nowrap; +} + +/* Tool radial */ +.tool-radial-ring { + position: relative; + width: min(320px, 78vw); + height: min(320px, 78vw); + border-radius: 50%; + border: 2px solid rgba(160, 140, 100, 0.4); + background: rgba(12, 10, 8, 0.78); +} + +.tool-radial-wedge { + position: absolute; + left: 50%; + top: 50%; + width: 50%; + height: 42px; + margin-top: -21px; + transform-origin: left center; + transform: rotate(var(--rot)) translateX(42px); + display: flex; + align-items: center; + pointer-events: none; +} + +.tool-radial-inner { + transform: rotate(var(--irot)); + display: flex; + flex-direction: column; + align-items: center; + gap: 0; + min-width: 56px; + padding: 4px 6px; + border-radius: 8px; + background: rgba(30, 22, 14, 0.75); + border: 1px solid #5c4030; + color: #d8c8a8; + opacity: 0.7; +} +.tool-radial-wedge.hot .tool-radial-inner { + opacity: 1; + border-color: #e0c080; + color: #fff8e8; + box-shadow: 0 0 14px rgba(200, 160, 80, 0.45); +} +.tool-radial-inner b { + font-size: 18px; + line-height: 1; + font-weight: 400; +} +.tool-radial-inner small { + font-size: 9px; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.tool-radial-core { + border-color: #6b4a32; +} diff --git a/src/ui/quickActions.js b/src/ui/quickActions.js new file mode 100644 index 0000000..da86d04 --- /dev/null +++ b/src/ui/quickActions.js @@ -0,0 +1,95 @@ +/** + * Danger Room / threejs-rapier quick-action catalog — 6+6 wing slots. + * SSOT mirror: gameopen/artifacts/animator/src/hud/quickActions.ts + * + * Keybind SSOT (combat session): + * LMB primary · F best-next-action (pickup / harvest / standard attack) + * X dodge · C parry · E block · R heavy + * J heal · H bomb · V kick · Q mode (equip↔combat in casting lab) + */ + +/** @typedef {'primary'|'fskill'|'interact'|'sig1'|'sig2'|'sig3'|'sig4'|'heavy'|'parry'|'block'|'dodge'|'kick'|'bomb'|'heal'|'mode'} QuickActionId */ + +/** + * @typedef {object} QuickAction + * @property {QuickActionId} id + * @property {string} label + * @property {string} glyph short UI glyph (no icon pack required) + * @property {string} key + * @property {'action'|'skill'|'item'|'meta'} kind + */ + +/** @type {Record} */ +export const QUICK_ACTIONS = { + primary: { id: 'primary', label: 'Attack', glyph: '⚔', key: 'LMB', kind: 'action' }, + /** F key — context: pickup → harvest → standard attack (residual on attack) */ + interact: { + id: 'interact', + label: 'Interact', + glyph: '◎', + key: 'F', + kind: 'action' + }, + /** @deprecated use interact — kept for bar slot id compatibility */ + fskill: { id: 'fskill', label: 'Interact', glyph: '◎', key: 'F', kind: 'action' }, + sig1: { id: 'sig1', label: 'Signature 1', glyph: '1', key: '1', kind: 'skill' }, + sig2: { id: 'sig2', label: 'Signature 2', glyph: '2', key: '2', kind: 'skill' }, + sig3: { id: 'sig3', label: 'Signature 3', glyph: '3', key: '3', kind: 'skill' }, + sig4: { id: 'sig4', label: 'Signature 4', glyph: '4', key: '4', kind: 'skill' }, + heavy: { id: 'heavy', label: 'Heavy', glyph: '◆', key: 'R', kind: 'action' }, + parry: { id: 'parry', label: 'Parry', glyph: '⛨', key: 'C', kind: 'action' }, + block: { id: 'block', label: 'Block', glyph: '▣', key: 'E', kind: 'action' }, + dodge: { id: 'dodge', label: 'Back dodge', glyph: '⟳', key: 'X', kind: 'action' }, + kick: { id: 'kick', label: 'Kick', glyph: '▹', key: 'V', kind: 'action' }, + bomb: { id: 'bomb', label: 'Bomb', glyph: '◉', key: 'H', kind: 'item' }, + heal: { id: 'heal', label: 'Heal', glyph: '✚', key: 'J', kind: 'item' }, + mode: { id: 'mode', label: 'Mode', glyph: '◎', key: 'Q', kind: 'meta' } +}; + +export const QUICK_SLOTS_PER_SIDE = 6; +export const QUICK_SLOT_COUNT = 12; + +/** + * Default 6+6 (threejs-rapier tight HUD): + * Left = offense + skills (LMB F 1 2 3 4) + * Right = defense + utility (X C R V J H) — E block is key-only (not wing) + * @returns {(QuickActionId|null)[]} + */ +export function defaultQuickSlots() { + return [ + 'primary', + 'interact', + 'sig1', + 'sig2', + 'sig3', + 'sig4', + 'dodge', + 'parry', + 'heavy', + 'kick', + 'heal', + 'bomb' + ]; +} + +export function leftWingSlots(slots = defaultQuickSlots()) { + return slots.slice(0, QUICK_SLOTS_PER_SIDE); +} + +export function rightWingSlots(slots = defaultQuickSlots()) { + return slots.slice(QUICK_SLOTS_PER_SIDE, QUICK_SLOT_COUNT); +} + +export const COMBAT_KEY_LEGEND = + 'AA/DD/WW dodge · X back · C parry · E block · F interact · 1–4 · Space jump'; + +export const COMBAT_KEY_CHIPS = Object.freeze([ + 'AA/DD/WW: Dodge', + 'X: Back dodge', + 'C: Parry', + 'E: Block', + 'F: Interact / attack', + 'LMB: Staff path', + '1–4: Skills', + 'Space: Jump' +]); diff --git a/src/ui/showcase.css b/src/ui/showcase.css new file mode 100644 index 0000000..39c7dd1 --- /dev/null +++ b/src/ui/showcase.css @@ -0,0 +1,253 @@ +/* Showcase panel — race / weapon / anim / master skill bind */ + +.showcase-panel { + position: fixed; + top: 56px; + right: 16px; + width: min(400px, calc(100vw - 24px)); + max-height: calc(100vh - 100px); + z-index: 42; + display: flex; + flex-direction: column; + pointer-events: auto; + background: var(--ui-bg-solid); + border: 1px solid var(--ui-border); + border-radius: var(--ui-radius); + box-shadow: var(--ui-shadow); + backdrop-filter: blur(12px); + color: var(--ui-text); + overflow: hidden; +} + +.showcase-panel[hidden] { + display: none !important; +} + +.showcase-panel__head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + padding: 14px 16px 8px; + border-bottom: 1px solid var(--ui-border); +} + +.showcase-panel__head h2 { + margin: 0; + font-size: 15px; + font-weight: 600; +} + +.showcase-panel__sub { + margin: 4px 0 0; + font-size: 11px; + color: var(--ui-text-dim); +} + +.showcase-panel__close { + border: 0; + background: transparent; + color: var(--ui-text-dim); + font-size: 22px; + cursor: pointer; + line-height: 1; +} + +.showcase-panel__body { + overflow: auto; + padding: 10px 14px 14px; + flex: 1; +} + +.showcase-panel__foot { + padding: 8px 12px; + border-top: 1px solid var(--ui-border); + font-size: 11px; + color: var(--ui-text-dim); +} + +.showcase-panel__foot a { + color: var(--ui-accent); + text-decoration: none; +} + +.showcase-block { + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.showcase-block h3 { + margin: 0 0 8px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--ui-accent); +} + +.showcase-row { + display: flex; + flex-direction: column; + gap: 4px; + margin-bottom: 8px; + font-size: 12px; + color: var(--ui-text-dim); +} + +.showcase-row select, +.showcase-row input { + width: 100%; + background: rgba(0, 0, 0, 0.35); + color: var(--ui-text); + border: 1px solid var(--ui-border); + border-radius: 8px; + padding: 8px; + font-size: 12px; +} + +.showcase-hint { + font-size: 11px; + color: var(--ui-text-dim); + margin: 0 0 8px; + line-height: 1.4; +} + +.showcase-btn-row { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 8px 0; +} + +.showcase-btn { + border: 1px solid var(--ui-border); + background: rgba(127, 214, 255, 0.14); + color: var(--ui-text); + border-radius: 8px; + padding: 7px 10px; + font-size: 11px; + font-weight: 600; + cursor: pointer; +} + +.showcase-btn--ghost { + background: rgba(255, 255, 255, 0.04); +} + +.showcase-btn--tiny { + padding: 2px 8px; + font-size: 12px; +} + +.showcase-btn:hover { + border-color: var(--ui-accent); +} + +.showcase-clip-grid { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.showcase-clip { + border: 1px solid var(--ui-border); + background: rgba(255, 255, 255, 0.05); + color: var(--ui-text); + border-radius: 6px; + padding: 5px 8px; + font-size: 10px; + cursor: pointer; +} + +.showcase-skill-list { + max-height: 220px; + overflow: auto; + display: flex; + flex-direction: column; + gap: 4px; + margin: 8px 0; +} + +.showcase-skill { + display: flex; + align-items: center; + gap: 8px; + border: 1px solid var(--ui-border); + background: rgba(0, 0, 0, 0.28); + color: var(--ui-text); + border-radius: 8px; + padding: 6px 8px; + cursor: pointer; + text-align: left; + font-size: 11px; +} + +.showcase-skill.is-selected { + border-color: var(--ui-accent); + background: rgba(127, 214, 255, 0.12); +} + +.showcase-skill img { + border-radius: 4px; + object-fit: cover; + background: rgba(0, 0, 0, 0.4); + flex-shrink: 0; +} + +.showcase-skill__glyph { + width: 28px; + height: 28px; + display: grid; + place-items: center; + flex-shrink: 0; +} + +.showcase-skill__meta { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.showcase-skill__meta b { + font-size: 12px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.showcase-skill__meta i { + font-style: normal; + font-size: 10px; + color: var(--ui-text-dim); +} + +.showcase-binds { + display: flex; + flex-direction: column; + gap: 4px; + margin-top: 8px; +} + +.showcase-bind-row { + display: grid; + grid-template-columns: 28px 1fr 28px; + gap: 8px; + align-items: center; + font-size: 11px; + padding: 4px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.showcase-bind-key { + font-weight: 700; + color: var(--ui-accent); +} + +.showcase-bind-name { + color: var(--ui-text-dim); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/src/ui/styles.css b/src/ui/styles.css index e6a25bb..29f1bdf 100644 --- a/src/ui/styles.css +++ b/src/ui/styles.css @@ -41,6 +41,145 @@ body { cursor: crosshair; } +/* Focus mode: OS cursor removed — screen crosshair is the only reticle */ +body.focus-aim, +body.focus-aim canvas, +body.focus-aim .hud { + cursor: none !important; +} + +/* Screen-center reticle — GRUDOX animator parity (dot + 4 ticks + bloom gap + range) */ +.hud__crosshair { + --xh-spread: 0; + --ch-gap: 0px; + --xh-size: 48px; + position: fixed; + left: 50%; + top: 50%; + width: var(--xh-size); + height: var(--xh-size); + margin: calc(var(--xh-size) / -2) 0 0 calc(var(--xh-size) / -2); + z-index: 26; + pointer-events: none; + opacity: 0; + transition: opacity 0.12s ease; +} +.hud__crosshair.is-visible { + opacity: 1; +} +.hud__crosshair.is-focus .hud__crosshair-ring { + border-color: rgba(180, 230, 255, 0.55); +} +.hud__crosshair.is-softlock .hud__crosshair-ring { + border-color: rgba(255, 120, 90, 0.75); + box-shadow: 0 0 12px rgba(255, 90, 60, 0.3); +} +.hud__crosshair.is-softlock .hud__crosshair-dot { + background: #ffb0a0; + box-shadow: 0 0 8px rgba(255, 120, 90, 0.95); +} +.hud__crosshair.is-fire .hud__crosshair-ring { + transform: scale(0.88); + border-color: #4ecdc4; + transition: transform 0.08s ease, border-color 0.08s ease; +} +.hud__crosshair-dot { + position: absolute; + left: 50%; + top: 50%; + width: 3px; + height: 3px; + margin: -1.5px 0 0 -1.5px; + border-radius: 50%; + background: #e8f4ff; + box-shadow: 0 0 6px rgba(127, 214, 255, 0.9); + z-index: 2; +} +/* Soft outer ring (animator uses range ring more than full circle) */ +.hud__crosshair-ring { + position: absolute; + inset: 10px; + border: 1px solid rgba(127, 214, 255, 0.28); + border-radius: 50%; + box-shadow: 0 0 8px rgba(0, 0, 0, 0.35); + transition: transform 0.12s ease, border-color 0.12s ease; +} +/* Distance ring — green optimal / yellow far / red close (animator rangeState) */ +.hud__crosshair-range { + position: absolute; + inset: 4px; + border: 1.5px solid transparent; + border-radius: 50%; + opacity: 0; + transition: opacity 0.12s ease, border-color 0.12s ease; +} +.hud__crosshair.is-range-optimal .hud__crosshair-range { + opacity: 0.85; + border-color: rgba(110, 230, 140, 0.9); + box-shadow: 0 0 10px rgba(80, 200, 100, 0.35); +} +.hud__crosshair.is-range-far .hud__crosshair-range { + opacity: 0.8; + border-color: rgba(240, 200, 80, 0.9); +} +.hud__crosshair.is-range-close .hud__crosshair-range { + opacity: 0.85; + border-color: rgba(255, 90, 80, 0.92); +} +/* Four ticks push out by --ch-gap (bloom / movement) */ +.hud__crosshair-tick { + position: absolute; + background: rgba(232, 244, 255, 0.92); + box-shadow: 0 0 4px rgba(0, 0, 0, 0.55); + transition: top 0.08s ease, bottom 0.08s ease, left 0.08s ease, right 0.08s ease; +} +.hud__crosshair-tick--n, +.hud__crosshair-tick--s { + left: 50%; + width: 2px; + height: 7px; + margin-left: -1px; +} +.hud__crosshair-tick--e, +.hud__crosshair-tick--w { + top: 50%; + width: 7px; + height: 2px; + margin-top: -1px; +} +.hud__crosshair-tick--n { top: calc(50% - 14px - var(--ch-gap)); } +.hud__crosshair-tick--s { bottom: calc(50% - 14px - var(--ch-gap)); top: auto; } +.hud__crosshair-tick--e { right: calc(50% - 14px - var(--ch-gap)); left: auto; } +.hud__crosshair-tick--w { left: calc(50% - 14px - var(--ch-gap)); } +.hud__crosshair.is-softlock .hud__crosshair-tick { + background: rgba(255, 176, 160, 0.95); +} +/* Hit-marker X pulse (animator ch-hit) */ +.hud__crosshair-hit { + position: absolute; + inset: 0; + opacity: 0; + pointer-events: none; +} +.hud__crosshair-hit.is-pulse { + animation: xh-hit-pulse 0.28s ease-out forwards; +} +.hud__crosshair-hit-line { + position: absolute; + width: 8px; + height: 1.5px; + background: #fff; + box-shadow: 0 0 4px rgba(255, 200, 120, 0.9); +} +.hud__crosshair-hit-tl { top: 12px; left: 12px; transform: rotate(45deg); } +.hud__crosshair-hit-tr { top: 12px; right: 12px; transform: rotate(-45deg); } +.hud__crosshair-hit-bl { bottom: 12px; left: 12px; transform: rotate(-45deg); } +.hud__crosshair-hit-br { bottom: 12px; right: 12px; transform: rotate(45deg); } +@keyframes xh-hit-pulse { + 0% { opacity: 1; transform: scale(0.85); } + 100% { opacity: 0; transform: scale(1.25); } +} + /* ------------------------------------------------------------------ * * Loading veil * ------------------------------------------------------------------ */ @@ -151,7 +290,8 @@ body { font-size: 13px; } -.hud__elements { +.hud__elements, +.hud__actionbar { position: absolute; left: 50%; bottom: 26px; @@ -161,8 +301,9 @@ body { pointer-events: auto; } -/* The element picker is meaningless while walking — keep it visible but quiet. */ -.hud__elements.is-dimmed { +/* Dim when not in combat / walk sandbox */ +.hud__elements.is-dimmed, +.hud__actionbar.is-dimmed { opacity: 0.32; filter: saturate(0.35); } @@ -223,7 +364,8 @@ body { color: var(--ui-text-dim); } -.element-card { +.element-card, +.action-slot { position: relative; width: 86px; padding: 10px 8px 9px; @@ -237,21 +379,25 @@ body { cursor: pointer; transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; user-select: none; + overflow: hidden; } -.element-card:hover { +.element-card:hover, +.action-slot:hover { transform: translateY(-3px); background: rgba(20, 28, 38, 0.7); } -.element-card.is-active { +.element-card.is-active, +.action-slot.is-active { border-color: var(--accent, var(--ui-accent)); background: rgba(20, 28, 38, 0.82); box-shadow: 0 0 0 1px var(--accent, var(--ui-accent)) inset, 0 8px 28px rgba(0, 0, 0, 0.5), 0 0 26px -6px var(--accent, var(--ui-accent)); } -.element-card__glyph { +.element-card__glyph, +.action-slot__glyph { font-size: 22px; line-height: 1; color: var(--accent, var(--ui-accent)); @@ -259,7 +405,8 @@ body { } /* Inline sigils: sized like the old text glyph, glow via filter instead. */ -.element-card__glyph .glyph-svg { +.element-card__glyph .glyph-svg, +.action-slot__glyph .glyph-svg { display: block; width: 30px; height: 30px; @@ -268,11 +415,13 @@ body { transition: filter 0.18s ease; } -.element-card.is-active .glyph-svg { +.element-card.is-active .glyph-svg, +.action-slot.is-active .glyph-svg { filter: drop-shadow(0 0 10px var(--accent, var(--ui-accent))); } -.element-card__label { +.element-card__label, +.action-slot__label { margin-top: 6px; font-size: 11px; font-weight: 600; @@ -280,12 +429,30 @@ body { text-transform: uppercase; } -.element-card__key { +.element-card__key, +.action-slot__key { position: absolute; top: 6px; right: 8px; font-size: 10px; color: var(--ui-text-dim); + z-index: 2; +} + +/* Cooldown sweep on skill slots */ +.action-slot__cd { + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.55); + transform-origin: bottom; + transform: scaleY(var(--cd, 0)); + pointer-events: none; + z-index: 1; + transition: transform 0.12s linear; +} + +.action-slot.is-cooling { + opacity: 0.85; } .hud__panel { @@ -433,3 +600,725 @@ body { padding: 6px 12px; } } + +/* ------------------------------------------------------------------ * + * Unit frames + allies (combat HUD) + * ------------------------------------------------------------------ */ + +.hud-frame { + position: fixed; + z-index: 22; + display: flex; + align-items: stretch; + gap: 8px; + pointer-events: none; + min-width: 200px; + max-width: 280px; + padding: 8px 10px; + background: var(--ui-bg-solid); + border: 1px solid var(--ui-border); + border-radius: 12px; + box-shadow: var(--ui-shadow); + backdrop-filter: blur(10px); +} + +.hud-frame--player { + top: 56px; + left: 16px; +} + +.hud-frame--target { + top: 56px; + right: 16px; + flex-direction: row; +} + +.hud-frame--target.is-empty { + opacity: 0.45; +} + +.hud-frame__portrait { + width: 44px; + height: 44px; + border-radius: 10px; + background: linear-gradient(145deg, rgba(127, 214, 255, 0.25), rgba(0, 0, 0, 0.35)); + border: 1px solid var(--ui-border); + display: grid; + place-items: center; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.04em; + flex-shrink: 0; +} + +.hud-frame__portrait--target { + background: linear-gradient(145deg, rgba(255, 90, 80, 0.3), rgba(0, 0, 0, 0.35)); +} + +.hud-frame__body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 4px; + justify-content: center; +} + +.hud-frame__name { + font-size: 12px; + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.hud-frame__bar { + position: relative; + height: 10px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.45); + overflow: hidden; +} + +.hud-frame__bar--hp .hud-frame__fill { + background: linear-gradient(90deg, #2d8a4e, #5ddf8a); +} + +.hud-frame__bar--mp .hud-frame__fill { + background: linear-gradient(90deg, #2563eb, #38bdf8); +} +.hud-frame__bar--sta .hud-frame__fill { + background: linear-gradient(90deg, #a16207, #fbbf24); +} + +.hud-frame__bar--hostile .hud-frame__fill { + background: linear-gradient(90deg, #a03028, #ff6a55); +} + +.hud-frame__fill { + height: 100%; + width: 100%; + transition: width 0.15s ease; +} + +/* RMB soft-lock focus outline (production targeting) */ +.hud--focus .hud__crosshair, +body.focus-aim .hud__crosshair { + opacity: 1; +} +.hud--focus .hud__crosshair { + opacity: 1; +} +.hud--focus .hud__crosshair-ring { + border-color: rgba(255, 180, 80, 0.95); + box-shadow: 0 0 12px rgba(255, 160, 60, 0.55); +} +.hud--softlock .hud__crosshair-ring { + border-color: rgba(255, 90, 70, 0.98); + box-shadow: 0 0 14px rgba(255, 70, 50, 0.65); +} +.hud--focus .hud__crosshair-dot { + background: #ffb84a; +} +.hud--softlock .hud__crosshair-dot { + background: #ff5a45; +} + +.hud-frame__val { + position: absolute; + inset: 0; + display: grid; + place-items: center; + font-size: 9px; + font-weight: 600; + text-shadow: 0 1px 2px #000; + pointer-events: none; +} + +.hud-allies { + position: fixed; + top: 118px; + left: 16px; + z-index: 22; + display: flex; + flex-direction: column; + gap: 4px; + pointer-events: none; +} + +.hud-ally { + position: relative; + width: 120px; + height: 22px; + border-radius: 6px; + background: rgba(12, 16, 22, 0.85); + border: 1px solid var(--ui-border); + font-size: 10px; + display: flex; + align-items: center; + padding: 0 8px; + overflow: hidden; +} + +.hud-ally.is-empty { + opacity: 0.35; +} + +.hud-ally i { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: rgba(93, 223, 138, 0.25); + z-index: 0; +} + +.hud-ally span { + position: relative; + z-index: 1; +} + +.action-slot--melee { + margin-left: 8px; + border-color: rgba(125, 211, 252, 0.35); +} + +/* ------------------------------------------------------------------ * + * Lab panel (I) — left side Main Panel tester + * ------------------------------------------------------------------ */ + +.inv-panel { + position: fixed; + top: 56px; + left: 16px; + width: min(420px, calc(100vw - 24px)); + max-height: calc(100vh - 80px); + z-index: 40; + display: flex; + flex-direction: column; + pointer-events: auto; + background: var(--ui-bg-solid); + border: 1px solid var(--ui-border); + border-radius: var(--ui-radius); + box-shadow: var(--ui-shadow); + backdrop-filter: blur(12px); + color: var(--ui-text); + overflow: hidden; +} + +.inv-panel--lab { + /* Keep clear of player frame when both open — panel stacks under frames at z40 */ + top: 148px; + max-height: calc(100vh - 168px); +} + +.inv-panel[hidden] { + display: none !important; +} + +.inv-panel__head { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 12px; + padding: 14px 16px 8px; + border-bottom: 1px solid var(--ui-border); +} + +.inv-panel__head h2 { + margin: 0; + font-size: 15px; + font-weight: 600; + letter-spacing: 0.04em; +} + +.inv-panel__sub { + margin: 4px 0 0; + font-size: 11px; + color: var(--ui-text-dim); +} + +.inv-panel__close { + border: 0; + background: transparent; + color: var(--ui-text-dim); + font-size: 22px; + line-height: 1; + cursor: pointer; + padding: 0 4px; +} + +.inv-panel__layout { + display: flex; + flex: 1; + min-height: 0; + overflow: hidden; +} + +.inv-panel__nav { + display: flex; + flex-direction: column; + gap: 2px; + padding: 8px 6px; + width: 96px; + flex-shrink: 0; + border-right: 1px solid var(--ui-border); + overflow-y: auto; + background: rgba(0, 0, 0, 0.2); +} + +.inv-nav-tab { + border: 1px solid transparent; + background: transparent; + color: var(--ui-text-dim); + border-radius: 8px; + padding: 8px 6px; + font-size: 11px; + text-align: left; + cursor: pointer; + line-height: 1.2; +} + +.inv-nav-tab:hover { + color: var(--ui-text); + background: rgba(255, 255, 255, 0.04); +} + +.inv-nav-tab.is-active { + color: var(--ui-text); + border-color: var(--ui-border); + background: rgba(127, 214, 255, 0.14); +} + +.inv-panel__tabs { + display: flex; + gap: 4px; + padding: 8px 12px; + border-bottom: 1px solid var(--ui-border); +} + +.inv-tab { + flex: 1; + border: 1px solid transparent; + background: rgba(255, 255, 255, 0.04); + color: var(--ui-text-dim); + border-radius: 8px; + padding: 8px; + font-size: 12px; + cursor: pointer; +} + +.inv-tab.is-active { + color: var(--ui-text); + border-color: var(--ui-border); + background: rgba(127, 214, 255, 0.12); +} + +.inv-panel__body { + overflow: auto; + padding: 12px 14px 16px; + flex: 1; + min-width: 0; +} + +.inv-panel__foot { + padding: 8px 12px; + border-top: 1px solid var(--ui-border); + font-size: 11px; + color: var(--ui-text-dim); +} + +.inv-panel__foot a { + color: var(--ui-accent); + text-decoration: none; +} + +.inv-card { + background: rgba(0, 0, 0, 0.28); + border: 1px solid var(--ui-border); + border-radius: 10px; + padding: 10px 12px; + margin-bottom: 12px; +} + +.inv-card__row { + display: flex; + justify-content: space-between; + gap: 8px; + font-size: 12px; + padding: 3px 0; + color: var(--ui-text-dim); +} + +.inv-card__row b { + color: var(--ui-text); + font-weight: 600; + text-align: right; +} + +.inv-code { + font-family: ui-monospace, Consolas, monospace; + font-size: 10px; + word-break: break-all; +} + +.inv-btn-row { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 10px 0; +} + +.inv-btn { + border: 1px solid var(--ui-border); + background: rgba(127, 214, 255, 0.15); + color: var(--ui-text); + border-radius: 8px; + padding: 8px 12px; + font-size: 12px; + cursor: pointer; + text-decoration: none; + display: inline-flex; + align-items: center; +} + +.inv-btn--ghost { + background: rgba(255, 255, 255, 0.04); +} + +.inv-btn:hover { + border-color: var(--ui-accent); +} + +.inv-hint { + font-size: 11px; + color: var(--ui-text-dim); + margin: 0 0 10px; + line-height: 1.4; +} + +.inv-race-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; +} + +.inv-race { + border: 1px solid var(--ui-border); + background: rgba(0, 0, 0, 0.25); + border-radius: 10px; + padding: 10px; + text-align: left; + color: var(--ui-text); + cursor: pointer; + display: flex; + flex-direction: column; + gap: 2px; +} + +.inv-race.is-active { + border-color: var(--ui-accent); + background: rgba(127, 214, 255, 0.12); +} + +.inv-race__id { + font-size: 11px; + font-weight: 700; + color: var(--ui-accent); +} + +.inv-race__label { + font-size: 12px; +} + +.inv-race__file { + font-size: 9px; + color: var(--ui-text-dim); + font-family: ui-monospace, Consolas, monospace; +} + +.inv-weapon-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 12px; +} + +.inv-weapon-equip-grid { + display: flex; + flex-direction: column; + gap: 6px; + max-height: 280px; + overflow: auto; + margin-bottom: 10px; + padding-right: 4px; +} + +.inv-weapon-card { + display: grid; + grid-template-columns: 40px 1fr; + gap: 8px; + align-items: center; + border: 1px solid var(--ui-border); + border-radius: 8px; + padding: 6px 8px; + background: rgba(0, 0, 0, 0.28); + color: var(--ui-text); + cursor: pointer; + text-align: left; +} + +.inv-weapon-card.is-on { + border-color: rgba(125, 211, 252, 0.65); + box-shadow: 0 0 0 1px rgba(125, 211, 252, 0.2); +} + +.inv-weapon-card__icon { + width: 40px; + height: 40px; + object-fit: contain; + image-rendering: auto; + background: rgba(0, 0, 0, 0.35); + border-radius: 6px; +} + +.inv-weapon-card__name { + font-size: 12px; + font-weight: 600; +} + +.inv-weapon-card__meta, +.inv-weapon-card__skills { + font-size: 10px; + color: var(--ui-text-dim); +} + +.inv-equip-banner { + display: grid; + grid-template-columns: 48px 1fr; + gap: 10px; + align-items: center; + margin: 8px 0 10px; + padding: 8px; + border: 1px solid rgba(125, 211, 252, 0.35); + border-radius: 8px; + background: rgba(125, 211, 252, 0.06); +} + +.inv-equip-banner img { + width: 48px; + height: 48px; + object-fit: contain; +} + +.inv-weapon { + border: 1px solid var(--ui-border); + border-radius: 8px; + padding: 8px; + background: rgba(0, 0, 0, 0.2); +} + +.inv-weapon.is-on { + border-color: rgba(125, 211, 252, 0.5); +} + +.inv-weapon__name { + font-size: 11px; + margin-bottom: 4px; + text-transform: capitalize; +} + +.inv-weapon select { + width: 100%; + background: rgba(0, 0, 0, 0.35); + color: var(--ui-text); + border: 1px solid var(--ui-border); + border-radius: 6px; + padding: 4px; + font-size: 11px; +} + +.inv-skill.is-dim { + opacity: 0.55; +} + +.inv-skill__icon { + width: 22px; + height: 22px; + object-fit: contain; + border-radius: 4px; +} + +.inv-clip-grid { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 8px 0 12px; +} + +.inv-clip { + border: 1px solid var(--ui-border); + background: rgba(255, 255, 255, 0.05); + color: var(--ui-text); + border-radius: 6px; + padding: 6px 8px; + font-size: 11px; + cursor: pointer; +} + +.inv-clip:hover { + border-color: var(--ui-accent); +} + +.inv-skill-list { + display: flex; + flex-direction: column; + gap: 6px; + margin: 8px 0; +} + +.inv-skill { + display: grid; + grid-template-columns: 22px 22px 1fr; + gap: 8px; + align-items: center; + border: 1px solid var(--ui-border); + background: rgba(0, 0, 0, 0.25); + color: var(--ui-text); + border-radius: 8px; + padding: 8px 10px; + cursor: pointer; + text-align: left; + font-size: 12px; +} + +.inv-skill__key { + font-weight: 700; + color: var(--ui-accent); +} + +.inv-skill__meta { + font-size: 10px; + color: var(--ui-text-dim); +} + +.inv-table { + width: 100%; + border-collapse: collapse; + font-size: 11px; + margin-bottom: 10px; +} + +.inv-table th, +.inv-table td { + border-bottom: 1px solid var(--ui-border); + padding: 4px 6px; + text-align: left; +} + +.inv-mesh-list { + max-height: 220px; + overflow: auto; + font-size: 10px; + font-family: ui-monospace, Consolas, monospace; + background: rgba(0, 0, 0, 0.3); + border-radius: 8px; + padding: 8px; +} + +.inv-mesh-line { + color: var(--ui-text-dim); + padding: 1px 0; +} + +.inv-mesh-line.is-vis { + color: #8ef0b0; +} + +.inv-pre { + font-size: 10px; + background: rgba(0, 0, 0, 0.4); + border-radius: 8px; + padding: 8px; + max-height: 160px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; + color: var(--ui-text-dim); +} + +/* Shift player frame when lab is open — handled by z-index; panel under title */ + +.inv-row { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 12px; + font-size: 12px; + color: var(--ui-text-dim); +} + +.inv-row select, +.inv-row input[type='range'] { + width: 100%; +} + +.inv-row select { + background: rgba(0, 0, 0, 0.35); + border: 1px solid var(--ui-border); + color: var(--ui-text); + border-radius: 8px; + padding: 8px 10px; +} + +.inv-row--slider span { + display: flex; + justify-content: space-between; +} + +.inv-hint { + font-size: 11px; + color: var(--ui-text-dim); + margin: 0 0 12px; + line-height: 1.4; +} + +.inv-btn { + width: 100%; + margin-top: 8px; + padding: 10px 12px; + border-radius: 10px; + border: 1px solid rgba(127, 214, 255, 0.35); + background: rgba(127, 214, 255, 0.15); + color: var(--ui-text); + cursor: pointer; + font-size: 12px; +} + +.inv-btn--ghost { + background: transparent; + border-color: var(--ui-border); +} + +.inv-panel__foot { + padding: 10px 16px; + border-top: 1px solid var(--ui-border); + font-size: 11px; + color: var(--ui-text-dim); +} + +.inv-panel__foot kbd, +.inv-hint kbd { + display: inline-block; + padding: 1px 5px; + border-radius: 4px; + border: 1px solid var(--ui-border); + background: rgba(255, 255, 255, 0.06); + font-size: 10px; +} + +/* Catalog T0�T1 picker */ +.mp-picker__grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; max-height: 280px; overflow: auto; margin-top: 8px; } +.mp-picker__search { width: 100%; margin: 6px 0; padding: 6px 8px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.12); background: #1a1d24; color: #e8e8e8; } +.mp-picker__item small { opacity: 0.55; margin-left: 4px; } +.mp-mesh-app { margin-top: 12px; } diff --git a/src/ui/tightBar.css b/src/ui/tightBar.css new file mode 100644 index 0000000..1d08b6e --- /dev/null +++ b/src/ui/tightBar.css @@ -0,0 +1,309 @@ +/* ============================================================ + HUD Tight — Danger Room / threejs-rapier (HUD.psd 3800×726) + Port of gameopen tightBar.css for Casting lab + ============================================================ */ + +.tightbar-root { + position: fixed; + left: 0; + right: 0; + bottom: 0; + z-index: 28; + pointer-events: none; + display: flex; + flex-direction: column; + align-items: center; +} + +.tightbar-root.is-hidden { + display: none !important; +} + +.tightbar { + position: relative; + width: min(96vw, 1160px); + aspect-ratio: 3800 / 726; + background-size: 100% 100%; + background-repeat: no-repeat; + background-position: center bottom; + pointer-events: none; + filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.55)); +} + +.tb-orb { + position: absolute; + border-radius: 50%; + /* Clip to chrome glass; metal rim stays from hud-tight-bar.png */ + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; + box-sizing: border-box; +} +/* Circular well — Island-Crusade health_globe / mana_globe PNGs */ +.tb-orb-clip { + position: absolute; + /* slight inset so metal lip of chrome still frames the fill */ + inset: 4%; + border-radius: 50%; + overflow: hidden; + background: #05070b; + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.08), + inset 0 4px 12px rgba(0, 0, 0, 0.55); +} +.tb-orb-globe { + position: absolute; + left: 50%; + top: 50%; + /* + * Globes are ~164² solid discs with ~14px transparent pad (opaque ≈ 83%). + * Scale so the opaque disc fills the well; cover crops the pad. + */ + width: 128%; + height: 128%; + transform: translate(-50%, -50%); + object-fit: cover; + object-position: center; + pointer-events: none; + user-select: none; + image-rendering: auto; +} +/* Glass sheen on top of flat solid-color globe art */ +.tb-orb-clip::after { + content: ''; + position: absolute; + inset: 0; + border-radius: 50%; + pointer-events: none; + z-index: 1; + background: + radial-gradient(circle at 32% 28%, rgba(255, 255, 255, 0.42) 0%, rgba(255, 255, 255, 0.08) 28%, transparent 52%), + radial-gradient(circle at 50% 100%, rgba(0, 0, 0, 0.35) 0%, transparent 55%), + linear-gradient(160deg, rgba(255, 255, 255, 0.12) 0%, transparent 42%); + box-shadow: inset 0 -6px 14px rgba(0, 0, 0, 0.28); +} +.tb-orb-hp .tb-orb-globe { + filter: saturate(1.15) brightness(1.04); +} +.tb-orb-mp .tb-orb-globe { + filter: saturate(1.12) brightness(1.06); +} +/* Liquid level mask (drains from top as resource empties) */ +.tb-orb-fill { + position: absolute; + inset: 0; + pointer-events: none; + background: transparent; + z-index: 1; +} +.tb-orb-drain { + position: absolute; + top: 0; + left: 0; + right: 0; + background: linear-gradient( + 180deg, + rgba(4, 6, 12, 0.92) 0%, + rgba(4, 6, 12, 0.8) 65%, + rgba(4, 6, 12, 0.55) 100% + ); + transition: height 160ms linear; + z-index: 2; + pointer-events: none; +} +.tb-orb-val { + position: relative; + z-index: 3; + font-size: clamp(11px, 1.4vw, 17px); + font-weight: 700; + letter-spacing: 0.04em; + color: #f2ecdf; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95), 0 0 10px rgba(0, 0, 0, 0.75); +} + +.tb-slot { + position: absolute; + display: flex; + align-items: center; + justify-content: center; + color: #cfe2ff; + border-radius: 4px; + pointer-events: auto; + cursor: pointer; + border: 0; + background: transparent; + padding: 0; + margin: 0; + font: inherit; +} +.tb-slot:hover { + filter: brightness(1.15); +} +.tb-slot:focus-visible { + outline: 1px solid #6fe0ff; +} +.tb-glyph { + font-size: clamp(14px, 1.8vw, 22px); + line-height: 1; + filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.9)); + pointer-events: none; +} +.tb-slot.tb-accent .tb-glyph { + color: #ffd27a; +} +.tb-slot.tb-empty { + opacity: 0.4; + pointer-events: none; +} +.tb-slot .tb-key { + position: absolute; + right: 5%; + bottom: 3%; + font-size: clamp(7px, 0.75vw, 10px); + font-weight: 700; + letter-spacing: 0.05em; + color: rgba(235, 240, 250, 0.8); + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.9); + pointer-events: none; +} +.tb-sweep { + position: absolute; + inset: 6%; + border-radius: 4px; + pointer-events: none; +} +.tb-cd { + position: absolute; + font-size: clamp(9px, 1vw, 13px); + font-weight: 700; + color: #ffe9b0; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95); + pointer-events: none; +} +.tb-slot.on-cd .tb-glyph { + opacity: 0.45; +} + +.tb-avatar { + position: absolute; + left: 45.947%; + top: 38.6%; + width: 7.789%; + height: 55.1%; + border-radius: 48% 48% 6% 6% / 34% 34% 4% 4%; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + background: radial-gradient(circle at 50% 30%, rgba(58, 66, 84, 0.55), rgba(10, 12, 18, 0.65)); + pointer-events: none; +} +.tb-avatar-letter { + font-size: clamp(12px, 1.6vw, 20px); + font-weight: 800; + letter-spacing: 0.06em; + color: #e8dcc2; + text-shadow: 0 1px 3px #000; +} +.tb-avatar-name { + position: absolute; + bottom: 2%; + left: 0; + right: 0; + text-align: center; + font-size: clamp(7px, 0.8vw, 11px); + font-weight: 700; + letter-spacing: 0.08em; + text-transform: uppercase; + color: #e8dcc2; + text-shadow: 0 1px 3px rgba(0, 0, 0, 0.95); +} + +.tb-poise { + position: absolute; + left: 45.947%; + top: 95%; + width: 7.789%; + height: 3%; + border-radius: 3px; + background: rgba(10, 8, 8, 0.72); + box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.6); + overflow: hidden; + pointer-events: none; +} +.tb-poise-fill { + height: 100%; + background: linear-gradient(90deg, #b9924e, #e8c979); + transition: width 120ms linear; +} + +/* Menu buttons under / beside bar */ +.tb-menus { + display: flex; + gap: 6px; + margin-top: 4px; + margin-bottom: 6px; + pointer-events: auto; + z-index: 2; +} +.tb-menu-btn { + border: 1px solid rgba(255, 255, 255, 0.14); + background: rgba(12, 16, 22, 0.88); + color: rgba(230, 238, 255, 0.9); + border-radius: 8px; + padding: 6px 10px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.04em; + cursor: pointer; + backdrop-filter: blur(8px); +} +.tb-menu-btn:hover { + border-color: rgba(127, 214, 255, 0.55); + color: #fff; +} + +.tb-keychips { + display: flex; + flex-wrap: wrap; + gap: 4px; + justify-content: center; + margin-bottom: 8px; + pointer-events: none; + opacity: 0.65; +} +.tb-keychips.is-hidden { + display: none !important; +} +.tb-chip { + font-size: 9px; + padding: 2px 6px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.45); + border: 1px solid rgba(255, 255, 255, 0.08); + color: rgba(200, 214, 230, 0.85); + letter-spacing: 0.04em; +} + +/* Hide legacy bottom action bar when tight is live */ +.hud--tight .hud__actionbar { + display: none !important; +} +.hud--tight .hud__modes { + bottom: auto; + top: 12px; + right: 16px; + left: auto; +} + +@media (max-width: 720px) { + .tightbar { + width: 100vw; + } + .tb-menus { + flex-wrap: wrap; + justify-content: center; + } +} diff --git a/src/ui/uiAssetCatalog.js b/src/ui/uiAssetCatalog.js new file mode 100644 index 0000000..2e8d520 --- /dev/null +++ b/src/ui/uiAssetCatalog.js @@ -0,0 +1,197 @@ +/** + * Casting UI asset inventory — every chrome pack we ship or link. + * Prefer same-origin public/ then CDN. Do not invent placeholder chrome. + * + * @see docs/MAIN_PANEL_INVENTORY_SSOT.md · craftpix-rpg-mmo-ui skill + */ + +import { ASSETS_URL } from '../config/fleetEnv.js'; +import { CRAFTPIX, craftpixUrl } from './craftpixUi.js'; +import { warlordsUiUrl } from './warlordsUiSkin.js'; + +/** @typedef {{ id: string, role: string, local?: string, cdn?: string, system: string }} UiAssetEntry */ + +/** + * Full inventory for agents + Admin API tab. + * @type {UiAssetEntry[]} + */ +export const UI_ASSET_CATALOG = Object.freeze([ + // —— CraftPix (HUD combat chrome) —— + { + id: 'cp.panel', + role: 'window / main panel shell', + local: './ui/craftpix/panel/bg.png', + system: 'Main Panel · modal chrome' + }, + { + id: 'cp.hotbar_slot', + role: 'hotbar / bag slot bg+border', + local: './ui/craftpix/hotbar/slot_bg.png', + system: 'TightBar · bag slots · skills' + }, + { + id: 'bars.player_frame', + role: 'player unit frame', + local: './hud/bars/unit-frames/unit_frame_009.png', + system: 'HUD bars-frame--player · BARS_HUD_PICKS' + }, + { + id: 'bars.overhead_enemy', + role: 'enemy overhead HP', + local: './hud/bars/overhead/overhead_health_003.png', + system: 'OverheadNameplates enemy' + }, + { + id: 'bars.overhead_ally', + role: 'ally overhead HP', + local: './hud/bars/overhead/overhead_health_001.png', + system: 'OverheadNameplates ally · HUD ally strip' + }, + { + id: 'cp.unit_frame', + role: 'CraftPix avatar layers (portrait well)', + local: './ui/craftpix/unit/avatar_bg.png', + system: 'HUD portrait under bars unit_frame_009' + }, + { + id: 'cp.cast', + role: 'cast / channel bar', + local: './ui/craftpix/cast/bg.png', + system: 'CastBar under crosshair' + }, + { + id: 'cp.fill', + role: 'thin HP/MP tracks', + local: './ui/craftpix/fill/track_bg.png', + system: 'HUD fill bars' + }, + // —— Warlords dev shells —— + { + id: 'wl.inventory', + role: 'full bag shell', + local: './ui/warlords-dev/inventory/inventory.png', + system: 'Main Panel inv-panel--main · wl-inv-shell' + }, + { + id: 'wl.mini', + role: 'mini bag 9×4 + hotbar', + local: './ui/warlords-dev/inventory/miniinventory.png', + system: 'DropBag harvest loot' + }, + { + id: 'wl.button', + role: 'pixel button atlas', + local: './ui/warlords-dev/buttons/button-ui.png', + system: 'Chrome buttons' + }, + { + id: 'wl.cursors', + role: 'pirate intent cursors', + local: './ui/warlords-dev/cursors/pirate/MouseIcon2.png', + system: 'warlordsCursors · world LMB/RMB' + }, + // —— Equipment / slots —— + { + id: 'inv.slots_set', + role: 'bag slot skins (10 tones)', + local: './ui/inventory/inventory-slots-set.png', + system: 'mainPanelSlots BAG_SKINS' + }, + { + id: 'inv.equip_ref', + role: 'paperdoll silhouette', + local: './ui/inventory/equipment-reference.png', + system: 'Main Panel character paperdoll' + }, + // —— HUD orbs —— + { + id: 'hud.health', + role: 'health globe', + local: './hud/health_globe.png', + system: 'HUD player resource' + }, + { + id: 'hud.mana', + role: 'mana globe', + local: './hud/mana_globe.png', + system: 'HUD player resource' + }, + { + id: 'hud.tight', + role: 'tight bar strip', + local: './hud-tight-bar.png', + system: 'TightBar skills' + }, + // —— Production hosts —— + { + id: 'host.main_panel', + role: 'fleet Main Panel production', + cdn: 'https://ui.grudge-studio.com/main-panel.html?era=warlords', + system: 'Embed / API tab · equipment paperdoll production' + }, + { + id: 'host.main_panel_equip', + role: 'equipment tab embed', + cdn: 'https://ui.grudge-studio.com/main-panel.html?era=warlords&embed=1&tab=equipment', + system: 'Admin / casting production tester' + }, + { + id: 'host.craft', + role: 'WCS craft + bag SSOT', + cdn: 'https://grudgewarlords.com/craft/', + system: 'Professions · materials bag Railway' + }, + { + id: 'host.hydra', + role: 'HYDRA UI studio', + cdn: 'https://ui.grudge-studio.com', + system: 'Design HUD outlines 1920×1080' + } +]); + +/** + * Resolve catalog entry URL (local first). + * @param {string} id + */ +export function resolveUiAsset(id) { + const e = UI_ASSET_CATALOG.find((x) => x.id === id); + if (!e) return null; + if (e.local) return e.local; + return e.cdn || null; +} + +/** + * Apply shared Main Panel CSS vars (CraftPix + Warlords shells). + * @param {HTMLElement} [root] + */ +export function applyMainPanelUiVars(root = document.documentElement) { + if (!root?.style) return; + root.style.setProperty('--mp-panel-bg', `url(${craftpixUrl(CRAFTPIX.panelBg)})`); + root.style.setProperty('--mp-slot-bg', `url(${craftpixUrl(CRAFTPIX.slotBg)})`); + root.style.setProperty('--mp-slot-border', `url(${craftpixUrl(CRAFTPIX.slotBorder)})`); + root.style.setProperty('--mp-inv-shell', `url(${warlordsUiUrl('inventory/inventory.png')})`); + root.style.setProperty('--mp-mini-shell', `url(${warlordsUiUrl('inventory/miniinventory.png')})`); + root.style.setProperty('--mp-equip-ref', `url(./ui/inventory/equipment-reference.png)`); + root.style.setProperty('--mp-slots-set', `url(./ui/inventory/inventory-slots-set.png)`); + root.style.setProperty('--mp-cdn', ASSETS_URL); +} + +/** Production Main Panel URLs */ +export const MAIN_PANEL_PROD = Object.freeze({ + full: 'https://ui.grudge-studio.com/main-panel.html?era=warlords', + embed: 'https://ui.grudge-studio.com/main-panel.html?era=warlords&embed=1', + equipment: + 'https://ui.grudge-studio.com/main-panel.html?era=warlords&embed=1&tab=equipment', + inventory: + 'https://ui.grudge-studio.com/main-panel.html?era=warlords&embed=1&tab=inventory', + craft: 'https://grudgewarlords.com/craft/' +}); + +export function catalogBySystem() { + /** @type {Record} */ + const m = {}; + for (const e of UI_ASSET_CATALOG) { + (m[e.system] ||= []).push(e); + } + return m; +} diff --git a/src/ui/vfxStudio/VfxStudio.js b/src/ui/vfxStudio/VfxStudio.js new file mode 100644 index 0000000..a89d5f0 --- /dev/null +++ b/src/ui/vfxStudio/VfxStudio.js @@ -0,0 +1,692 @@ +/** + * VFX Studio — singular tabbed authoring shell for Casting lab. + * + * Hosts: + * - Pipeline / Skill / Delivery / VFX / Linear / Samples / Export (rich UI) + * - Knobs tab = existing lil-gui Editor (settings.js live SSOT) + * + * Does not invent a second combat engine or second mixer. + * Preview uses App hooks (VfxDirector / residual / clear). + * + * @see src/config/skillAuthoringSSOT.js + * @see docs/SKILL_AUTHORING_STUDIO_SSOT.md + */ + +import { + STUDIO_TABS, + DELIVERY_GROUPS, + PRIMITIVE_META, + SAMPLE_LIBRARY, + LINEAR_FAMILIES, + SKILL_TEMPLATE_FIELDS, + deliveryMetaList, + catalogByCategory, + EFFECT_KINDS, + DELIVERY_META +} from '../../config/skillAuthoringSSOT.js'; +import { settings } from '../../config/settings.js'; +import { + applyPrimitiveToSettings, + buildActiveKindPrefab, + buildEffectPrefab, + downloadJson, + residualFromSettings +} from '../../vfx/effectPrefab.js'; +import './vfxStudio.css'; + +/** + * @typedef {{ + * editor: import('../Editor.js').Editor, + * onToast?: (m: string) => void, + * onPreviewEffect?: (effectId: string) => void, + * onPreviewDelivery?: (pattern: string) => void, + * onClear?: () => void, + * getActiveSkill?: () => object|null + * }} VfxStudioHooks + */ + +export class VfxStudio { + /** + * @param {VfxStudioHooks} hooks + */ + constructor(hooks = {}) { + this.hooks = hooks; + this.editor = hooks.editor; + this._open = true; + this._tab = 'pipeline'; + this._selectedDelivery = 'caster_to_target'; + this._selectedPrimitive = settings.effect?.activeKind || 'impact'; + this._selectedSample = null; + this._selectedVfxId = null; + this._skillDraft = this._defaultSkillDraft(); + this._knobsDocked = false; + + this.root = document.createElement('div'); + this.root.className = 'vfx-studio'; + this.root.setAttribute('role', 'dialog'); + this.root.setAttribute('aria-label', 'VFX Studio'); + document.body.appendChild(this.root); + + this._buildShell(); + this._bindDrag(); + this.setTab('pipeline'); + + // Dock lil-gui into Knobs when first opened to that tab + // Keep editor visible through studio until knobs dock + if (this.editor) { + // Hide free-floating title chrome confusion: studio is the shell + try { + this.editor.gui.domElement.classList.add('vfx-studio-docked'); + } catch { + /* */ + } + } + } + + _defaultSkillDraft() { + return { + id: 'lab_skill_draft', + label: 'Lab skill', + style: 'spell', + element: 'fire', + cooldownSec: 4, + castTimeSec: 0.6, + manaCost: 12, + staminaCost: 0, + rangeM: 18, + damage: 24, + delivery: 'caster_to_target', + castEffectId: 'arcane_swirl', + travelEffectId: 'fireball', + impactEffectId: 'inferno' + }; + } + + _buildShell() { + this.root.innerHTML = ` +
    +

    VFX Studio skill · delivery · samples

    + + +
    + +
    +
    + uMMORPG-shaped · catalog first + settings.js live +
    + `; + + const tabNav = this.root.querySelector('.vfx-studio__tabs'); + for (const t of STUDIO_TABS) { + const b = document.createElement('button'); + b.type = 'button'; + b.className = 'vfx-studio__tab'; + b.dataset.tab = t.id; + b.setAttribute('role', 'tab'); + b.innerHTML = `${t.icon}${t.label}`; + b.title = t.hint; + b.addEventListener('click', () => this.setTab(t.id)); + tabNav.appendChild(b); + } + + this.body = this.root.querySelector('.vfx-studio__body'); + this._panels = {}; + for (const t of STUDIO_TABS) { + const panel = document.createElement('div'); + panel.className = 'vfx-studio__panel'; + panel.dataset.panel = t.id; + panel.setAttribute('role', 'tabpanel'); + this.body.appendChild(panel); + this._panels[t.id] = panel; + } + + this._renderAllPanels(); + + this.root.querySelector('[data-act="close"]').addEventListener('click', () => this.close()); + this.root.querySelector('[data-act="clear"]').addEventListener('click', () => { + this.hooks.onClear?.(); + this.hooks.onToast?.('Effects cleared'); + }); + } + + _renderAllPanels() { + this._renderPipeline(this._panels.pipeline); + this._renderSkill(this._panels.skill); + this._renderDelivery(this._panels.delivery); + this._renderVfx(this._panels.vfx); + this._renderLinear(this._panels.linear); + this._renderSamples(this._panels.samples); + this._renderKnobs(this._panels.knobs); + this._renderExport(this._panels.export); + } + + /* ---- panels ---- */ + + _renderPipeline(el) { + el.innerHTML = ` +

    ${STUDIO_TABS.find((t) => t.id === 'pipeline').hint}

    +
      +
    1. Catalog skill — master-weaponSkills / T0 row (id · costs · style). Never invent ids.
    2. +
    3. Delivery — weapon · linear · over · under · around · aura · path (skillDelivery.js).
    4. +
    5. Effect primitives — trail · travel · cast · impact · residual · decal · aura.
    6. +
    7. Samples — mesh/orb/slash swatches → meshId + effectId bind.
    8. +
    9. Knobs — live settings.js (intensity · AOE · residual · element · post).
    10. +
    11. Export — EffectPrefab JSON → Warlords / Open client production.
    12. +
    +
    +

    uMMORPG ↔ Casting

    +

    + ScriptableObject skill packs become JSON skill rows + EffectPrefab. + Runtime only activates assets — DrcCombatController + VfxDirector + projectiles. + No second ScriptMachine / second mixer. +

    +
    +
    + + + +
    + `; + el.querySelectorAll('[data-go]').forEach((b) => + b.addEventListener('click', () => this.setTab(b.dataset.go)) + ); + } + + _renderSkill(el) { + const d = this._skillDraft; + const fields = SKILL_TEMPLATE_FIELDS.map((f) => { + if (f.type === 'enum') { + const opts = (f.options || []).map( + (o) => `` + ); + return `
    +
    `; + } + if (f.type === 'delivery') { + const opts = Object.keys(DELIVERY_META) + .map( + (id) => + `` + ) + .join(''); + return `
    +
    `; + } + return `
    +
    `; + }).join(''); + + el.innerHTML = ` +

    Scriptable skill draft (lab). Production rows stay in catalog APIs.

    +
    ${fields}
    +
    + + +
    + `; + + el.querySelectorAll('[data-sk]').forEach((input) => { + const sync = () => { + const k = input.dataset.sk; + let v = input.value; + if (['cooldownSec', 'castTimeSec', 'manaCost', 'staminaCost', 'rangeM', 'damage'].includes(k)) { + v = Number(v); + } + this._skillDraft[k] = v; + if (k === 'delivery') this._selectedDelivery = v; + }; + input.addEventListener('change', sync); + input.addEventListener('input', sync); + }); + + el.querySelector('[data-act="apply-skill"]').addEventListener('click', () => { + this._selectedDelivery = this._skillDraft.delivery; + settings.effect = settings.effect || {}; + if (this._skillDraft.element) { + // soft bind color by element for authoring + const map = { + fire: '#ff6a1e', + ice: '#9fdcff', + storm: '#a0d8ff', + holy: '#fff6c0', + arcane: '#b070ff', + nature: '#80e060', + water: '#60c0e0', + earth: '#c4a574' + }; + const c = map[String(this._skillDraft.element).toLowerCase()]; + if (c) settings.effect.color = c; + } + this.editor?.refresh?.(); + this.hooks.onToast?.( + `Skill draft · ${this._skillDraft.label} · ${this._skillDraft.delivery}` + ); + this.setTab('delivery'); + }); + + el.querySelector('[data-act="pull-active"]').addEventListener('click', () => { + const s = this.hooks.getActiveSkill?.(); + if (!s) { + this.hooks.onToast?.('No active skill on bar'); + return; + } + Object.assign(this._skillDraft, { + id: s.id || this._skillDraft.id, + label: s.label || s.name || s.id, + style: s.style || 'spell', + element: s.element || '', + cooldownSec: s.cooldownSec ?? s.cooldown ?? 4, + castTimeSec: s.castTimeSec ?? s.castTime ?? 0.5, + manaCost: s.manaCost ?? s.mana ?? 0, + staminaCost: s.staminaCost ?? 0, + rangeM: s.rangeM ?? s.range ?? 12, + damage: s.damage ?? 0, + castEffectId: s.castEffectId || '', + travelEffectId: s.travelEffectId || '', + impactEffectId: s.impactEffectId || '' + }); + this._renderSkill(el); + this.hooks.onToast?.(`Pulled · ${this._skillDraft.label}`); + }); + } + + _renderDelivery(el) { + const groups = DELIVERY_GROUPS.map((g) => { + const sel = g.patterns.includes(this._selectedDelivery) ? ' is-selected' : ''; + return ``; + }).join(''); + + const all = deliveryMetaList() + .map((m) => { + const sel = m.id === this._selectedDelivery ? ' is-selected' : ''; + return ``; + }) + .join(''); + + el.innerHTML = ` +

    Where the skill lives in space — SSOT: skillDelivery.js

    +
    +

    Groups

    +
    ${groups}
    +
    +
    +

    All patterns

    +
    ${all}
    +
    +
    + +
    + `; + + const pick = (pattern) => { + this._selectedDelivery = pattern; + this._skillDraft.delivery = pattern; + this._renderDelivery(el); + this.hooks.onPreviewDelivery?.(pattern); + this.hooks.onToast?.(`Delivery · ${pattern}`); + }; + + el.querySelectorAll('[data-pattern]').forEach((b) => + b.addEventListener('click', () => pick(b.dataset.pattern)) + ); + el.querySelector('[data-act="preview-del"]').addEventListener('click', () => { + const meta = DELIVERY_META[this._selectedDelivery]; + this.hooks.onToast?.(meta?.description || this._selectedDelivery); + }); + } + + _renderVfx(el) { + const prims = EFFECT_KINDS.map((k) => { + const m = PRIMITIVE_META[k] || { label: k, color: '#888', blurb: '' }; + const sel = k === this._selectedPrimitive ? ' is-selected' : ''; + return ``; + }).join(''); + + const byCat = catalogByCategory(); + let listHtml = ''; + for (const [cat, items] of Object.entries(byCat)) { + listHtml += `

    ${cat}

    `; + for (const e of items) { + const hex = '#' + (e.color >>> 0).toString(16).padStart(6, '0'); + const sel = e.id === this._selectedVfxId ? ' is-selected' : ''; + listHtml += `
    + + ${e.name}
    ${e.id} · ${e.tags?.slice(0, 3).join(', ')}
    +
    `; + } + listHtml += '
    '; + } + + el.innerHTML = ` +

    Primitives + VfxDirector catalog (sandbox Alt+V/B/F/G/T/C ids)

    +
    +

    Primitives

    +
    ${prims}
    +
    +
    +

    Catalog

    + ${listHtml} +
    + `; + + el.querySelectorAll('[data-prim]').forEach((b) => + b.addEventListener('click', () => { + const k = b.dataset.prim; + this._selectedPrimitive = k; + settings.effect.activeKind = k; + if (k === 'residual') { + /* residual knobs already in settings.residual */ + } + applyPrimitiveToSettings({ + kind: k, + intensity: settings.effect.intensity, + aoe: settings.effect.aoe, + speed: settings.effect.speed, + size: settings.effect.size, + color: settings.effect.color, + meshId: settings.effect.meshId, + duration: settings.effect.duration, + attach: settings.effect.attach + }); + this.editor?.refresh?.(); + this._renderVfx(el); + this.hooks.onToast?.(`Primitive · ${k}`); + }) + ); + + el.querySelectorAll('[data-vfx]').forEach((b) => + b.addEventListener('click', () => { + const id = b.dataset.vfx; + this._selectedVfxId = id; + this.hooks.onPreviewEffect?.(id); + this.hooks.onToast?.(`Preview · ${id}`); + this._renderVfx(el); + }) + ); + } + + _renderLinear(el) { + const cards = LINEAR_FAMILIES.map((f) => { + return ``; + }).join(''); + + el.innerHTML = ` +

    Linear skillshots — elementalLinearCast + caster_to_target / over / under

    +
    ${cards}
    +
    + + +
    + `; + + el.querySelectorAll('[data-lin]').forEach((b) => + b.addEventListener('click', () => { + const f = LINEAR_FAMILIES.find((x) => x.id === b.dataset.lin); + if (!f) return; + this._selectedDelivery = f.delivery; + this._skillDraft.element = f.element; + this._skillDraft.delivery = f.delivery; + const map = { + ice: '#9fdcff', + fire: '#ff6a1e', + storm: '#a0d8ff', + arcane: '#b070ff', + nature: '#80e060' + }; + if (map[f.element]) settings.effect.color = map[f.element]; + settings.effect.activeKind = f.delivery === 'over_target' ? 'impact' : 'travel'; + this.editor?.refresh?.(); + this.hooks.onToast?.(`Linear · ${f.label}`); + }) + ); + + el.querySelector('[data-act="lin-ice"]').addEventListener('click', () => { + settings.effect.color = '#9fdcff'; + this.editor?.refresh?.(); + this.hooks.onPreviewEffect?.('frost_wave'); + }); + el.querySelector('[data-act="lin-fire"]').addEventListener('click', () => { + settings.effect.color = '#ff6a1e'; + this.editor?.refresh?.(); + this.hooks.onPreviewEffect?.('inferno'); + }); + } + + _renderSamples(el) { + const cards = SAMPLE_LIBRARY.map((s) => { + const g = `linear-gradient(145deg, ${s.swatch[0]} 0%, ${s.swatch[1]} 55%, ${s.swatch[2]} 100%)`; + const sel = s.id === this._selectedSample ? ' is-selected' : ''; + return ``; + }).join(''); + + el.innerHTML = ` +

    Texture / mesh samples — pick to set meshId + color for authoring

    +
    ${cards}
    +

    + Paths under public/models/vfx/. Slash variants are catalog mesh ids (not always local GLB). +

    + `; + + el.querySelectorAll('[data-sample]').forEach((b) => + b.addEventListener('click', () => { + const s = SAMPLE_LIBRARY.find((x) => x.id === b.dataset.sample); + if (!s) return; + this._selectedSample = s.id; + settings.effect.meshId = s.id; + if (s.swatch?.[0]) settings.effect.color = s.swatch[0]; + // residual slash variants + if (s.id.startsWith('slash') && settings.residual) { + settings.residual.variant = s.id; + settings.residual.color = s.swatch[0]; + } + this.editor?.refresh?.(); + this._renderSamples(el); + this.hooks.onToast?.(`Sample · ${s.label} → meshId`); + }) + ); + } + + _renderKnobs(el) { + el.innerHTML = ` +

    Live knobs — same settings.js fields the frame loop reads. No rebuild.

    +
    + `; + this._dockEditor(el.querySelector('[data-knobs-host]')); + } + + _dockEditor(host) { + if (!this.editor?.gui?.domElement || !host) return; + const guiEl = this.editor.gui.domElement; + if (guiEl.parentElement !== host) { + host.appendChild(guiEl); + guiEl.classList.add('vfx-studio-docked'); + this._knobsDocked = true; + } + // Ensure visible when studio open + this.editor.gui.show(this._open); + this._hiddenGuiSync = true; + } + + _renderExport(el) { + el.innerHTML = ` +

    Export EffectPrefab packs for client production (Warlords / Open).

    +
    +

    Current draft

    +
    
    +      
    +
    + + + + +
    + `; + + const prev = el.querySelector('[data-export-preview]'); + try { + const snap = { + skillDraft: this._skillDraft, + delivery: this._selectedDelivery, + primitive: this._selectedPrimitive, + residual: residualFromSettings(), + effect: { ...settings.effect } + }; + prev.textContent = JSON.stringify(snap, null, 2); + } catch { + prev.textContent = '{}'; + } + + el.querySelector('[data-exp="solo"]').addEventListener('click', () => { + const prefab = buildActiveKindPrefab(); + downloadJson(prefab, `${prefab.id}.json`); + this.hooks.onToast?.(`Exported ${prefab.id}`); + }); + el.querySelector('[data-exp="melee"]').addEventListener('click', () => { + const prefab = buildEffectPrefab('drc_melee_strike'); + downloadJson(prefab, 'prefab_drc_melee_strike.json'); + this.hooks.onToast?.('Exported melee residual'); + }); + el.querySelector('[data-exp="fire"]').addEventListener('click', () => { + const prefab = buildEffectPrefab('drc_fire_bolt'); + downloadJson(prefab, 'prefab_drc_fire_bolt.json'); + this.hooks.onToast?.('Exported fire bolt'); + }); + el.querySelector('[data-exp="pack"]').addEventListener('click', () => { + const ids = [ + 'drc_melee_strike', + 'drc_fire_bolt', + 'drc_water_lash', + 'drc_earth_spike', + 'drc_wind_tempest' + ]; + const pack = { + source: 'casting-lab-vfx-studio', + version: '1.1.0', + skillDraft: this._skillDraft, + delivery: this._selectedDelivery, + exportedAt: new Date().toISOString(), + prefabs: ids.map((id) => buildEffectPrefab(id)) + }; + downloadJson(pack, 'casting-effect-prefabs.json'); + this.hooks.onToast?.(`Exported pack ×${pack.prefabs.length}`); + }); + } + + /* ---- chrome ---- */ + + setTab(id) { + if (!this._panels[id]) return; + this._tab = id; + this.root.querySelectorAll('.vfx-studio__tab').forEach((t) => { + t.classList.toggle('is-active', t.dataset.tab === id); + }); + Object.entries(this._panels).forEach(([k, p]) => { + p.classList.toggle('is-active', k === id); + }); + const foot = this.root.querySelector('[data-foot-right]'); + if (foot) foot.textContent = STUDIO_TABS.find((t) => t.id === id)?.hint?.slice(0, 42) || ''; + + if (id === 'knobs') { + this._renderKnobs(this._panels.knobs); + this.editor?.gui?.show?.(this._open); + } else if (this._knobsDocked) { + // Keep docked DOM hidden with panel; no free-float + this.editor?.gui?.show?.(false); + } + if (id === 'export') { + this._renderExport(this._panels.export); + } + document.body.classList.toggle('vfx-studio-open', this._open); + } + + open() { + this._open = true; + this.root.classList.remove('is-hidden'); + // Keep lil-gui docked in Knobs panel (hidden until that tab) — never free-float + if (!this._knobsDocked) { + this._renderKnobs(this._panels.knobs); + } + // Only show gui chrome when Knobs tab active (avoids empty floating panel) + this.editor?.gui?.show?.(this._tab === 'knobs'); + document.body.classList.add('vfx-studio-open'); + } + + close() { + this._open = false; + this.root.classList.add('is-hidden'); + this.editor?.close?.(); + document.body.classList.remove('vfx-studio-open'); + } + + toggle() { + if (this._open) this.close(); + else this.open(); + } + + get isOpen() { + return this._open; + } + + refresh() { + this.editor?.refresh?.(); + if (this._tab === 'export') this._renderExport(this._panels.export); + } + + _bindDrag() { + const head = this.root.querySelector('[data-drag]'); + let ox = 0; + let oy = 0; + let dragging = false; + head.addEventListener('pointerdown', (e) => { + if (e.target.closest('button')) return; + dragging = true; + const r = this.root.getBoundingClientRect(); + ox = e.clientX - r.left; + oy = e.clientY - r.top; + head.setPointerCapture(e.pointerId); + }); + head.addEventListener('pointermove', (e) => { + if (!dragging) return; + const x = Math.max(0, Math.min(window.innerWidth - 80, e.clientX - ox)); + const y = Math.max(0, Math.min(window.innerHeight - 80, e.clientY - oy)); + this.root.style.left = `${x}px`; + this.root.style.top = `${y}px`; + this.root.style.right = 'auto'; + }); + head.addEventListener('pointerup', () => { + dragging = false; + }); + } + + dispose() { + // Return gui to body if docked + try { + const guiEl = this.editor?.gui?.domElement; + if (guiEl && guiEl.parentElement !== document.body) { + document.body.appendChild(guiEl); + } + } catch { + /* */ + } + this.root.remove(); + document.body.classList.remove('vfx-studio-open'); + } +} + +export default VfxStudio; diff --git a/src/ui/vfxStudio/vfxStudio.css b/src/ui/vfxStudio/vfxStudio.css new file mode 100644 index 0000000..ebe3a52 --- /dev/null +++ b/src/ui/vfxStudio/vfxStudio.css @@ -0,0 +1,460 @@ +/** + * VFX Studio — singular tabbed authoring shell (Casting lab). + * Floats over viewport; does not invent a second combat UI. + */ + +.vfx-studio { + --vs-bg: rgba(10, 14, 20, 0.94); + --vs-panel: rgba(16, 22, 32, 0.98); + --vs-border: rgba(255, 255, 255, 0.1); + --vs-accent: #7fd6ff; + --vs-accent-dim: rgba(127, 214, 255, 0.15); + --vs-text: rgba(238, 245, 255, 0.94); + --vs-dim: rgba(180, 200, 220, 0.55); + --vs-radius: 12px; + --vs-width: min(420px, 96vw); + --vs-max-h: min(88vh, 920px); + --vs-font: 'Inter', 'Segoe UI', system-ui, sans-serif; + + position: fixed; + top: 12px; + right: 12px; + z-index: 80; + width: var(--vs-width); + max-height: var(--vs-max-h); + display: flex; + flex-direction: column; + font-family: var(--vs-font); + color: var(--vs-text); + background: var(--vs-bg); + border: 1px solid var(--vs-border); + border-radius: var(--vs-radius); + box-shadow: 0 16px 48px rgba(0, 0, 0, 0.55); + backdrop-filter: blur(12px); + overflow: hidden; + pointer-events: auto; +} + +.vfx-studio.is-hidden { + display: none; +} + +.vfx-studio__head { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.55rem 0.75rem; + border-bottom: 1px solid var(--vs-border); + background: linear-gradient(180deg, rgba(40, 55, 75, 0.5), transparent); + cursor: grab; + user-select: none; + flex-shrink: 0; +} + +.vfx-studio__head:active { + cursor: grabbing; +} + +.vfx-studio__title { + margin: 0; + font-size: 0.82rem; + font-weight: 650; + letter-spacing: 0.06em; + text-transform: uppercase; + flex: 1; +} + +.vfx-studio__title span { + color: var(--vs-accent); + font-weight: 500; + letter-spacing: 0.02em; + text-transform: none; + opacity: 0.85; + margin-left: 0.35rem; + font-size: 0.72rem; +} + +.vfx-studio__head-btn { + appearance: none; + border: 1px solid var(--vs-border); + background: rgba(255, 255, 255, 0.04); + color: var(--vs-text); + border-radius: 6px; + width: 28px; + height: 28px; + font-size: 0.85rem; + line-height: 1; + cursor: pointer; + display: grid; + place-items: center; +} + +.vfx-studio__head-btn:hover { + border-color: var(--vs-accent); + color: var(--vs-accent); +} + +/* Tab strip */ +.vfx-studio__tabs { + display: flex; + flex-wrap: wrap; + gap: 2px; + padding: 0.4rem 0.45rem; + border-bottom: 1px solid var(--vs-border); + background: rgba(0, 0, 0, 0.25); + flex-shrink: 0; +} + +.vfx-studio__tab { + appearance: none; + border: 1px solid transparent; + background: transparent; + color: var(--vs-dim); + font-size: 0.68rem; + font-weight: 600; + letter-spacing: 0.02em; + padding: 0.35rem 0.45rem; + border-radius: 6px; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + +.vfx-studio__tab:hover { + color: var(--vs-text); + background: rgba(255, 255, 255, 0.05); +} + +.vfx-studio__tab.is-active { + color: var(--vs-accent); + background: var(--vs-accent-dim); + border-color: rgba(127, 214, 255, 0.25); +} + +.vfx-studio__tab-icon { + opacity: 0.85; + font-size: 0.75rem; +} + +/* Body */ +.vfx-studio__body { + flex: 1; + min-height: 0; + overflow: auto; + padding: 0.65rem 0.75rem 0.9rem; +} + +.vfx-studio__panel { + display: none; + animation: vsFade 0.12s ease; +} + +.vfx-studio__panel.is-active { + display: block; +} + +@keyframes vsFade { + from { + opacity: 0; + transform: translateY(4px); + } + to { + opacity: 1; + transform: none; + } +} + +.vfx-studio__hint { + margin: 0 0 0.75rem; + font-size: 0.72rem; + line-height: 1.4; + color: var(--vs-dim); +} + +.vfx-studio__section { + margin-bottom: 0.85rem; +} + +.vfx-studio__section-title { + margin: 0 0 0.4rem; + font-size: 0.68rem; + font-weight: 650; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--vs-accent); + opacity: 0.9; +} + +/* Cards / samples grid */ +.vfx-studio__grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 0.45rem; +} + +.vfx-studio__grid--3 { + grid-template-columns: repeat(3, 1fr); +} + +.vfx-studio__card { + appearance: none; + text-align: left; + border: 1px solid var(--vs-border); + background: var(--vs-panel); + border-radius: 8px; + padding: 0.45rem; + color: inherit; + cursor: pointer; + transition: border-color 0.12s, transform 0.12s; +} + +.vfx-studio__card:hover { + border-color: rgba(127, 214, 255, 0.4); + transform: translateY(-1px); +} + +.vfx-studio__card.is-selected { + border-color: var(--vs-accent); + box-shadow: 0 0 0 1px rgba(127, 214, 255, 0.2); +} + +.vfx-studio__card-swatch { + height: 42px; + border-radius: 5px; + margin-bottom: 0.35rem; + border: 1px solid rgba(255, 255, 255, 0.08); + background-size: cover; + background-position: center; +} + +.vfx-studio__card-label { + display: block; + font-size: 0.72rem; + font-weight: 600; +} + +.vfx-studio__card-meta { + display: block; + font-size: 0.62rem; + color: var(--vs-dim); + margin-top: 0.1rem; + line-height: 1.3; +} + +/* Delivery chips */ +.vfx-studio__chips { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; +} + +.vfx-studio__chip { + appearance: none; + border: 1px solid var(--vs-border); + background: rgba(255, 255, 255, 0.03); + color: var(--vs-text); + border-radius: 999px; + padding: 0.3rem 0.65rem; + font-size: 0.7rem; + font-weight: 600; + cursor: pointer; +} + +.vfx-studio__chip.is-selected, +.vfx-studio__chip:hover { + border-color: var(--vs-accent); + color: var(--vs-accent); + background: var(--vs-accent-dim); +} + +/* Pipeline steps */ +.vfx-studio__steps { + list-style: none; + margin: 0; + padding: 0; + counter-reset: vs-step; +} + +.vfx-studio__steps li { + counter-increment: vs-step; + position: relative; + padding: 0.45rem 0.5rem 0.45rem 2rem; + margin-bottom: 0.35rem; + border: 1px solid var(--vs-border); + border-radius: 8px; + font-size: 0.72rem; + line-height: 1.35; + background: rgba(0, 0, 0, 0.2); +} + +.vfx-studio__steps li::before { + content: counter(vs-step); + position: absolute; + left: 0.45rem; + top: 0.45rem; + width: 1.15rem; + height: 1.15rem; + border-radius: 50%; + background: var(--vs-accent-dim); + color: var(--vs-accent); + font-size: 0.62rem; + font-weight: 700; + display: grid; + place-items: center; +} + +.vfx-studio__steps b { + color: var(--vs-accent); + font-weight: 650; +} + +/* Skill form */ +.vfx-studio__form { + display: grid; + gap: 0.4rem; +} + +.vfx-studio__field { + display: grid; + grid-template-columns: 7.5rem 1fr; + gap: 0.35rem; + align-items: center; + font-size: 0.7rem; +} + +.vfx-studio__field label { + color: var(--vs-dim); +} + +.vfx-studio__field input, +.vfx-studio__field select { + appearance: none; + width: 100%; + border: 1px solid var(--vs-border); + background: rgba(0, 0, 0, 0.35); + color: var(--vs-text); + border-radius: 6px; + padding: 0.3rem 0.4rem; + font-size: 0.72rem; +} + +.vfx-studio__field input:focus, +.vfx-studio__field select:focus { + outline: none; + border-color: var(--vs-accent); +} + +/* Actions */ +.vfx-studio__actions { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + margin-top: 0.65rem; +} + +.vfx-studio__btn { + appearance: none; + border: 1px solid var(--vs-border); + background: rgba(255, 255, 255, 0.06); + color: var(--vs-text); + border-radius: 7px; + padding: 0.4rem 0.7rem; + font-size: 0.72rem; + font-weight: 600; + cursor: pointer; +} + +.vfx-studio__btn:hover { + border-color: var(--vs-accent); + color: var(--vs-accent); +} + +.vfx-studio__btn--primary { + background: linear-gradient(180deg, rgba(80, 160, 220, 0.35), rgba(40, 90, 140, 0.45)); + border-color: rgba(127, 214, 255, 0.35); + color: #e8f6ff; +} + +/* Knobs host — lil-gui reparented here */ +.vfx-studio__knobs-host { + min-height: 200px; +} + +.vfx-studio__knobs-host .lil-gui { + --background-color: transparent !important; + --widget-color: rgba(255, 255, 255, 0.08) !important; + --focus-color: #7fd6ff !important; + --number-color: #a8e0ff !important; + --string-color: #c8f0c0 !important; + position: relative !important; + top: auto !important; + right: auto !important; + max-height: none !important; + width: 100% !important; + box-shadow: none !important; + border: none !important; +} + +.vfx-studio__knobs-host .lil-gui.root > .title { + display: none; /* studio head is the title */ +} + +/* Catalog list */ +.vfx-studio__list { + display: flex; + flex-direction: column; + gap: 0.3rem; + max-height: 280px; + overflow: auto; +} + +.vfx-studio__list-item { + display: flex; + align-items: center; + gap: 0.45rem; + padding: 0.35rem 0.45rem; + border: 1px solid var(--vs-border); + border-radius: 7px; + background: rgba(0, 0, 0, 0.2); + cursor: pointer; + font-size: 0.7rem; +} + +.vfx-studio__list-item:hover, +.vfx-studio__list-item.is-selected { + border-color: rgba(127, 214, 255, 0.4); +} + +.vfx-studio__dot { + width: 10px; + height: 10px; + border-radius: 50%; + flex-shrink: 0; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.vfx-studio__footer { + flex-shrink: 0; + padding: 0.4rem 0.75rem; + border-top: 1px solid var(--vs-border); + font-size: 0.62rem; + color: var(--vs-dim); + display: flex; + justify-content: space-between; + gap: 0.5rem; +} + +/* When studio open, hide floating lil-gui default position if not reparented yet */ +body.vfx-studio-open .lil-gui.root:not(.vfx-studio-docked) { + /* still show if knobs tab not visited — Editor manages show */ +} + +/* Compact on short screens */ +@media (max-height: 700px) { + .vfx-studio { + --vs-max-h: 94vh; + top: 4px; + right: 4px; + } +} diff --git a/src/ui/warlords-dev-ui.css b/src/ui/warlords-dev-ui.css new file mode 100644 index 0000000..7b859f6 --- /dev/null +++ b/src/ui/warlords-dev-ui.css @@ -0,0 +1,210 @@ +/** + * Warlords Dev Island UI — inventory shells + button atlas + mini bag. + * Textures via CSS vars from warlordsUiSkin.js. + */ + +/* ── Mini inventory (DropBag / quick bag) ───────────────────────── */ + +.drop-bag.wl-mini-inv { + --wl-slot: 40px; + bottom: 120px; + left: 50%; + transform: translateX(-50%); + width: min(720px, calc(100vw - 24px)); + max-height: min(52vh, 420px); + background: + linear-gradient(180deg, rgba(20, 12, 8, 0.55), rgba(12, 8, 6, 0.72)), + var(--wl-mini-inv-shell) center top / 100% auto no-repeat, + #1a120e; + border: 2px solid #5c3d28; + border-radius: 10px; + box-shadow: + 0 0 0 1px #2a1a10, + 0 16px 48px rgba(0, 0, 0, 0.65), + inset 0 0 40px rgba(0, 0, 0, 0.35); + backdrop-filter: none; + color: #e8d4b8; +} + +.drop-bag.wl-mini-inv .drop-bag__head { + border-bottom: 1px solid #6b4a32; + background: linear-gradient(180deg, rgba(80, 50, 30, 0.55), transparent); + padding: 10px 14px 8px; +} + +.drop-bag.wl-mini-inv .drop-bag__head h3 { + font-size: 12px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: #f0dcc0; + text-shadow: 0 1px 2px #000; +} + +.drop-bag.wl-mini-inv .drop-bag__close { + color: #c9a87a; + width: 28px; + height: 28px; + border-radius: 4px; + background: rgba(0, 0, 0, 0.35); + border: 1px solid #6b4a32; +} + +.drop-bag.wl-mini-inv .drop-bag__close:hover { + color: #fff; + background: rgba(120, 60, 30, 0.5); +} + +.drop-bag.wl-mini-inv .drop-bag__hint { + color: #a89070; + margin: 4px 14px 2px; +} + +/* 9×4 bag grid (matches miniinventory.png) */ +.drop-bag.wl-mini-inv .drop-bag__grid { + display: grid; + grid-template-columns: repeat(9, minmax(0, 1fr)); + gap: 4px; + padding: 10px 14px 8px; + overflow: auto; + min-height: 160px; +} + +.drop-bag.wl-mini-inv .drop-bag__slot { + min-height: var(--wl-slot); + aspect-ratio: 1; + border-radius: 4px; + border: 1px solid #3d2a1c; + background: + linear-gradient(160deg, rgba(40, 28, 18, 0.9), rgba(18, 12, 8, 0.95)); + box-shadow: + inset 0 0 0 1px rgba(90, 60, 40, 0.45), + 0 0 8px color-mix(in srgb, var(--tier, #9aa3ad) 40%, transparent); + padding: 3px; + gap: 0; +} + +.drop-bag.wl-mini-inv .drop-bag__slot:hover { + border-color: #c9a87a; + filter: brightness(1.08); +} + +.drop-bag.wl-mini-inv .drop-bag__slot img { + width: 70%; + height: 70%; + margin: auto; +} + +.drop-bag.wl-mini-inv .drop-bag__name { + display: none; /* dense mini grid — tooltip title only */ +} + +.drop-bag.wl-mini-inv .drop-bag__qty { + font-size: 9px; + top: 1px; + right: 2px; +} + +.drop-bag.wl-mini-inv .drop-bag__empty { + grid-column: 1 / -1; + text-align: center; + color: #8a7058; + padding: 24px 8px; +} + +/* Hotbar strip under bag (labels 1–10) */ +.drop-bag.wl-mini-inv .drop-bag__hotbar { + display: grid; + grid-template-columns: repeat(10, minmax(0, 1fr)); + gap: 4px; + padding: 4px 14px 12px; + border-top: 1px solid #4a3220; + background: rgba(0, 0, 0, 0.25); +} + +.drop-bag.wl-mini-inv .drop-bag__hotbar-slot { + position: relative; + aspect-ratio: 1; + min-height: 36px; + border-radius: 3px; + border: 1px solid #3d2a1c; + background: rgba(12, 8, 6, 0.85); + display: flex; + align-items: center; + justify-content: center; +} + +.drop-bag.wl-mini-inv .drop-bag__hotbar-slot::before { + content: attr(data-n); + position: absolute; + top: -10px; + left: 50%; + transform: translateX(-50%); + font-size: 8px; + color: #a89070; + background: #2a1c12; + padding: 0 3px; + border-radius: 2px; + border: 1px solid #5c4030; +} + +/* ── Main Panel: inventory.png shell accent ─────────────────────── */ + +.inv-panel--main.wl-inv-shell { + background: + linear-gradient(165deg, rgba(12, 20, 32, 0.92) 0%, rgba(8, 14, 22, 0.94) 100%), + var(--wl-inv-shell) right 12px bottom 12px / min(220px, 28%) auto no-repeat; +} + +.inv-panel--main.wl-inv-shell .mp-bag-grid, +.inv-panel--main.wl-inv-shell [data-bag-grid] { + /* denser slots with wood-tint when bag tab active */ + gap: 6px; +} + +.inv-panel--main.wl-inv-shell .mp-slot, +.inv-panel--main.wl-inv-shell .inv-slot { + border-color: #3a5a78; + background: + linear-gradient(160deg, rgba(20, 32, 48, 0.9), rgba(10, 16, 24, 0.95)); +} + +/* ── Button atlas utility icons ─────────────────────────────────── */ + +.wl-btn-icon { + display: inline-block; + width: 24px; + height: 24px; + background-image: var(--wl-btn-atlas); + background-repeat: no-repeat; + background-size: 192px 192px; + image-rendering: pixelated; + vertical-align: middle; + flex-shrink: 0; +} + +/* Common chrome (row-major 8×8 atlas) — pick cells used in HUD menus */ +.wl-btn-icon--close { + background-position: -168px 0; /* approx last cell row0 */ +} +.wl-btn-icon--bag { + background-position: 0 0; +} +.wl-btn-icon--gear { + background-position: -144px -144px; +} + +/* Admin / menu buttons with wood + atlas */ +.admin-btn.wl-chrome, +.hud__menu button.wl-chrome { + border-image: none; + border: 1px solid #5c4030; + background: linear-gradient(180deg, #3a2818, #24180e); + color: #f0dcc0; + image-rendering: auto; +} + +.admin-btn.wl-chrome:hover, +.hud__menu button.wl-chrome:hover { + border-color: #c9a87a; + filter: brightness(1.1); +} diff --git a/src/ui/warlordsCursors.js b/src/ui/warlordsCursors.js new file mode 100644 index 0000000..9e389fc --- /dev/null +++ b/src/ui/warlordsCursors.js @@ -0,0 +1,443 @@ +/** + * Pirate Pack cursors — Warlords / casting mouse-intent theme. + * + * Pack: public/ui/warlords-dev/cursors/pirate/MouseIcon1…10.png + * Display size ~28–32 px (near OS arrow) — source art is large; we bake downscaled + * blob URLs so CSS cursor matches normal mouse size. + * + * Tooltips: LMB / RMB action lines follow the pointer (animate on intent change). + * Focus combat still hides OS cursor for crosshair. + */ + +import { warlordsUiUrl } from './warlordsUiSkin.js'; + +/** @typedef {keyof typeof CURSOR_INTENTS | 'default' | 'none'} CursorIntentId */ + +/** + * Intent → pack file + LMB/RMB product actions. + * Hotspot in source px is remapped to display size after scale. + */ +export const CURSOR_INTENTS = Object.freeze({ + default: { + file: 'MouseIcon2.png', + hotspot: [8, 4], + label: 'Ready', + lmb: 'Select target', + rmb: 'Focus look (toggle)' + }, + attack: { + file: 'MouseIcon2.png', + hotspot: [8, 4], + label: 'Hostile', + lmb: 'Select / attack (focus)', + rmb: 'Focus · soft lock' + }, + slash: { + file: 'MouseIcon3.png', + hotspot: [10, 6], + label: 'Strike', + lmb: 'Primary attack', + rmb: 'Focus look' + }, + enter_ship: { + file: 'MouseIcon1.png', + hotspot: [16, 6], + label: 'Board ship', + lmb: 'Board / enter', + rmb: 'Focus look' + }, + sail: { + file: 'MouseIcon6.png', + hotspot: [12, 12], + label: 'Sail', + lmb: 'Helm / use', + rmb: 'Focus look' + }, + door: { + file: 'MouseIcon4.png', + hotspot: [12, 6], + label: 'Door', + lmb: 'Open / enter', + rmb: 'Focus look' + }, + loot: { + file: 'MouseIcon7.png', + hotspot: [14, 14], + label: 'Loot', + lmb: 'Open / select', + rmb: 'Focus look' + }, + look: { + file: 'MouseIcon5.png', + hotspot: [6, 18], + label: 'Inspect', + lmb: 'Examine', + rmb: 'Focus look' + }, + harvest: { + file: 'MouseIcon8.png', + hotspot: [10, 6], + label: 'Harvest', + lmb: 'Select node', + rmb: 'Focus look', + extra: 'F · harvest nearest (tool)' + }, + talk: { + file: 'MouseIcon9.png', + hotspot: [12, 12], + label: 'Talk', + lmb: 'Dialogue', + rmb: 'Focus look' + }, + use: { + file: 'MouseIcon10.png', + hotspot: [12, 12], + label: 'Use', + lmb: 'Interact', + rmb: 'Focus look' + }, + pickup: { + file: 'MouseIcon7.png', + hotspot: [14, 14], + label: 'Pickup', + lmb: 'Select drop', + rmb: 'Focus look', + extra: 'F · pick up' + }, + /** Path draw / freeride cast (unlocked) */ + draw: { + file: 'MouseIcon5.png', + hotspot: [6, 18], + label: 'Draw path', + lmb: 'Draw cast / course', + rmb: 'Focus look' + }, + /** Soft select idle */ + select: { + file: 'MouseIcon2.png', + hotspot: [8, 4], + label: 'Select', + lmb: 'Select target', + rmb: 'Focus look (toggle)' + } +}); + +const BASE = 'cursors/pirate'; +/** Target display size (px) — OS arrows are typically 16–32 */ +const DISPLAY_SIZE = 28; +const SOURCE_HINT = 128; // pack icons are large; scale factor from this if unknown + +/** @type {{ theme: string, enabled: boolean, intent: string, target: HTMLElement|null, tipEl: HTMLElement|null, tipVisible: boolean }} */ +const state = { + theme: 'pirate', + enabled: true, + intent: 'default', + target: null, + tipEl: null, + tipVisible: false, + /** @type {Map} */ + baked: new Map() +}; + +/** + * @param {string} file + */ +function cursorUrl(file) { + return warlordsUiUrl(`${BASE}/${file}`); +} + +/** + * Bake a small cursor from source PNG (blob URL). + * @param {string} file + * @param {[number, number]} hotspotSrc + * @returns {Promise<{ url: string, hx: number, hy: number }>} + */ +async function bakeCursor(file, hotspotSrc) { + const key = file; + if (state.baked.has(key)) return state.baked.get(key); + + const src = cursorUrl(file); + const img = await new Promise((resolve, reject) => { + const i = new Image(); + i.crossOrigin = 'anonymous'; + i.onload = () => resolve(i); + i.onerror = () => reject(new Error(`cursor load ${file}`)); + i.src = src; + }).catch(() => null); + + if (!img) { + const fallback = { url: src, hx: 4, hy: 2 }; + state.baked.set(key, fallback); + return fallback; + } + + const sw = img.naturalWidth || img.width || SOURCE_HINT; + const sh = img.naturalHeight || img.height || SOURCE_HINT; + const size = DISPLAY_SIZE; + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const ctx = canvas.getContext('2d'); + ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingQuality = 'high'; + ctx.clearRect(0, 0, size, size); + ctx.drawImage(img, 0, 0, sw, sh, 0, 0, size, size); + + const scale = size / Math.max(sw, sh, 1); + const hx = Math.max(0, Math.min(size - 1, Math.round((hotspotSrc[0] || 8) * scale))); + const hy = Math.max(0, Math.min(size - 1, Math.round((hotspotSrc[1] || 4) * scale))); + + const url = canvas.toDataURL('image/png'); + const baked = { url, hx, hy }; + state.baked.set(key, baked); + return baked; +} + +/** + * @param {string} intentId + * @returns {string} CSS cursor value + */ +export function cursorCssForIntent(intentId) { + if (intentId === 'none') return 'none'; + const def = CURSOR_INTENTS[intentId] || CURSOR_INTENTS.default; + const baked = state.baked.get(def.file); + if (baked) { + return `url("${baked.url}") ${baked.hx} ${baked.hy}, auto`; + } + // Pre-bake fallback: raw file with small hotspot (may be large until preload) + const url = cursorUrl(def.file); + return `url("${url}") 4 2, auto`; +} + +/** + * Ensure tip DOM exists. + */ +function ensureTipEl() { + if (state.tipEl) return state.tipEl; + const el = document.createElement('div'); + el.id = 'wl-cursor-tip'; + el.className = 'wl-cursor-tip'; + el.hidden = true; + el.setAttribute('aria-live', 'polite'); + document.body.appendChild(el); + state.tipEl = el; + + // Inject once + if (!document.getElementById('wl-cursor-tip-style')) { + const st = document.createElement('style'); + st.id = 'wl-cursor-tip-style'; + st.textContent = ` + .wl-cursor-tip { + position: fixed; + z-index: 120; + pointer-events: none; + min-width: 120px; + max-width: 220px; + padding: 6px 10px 7px; + border-radius: 6px; + background: rgba(10, 12, 16, 0.88); + border: 1px solid rgba(180, 160, 120, 0.45); + box-shadow: 0 6px 20px rgba(0,0,0,0.45); + color: #e8e4d8; + font: 600 11px/1.35 system-ui, Segoe UI, sans-serif; + letter-spacing: 0.02em; + opacity: 0; + transform: translate(14px, 16px) scale(0.94); + transition: opacity 0.14s ease, transform 0.16s ease; + backdrop-filter: blur(6px); + } + .wl-cursor-tip.is-on { + opacity: 1; + transform: translate(14px, 16px) scale(1); + } + .wl-cursor-tip[hidden] { display: none !important; } + .wl-cursor-tip__title { + font-size: 10px; + letter-spacing: 0.12em; + text-transform: uppercase; + color: #c9b48a; + margin-bottom: 3px; + } + .wl-cursor-tip__row { + display: flex; + gap: 6px; + align-items: baseline; + margin-top: 2px; + } + .wl-cursor-tip__key { + flex: 0 0 auto; + font-size: 9px; + font-weight: 700; + color: #7ec8ff; + min-width: 2.4em; + } + .wl-cursor-tip__act { + color: #f0ece0; + font-weight: 500; + } + .wl-cursor-tip__extra { + margin-top: 4px; + font-size: 10px; + color: #9ef0b8; + } + `; + document.head.appendChild(st); + } + + window.addEventListener( + 'pointermove', + (e) => { + if (!state.tipEl || state.tipEl.hidden) return; + state.tipEl.style.left = `${e.clientX}px`; + state.tipEl.style.top = `${e.clientY}px`; + }, + { passive: true } + ); + + return el; +} + +/** + * @param {string} intentId + * @param {{ lmb?: string, rmb?: string, extra?: string, label?: string }|null} [override] + */ +export function setCursorTooltip(intentId, override = null) { + const el = ensureTipEl(); + if (intentId === 'none' || !state.enabled) { + hideCursorTooltip(); + return; + } + const def = CURSOR_INTENTS[intentId] || CURSOR_INTENTS.default; + const label = override?.label ?? def.label; + const lmb = override?.lmb ?? def.lmb ?? 'Select'; + const rmb = override?.rmb ?? def.rmb ?? 'Focus look'; + const extra = override?.extra ?? def.extra ?? ''; + + el.innerHTML = ` +
    ${escapeHtml(label)}
    +
    LMB${escapeHtml(lmb)}
    +
    RMB${escapeHtml(rmb)}
    + ${extra ? `
    ${escapeHtml(extra)}
    ` : ''} + `; + el.hidden = false; + // reflow for animation + void el.offsetWidth; + el.classList.add('is-on'); + state.tipVisible = true; +} + +export function hideCursorTooltip() { + if (!state.tipEl) return; + state.tipEl.classList.remove('is-on'); + state.tipVisible = false; + window.setTimeout(() => { + if (!state.tipVisible && state.tipEl) state.tipEl.hidden = true; + }, 150); +} + +function escapeHtml(s) { + return String(s ?? '') + .replace(/&/g, '&') + .replace(//g, '>'); +} + +/** + * @param {{ + * theme?: string, + * enabled?: boolean, + * target?: HTMLElement|null, + * root?: HTMLElement + * }} [opts] + */ +export function configureWarlordsCursors(opts = {}) { + if (opts.theme != null) state.theme = opts.theme; + if (opts.enabled != null) state.enabled = !!opts.enabled; + if (opts.target !== undefined) state.target = opts.target; + ensureTipEl(); + if (opts.root) applyCursorTo(opts.root, state.intent); + applyCursorTo(document.body, state.intent); + if (state.target) applyCursorTo(state.target, state.intent); + return { ...state }; +} + +/** + * @param {string} intentId + * @param {{ force?: boolean, tooltip?: boolean, lmb?: string, rmb?: string, extra?: string, label?: string }} [opts] + */ +export function setCursorIntent(intentId, opts = {}) { + const id = intentId || 'default'; + const tipOn = opts.tooltip !== false; + if (!opts.force && id === state.intent) { + if (tipOn && id !== 'none') setCursorTooltip(id, opts); + return state.intent; + } + state.intent = id; + if (!state.enabled && id !== 'none') return state.intent; + + const css = cursorCssForIntent(id); + if (document.body) document.body.style.cursor = css; + if (state.target) state.target.style.cursor = css; + const canvas = document.getElementById('viewport'); + if (canvas) { + if (id === 'none') canvas.style.cursor = 'none'; + else if (canvas.style.cursor !== 'none' || opts.force) canvas.style.cursor = css; + } + + if (id === 'none' || !tipOn) hideCursorTooltip(); + else setCursorTooltip(id, opts); + + return state.intent; +} + +/** + * @param {HTMLElement|null} el + * @param {string} intentId + */ +function applyCursorTo(el, intentId) { + if (!el) return; + el.style.cursor = cursorCssForIntent(intentId); +} + +export function getCursorIntent() { + return state.intent; +} + +/** + * Map world interact kind → cursor intent (dev island). + * @param {string|null|undefined} kind + * @returns {string} + */ +export function intentFromInteractKind(kind) { + const k = String(kind || '').toLowerCase(); + if (!k || k === 'none') return 'default'; + if (/harvest|mine|ore|rock|tree|gather|herb|node/.test(k)) return 'harvest'; + if (/loot|chest|drop|coin|pickup/.test(k)) return 'pickup'; + if (/hostile|enemy|dummy|attack|combat/.test(k)) return 'attack'; + if (/ship|board|gang|anchor|hull/.test(k)) return 'enter_ship'; + if (/sail|helm|oar|windsurf|board_ride/.test(k)) return 'sail'; + if (/door|portal|gate|key/.test(k)) return 'door'; + if (/npc|talk|dialogue|quest/.test(k)) return 'talk'; + if (/look|inspect|examine|info/.test(k)) return 'look'; + if (/draw|cast|path/.test(k)) return 'draw'; + if (/use|interact/.test(k)) return 'use'; + return 'default'; +} + +/** + * Preload + bake all pirate cursors to ~28px. + */ +export async function preloadWarlordsCursors() { + const entries = Object.values(CURSOR_INTENTS); + const files = new Map(); + for (const d of entries) { + if (!files.has(d.file)) files.set(d.file, d.hotspot || [8, 4]); + } + await Promise.all( + [...files.entries()].map(([file, hs]) => bakeCursor(file, hs).catch(() => null)) + ); + // Refresh current intent with baked sizes + if (state.intent && state.intent !== 'none') { + setCursorIntent(state.intent, { force: true }); + } +} diff --git a/src/ui/warlordsUiSkin.js b/src/ui/warlordsUiSkin.js new file mode 100644 index 0000000..ca1b232 --- /dev/null +++ b/src/ui/warlordsUiSkin.js @@ -0,0 +1,132 @@ +/** + * Warlords-era Dev Island UI skin SSOT (casting lab). + * + * Author sources (Documents): + * - inventory.png → full bag / paperdoll + craft grid shell (MC-style layout ref) + * - miniinventory.png → fantasy wood mini bag 9×4 + hotbar 1–10 + * - button UI.png → pixel icon atlas for chrome / toggles + * - Pirate Pack Cursors → hover intents (not a second inventory stack) + * + * Shipped: public/ui/warlords-dev/* + * Does not replace CraftPix unit frames / hotbar — those stay craftpixUi.js. + * This pack owns bag chrome + cursor theme for gameplay interact. + * + * @see docs/WARLORDS_DEV_UI_SSOT.md · docs/MAIN_PANEL_INVENTORY_SSOT.md + */ + +import { sameOriginFleetUrl } from '../config/fleetEnv.js'; + +const LOCAL = './ui/warlords-dev'; +const CDN = 'https://assets.grudge-studio.com/ui/warlords-dev'; + +/** @type {Record} */ +export const WARLORDS_UI = Object.freeze({ + inventoryShell: 'inventory/inventory.png', + miniInventoryShell: 'inventory/miniinventory.png', + buttonAtlas: 'buttons/button-ui.png', + /** Legacy slot set still used by Main Panel grid cells */ + inventorySlotsSet: './ui/inventory/inventory-slots-set.png', + equipmentRef: './ui/inventory/equipment-reference.png' +}); + +/** + * Mini-inventory grid contract (from miniinventory.png art). + */ +export const MINI_INV = Object.freeze({ + bagCols: 9, + bagRows: 4, + bagSlots: 36, + hotbarSlots: 10, + /** Design art size */ + artW: 800, + artH: 512 +}); + +/** + * Full inventory.png layout notes (201×188 author). + * Armor doll TL · 2×2 craft + result · 9×3 bag · 9 hotbar · side tabs. + */ +export const FULL_INV = Object.freeze({ + bagCols: 9, + bagRows: 3, + bagSlots: 27, + hotbarSlots: 9, + craftGrid: 4, + craftResult: 1, + artW: 201, + artH: 188 +}); + +/** + * Button atlas is 192×192 with an 8×8 cell grid (~24px cells). + * Index = row * 8 + col (0-based). Used for CSS sprite icons. + */ +export const BUTTON_ATLAS = Object.freeze({ + size: 192, + cols: 8, + rows: 8, + cell: 24 +}); + +/** + * @param {string} rel path under ui/warlords-dev + * @param {{ preferCdn?: boolean }} [opts] + */ +export function warlordsUiUrl(rel, opts = {}) { + const clean = String(rel || '').replace(/^\/+/, ''); + if (clean.startsWith('ui/') || clean.startsWith('/')) { + return clean.startsWith('/') ? clean : `/${clean}`; + } + if (opts.preferCdn) return sameOriginFleetUrl(`${CDN}/${clean}`); + return `${LOCAL}/${clean}`; +} + +/** + * CSS custom properties for Warlords bag / button chrome. + * @param {HTMLElement} [el] + */ +export function applyWarlordsUiCssVars(el = document.documentElement) { + if (!el) return; + const inv = warlordsUiUrl(WARLORDS_UI.inventoryShell); + const mini = warlordsUiUrl(WARLORDS_UI.miniInventoryShell); + const btn = warlordsUiUrl(WARLORDS_UI.buttonAtlas); + el.style.setProperty('--wl-inv-shell', `url("${inv}")`); + el.style.setProperty('--wl-mini-inv-shell', `url("${mini}")`); + el.style.setProperty('--wl-btn-atlas', `url("${btn}")`); + el.style.setProperty('--wl-inv-slots', `url("${WARLORDS_UI.inventorySlotsSet}")`); + el.classList.add('wl-ui-skin'); +} + +/** + * Preload bag shells so first open is instant. + * @returns {Promise} + */ +export function preloadWarlordsUi() { + const urls = [ + warlordsUiUrl(WARLORDS_UI.inventoryShell), + warlordsUiUrl(WARLORDS_UI.miniInventoryShell), + warlordsUiUrl(WARLORDS_UI.buttonAtlas) + ]; + return Promise.all( + urls.map( + (u) => + new Promise((resolve) => { + const img = new Image(); + img.onload = img.onerror = () => resolve(); + img.src = u; + }) + ) + ).then(() => undefined); +} + +/** + * CSS background-position for a button-atlas cell. + * @param {number} col 0..7 + * @param {number} row 0..7 + */ +export function buttonAtlasPos(col, row) { + const c = Math.max(0, Math.min(7, col | 0)); + const r = Math.max(0, Math.min(7, row | 0)); + const cell = BUTTON_ATLAS.cell; + return `-${c * cell}px -${r * cell}px`; +} diff --git a/src/vfx/DodgeAfterimage.js b/src/vfx/DodgeAfterimage.js new file mode 100644 index 0000000..b908cb2 --- /dev/null +++ b/src/vfx/DodgeAfterimage.js @@ -0,0 +1,260 @@ +import { + AdditiveBlending, + Color, + DoubleSide, + Group, + MathUtils, + MeshBasicMaterial, + NormalBlending, + Vector3 +} from 'three'; +import { clone as skeletonClone } from 'three/addons/utils/SkeletonUtils.js'; +import { settings } from '../config/settings.js'; +import { LAYER } from '../core/Layers.js'; + +const _dir = new Vector3(); +const _pos = new Vector3(); +const _from = new Vector3(); +const _c = new Color(); + +/** + * Dodge afterimage — trailing copies of the **model itself** (blur of own colors). + * + * Not a flat cyan tint: each skinned mesh keeps a soft read of its map/color, + * then dissipates **vaporously** (rise, expand, opacity falloff). + * + * Pattern from Animator afterimage + Casting wind residual fade feel. + */ +export class DodgeAfterimage { + /** + * @param {import('three').Scene} scene + */ + constructor(scene) { + this.scene = scene; + /** @type {{ root: Group, mats: import('three').Material[], age: number, life: number, baseOpacity: number, baseScale: number }[]} */ + this._active = []; + this._stampAcc = 0; + } + + /** + * @param {import('three').Object3D} source + * @param {import('three').Vector3} from + * @param {import('three').Vector3} dir + * @param {number} distanceM + * @param {{ count?: number, life?: number }} [opts] + */ + spawnPath(source, from, dir, distanceM, opts = {}) { + if (!source || !from || distanceM < 0.05) return; + const cfg = settings.drc?.afterimage || {}; + const count = Math.max(2, Math.min(10, opts.count ?? cfg.count ?? 6)); + const life = opts.life ?? cfg.life ?? 0.55; + + _dir.copy(dir); + _dir.y = 0; + if (_dir.lengthSq() < 1e-6) _dir.set(0, 0, 1); + else _dir.normalize(); + + _from.copy(from); + const yaw = this._sourceYaw(source); + + for (let i = 0; i < count; i++) { + const f = (i + 1) / (count + 1); + _pos.copy(_from).addScaledVector(_dir, distanceM * f); + // Later ghosts thinner / shorter life (vapor chain) + const op = 0.42 * (1 - i / (count + 0.5)); + this._spawnGhost(source, _pos, yaw, op, life * (0.75 + f * 0.35)); + } + } + + /** + * @param {import('three').Object3D} source + * @param {import('three').Vector3} worldPos + * @param {number} [yaw] + */ + stamp(source, worldPos, yaw) { + if (!source || !worldPos) return; + const cfg = settings.drc?.afterimage || {}; + const life = cfg.stampLife ?? 0.32; + this._spawnGhost(source, worldPos, yaw ?? this._sourceYaw(source), 0.32, life); + } + + /** + * @param {number} dt + * @param {boolean} active + * @param {import('three').Object3D|null} source + * @param {import('three').Vector3|null} worldPos + * @param {number} [yaw] + */ + updateTrail(dt, active, source, worldPos, yaw) { + this.update(dt); + if (!active || !source || !worldPos) { + this._stampAcc = 0; + return; + } + const interval = settings.drc?.afterimage?.stampInterval ?? 0.048; + this._stampAcc += dt; + while (this._stampAcc >= interval) { + this._stampAcc -= interval; + this.stamp(source, worldPos, yaw); + } + } + + /** Vapor dissipate: opacity falloff + rise + soft expand. */ + update(dt) { + const cfg = settings.drc?.afterimage || {}; + const rise = cfg.vaporRise ?? 0.55; + const expand = cfg.vaporExpand ?? 0.28; + + for (let i = this._active.length - 1; i >= 0; i--) { + const g = this._active[i]; + g.age += dt; + const t = MathUtils.clamp(g.age / g.life, 0, 1); + if (t >= 1) { + this._disposeGhost(g); + this._active.splice(i, 1); + continue; + } + // Vapor curve: holds briefly then softens (ease-in-out power) + const hold = cfg.vaporHold ?? 0.12; + const u = t < hold ? 0 : (t - hold) / (1 - hold); + const fade = Math.pow(1 - u, cfg.vaporPower ?? 2.4); + const op = g.baseOpacity * fade; + + for (const mat of g.mats) { + mat.opacity = op; + } + + // Expand + drift up like steam + const s = g.baseScale * (1 + expand * u * u); + g.root.scale.setScalar(s); + g.root.position.y = g.baseY + rise * u * u; + } + } + + dispose() { + for (const g of this._active) this._disposeGhost(g); + this._active.length = 0; + } + + /* ------------------------------------------------------------------ */ + + _sourceYaw(source) { + return source.rotation?.y ?? source.parent?.rotation?.y ?? 0; + } + + /** + * Pull a soft tint from a source material (map keeps silhouette of gear colors). + * @param {import('three').Material|import('three').Material[]|null} srcMat + * @param {number} opacity + * @returns {MeshBasicMaterial} + */ + _ghostMaterialFrom(srcMat, opacity) { + const src = Array.isArray(srcMat) ? srcMat[0] : srcMat; + const map = src?.map || null; + if (src?.color) _c.copy(src.color); + else _c.setHex(0xc8c0b8); + // Soften toward mid-grey so additive doesn't blow out + _c.lerp(new Color(0xb0aaa4), 0.15); + + const mat = new MeshBasicMaterial({ + color: _c.clone(), + map: map || null, + transparent: true, + opacity, + // Soft vapor: mostly normal so model colors show, slight additive lift + blending: map ? NormalBlending : AdditiveBlending, + depthWrite: false, + side: DoubleSide, + toneMapped: false, + fog: false + }); + if (map) { + mat.map = map; + // Do not dispose shared map on ghost death + mat.userData.sharedMap = true; + } + return mat; + } + + /** + * @param {import('three').Object3D} source + * @param {import('three').Vector3} pos + * @param {number} yaw + * @param {number} opacity + * @param {number} life + */ + _spawnGhost(source, pos, yaw, opacity, life) { + let ghost; + try { + ghost = skeletonClone(source); + } catch { + ghost = source.clone(true); + } + + ghost.position.set(0, 0, 0); + ghost.rotation.set(0, 0, 0); + ghost.scale.copy(source.scale); + + /** @type {import('three').Material[]} */ + const mats = []; + + ghost.traverse((o) => { + if (o.isLight || o.isCamera) { + o.visible = false; + return; + } + if (!o.isMesh && !o.isSkinnedMesh) return; + o.layers?.set?.(LAYER.VFX); + o.frustumCulled = false; + o.castShadow = false; + o.receiveShadow = false; + + const srcMats = Array.isArray(o.material) ? o.material : [o.material]; + if (srcMats.length > 1) { + const multi = srcMats.map((m) => { + const g = this._ghostMaterialFrom(m, opacity); + mats.push(g); + return g; + }); + o.material = multi; + } else { + const g = this._ghostMaterialFrom(srcMats[0], opacity); + mats.push(g); + o.material = g; + } + }); + + const root = new Group(); + root.name = 'DodgeAfterimage'; + root.position.copy(pos); + root.rotation.set(0, yaw, 0); + root.add(ghost); + this.scene.add(root); + + const baseScale = root.scale.x || 1; + this._active.push({ + root, + mats, + age: 0, + life: Math.max(0.1, life), + baseOpacity: opacity, + baseScale, + baseY: pos.y + }); + } + + _disposeGhost(g) { + this.scene.remove(g.root); + g.root.traverse((o) => { + if (o.isMesh || o.isSkinnedMesh) { + o.geometry = null; + o.skeleton = null; + } + }); + for (const mat of g.mats) { + // Never dispose shared textures from live hero + if (mat.map && mat.userData?.sharedMap) mat.map = null; + mat.dispose(); + } + } +} diff --git a/src/vfx/VfxDirector.js b/src/vfx/VfxDirector.js new file mode 100644 index 0000000..af991c0 --- /dev/null +++ b/src/vfx/VfxDirector.js @@ -0,0 +1,883 @@ +import { Color, Vector3 } from 'three'; +import { BurstMode } from '../effects/BurstSphere.js'; +import { DecalType } from '../effects/GroundDecals.js'; +import { ParticleShape } from '../particles/ParticleSystem.js'; +import { RateEmitter } from '../particles/ParticleEngine.js'; +import { frame } from '../core/FrameUniforms.js'; +import { settings } from '../config/settings.js'; +import { getColor } from '../utils/color.js'; +import { + ELEMENT_EFFECT_MAP, + SKILL_VFX_BIND, + VFX_SANDBOX_SHORTCUTS, + vfxCatalogById +} from './vfxCatalog.js'; +import { DodgeAfterimage } from './DodgeAfterimage.js'; +import { + presentationFor, + HYBRID_SPIKE_OVERLAY +} from '../combat/elementPresentation.js'; +import { normalizeElement } from '../combat/elementWeaponSkills.js'; + +const _p = new Vector3(); +const _f = new Vector3(); +const _g = new Vector3(); +const _c = new Color(); +const _emit = {}; + +/** + * High-beauty VFX director for weapon skills — layers particles, bursts, + * decals, lights, shake, flash using this app's existing systems. + * Catalog / hotkeys aligned with https://vfxgrudge.puter.site/ + * + * Also owns dodge MM afterimage trails (model trailing images). + */ +export class VfxDirector { + /** + * @param {object} ctx { scene, particles, lights, decals, bursts, shake, flash } + */ + constructor(ctx) { + this.ctx = ctx; + this._systems = new Map(); + this._bootSystems(); + /** @type {DodgeAfterimage|null} */ + this.afterimages = ctx.scene ? new DodgeAfterimage(ctx.scene) : null; + } + + _bootSystems() { + const make = (id, opts) => { + const sys = this.ctx.particles.get(`vfx.${id}`, { + capacity: opts.capacity ?? 600, + shape: opts.shape ?? ParticleShape.SOFT, + additive: opts.additive !== false, + swirl: !!opts.swirl, + softFade: opts.softFade ?? 0.35 + }); + if (opts.gravity) sys.uniforms.uGravity.value.set(0, opts.gravity, 0); + if (opts.drag != null) sys.uniforms.uDrag.value = opts.drag; + this._systems.set(id, { sys, emitter: new RateEmitter() }); + return sys; + }; + make('ember', { capacity: 800, swirl: true, gravity: -0.4 }); + make('frost', { capacity: 700, swirl: true, gravity: -0.15 }); + make('spark', { capacity: 500, gravity: -1.2, softFade: 0.5 }); + make('mote', { capacity: 600, swirl: true, gravity: 0.05 }); + make('dust', { capacity: 500, additive: false, gravity: -0.8 }); + } + + /** + * Deploy a catalog effect at origin with optional forward/aim. + * @param {string} effectId + * @param {{ origin: Vector3, forward?: Vector3, aim?: Vector3, intensity?: number }} opts + */ + deploy(effectId, opts) { + const entry = vfxCatalogById(effectId); + // Live editor knobs (settings.effect) override catalog defaults when set + const e = settings.effect || {}; + let color = entry?.color ?? 0xffffff; + if (opts.color) { + color = typeof opts.color === 'number' ? opts.color : new Color(opts.color).getHex(); + } else if (e.color && effectId === 'getsuga_slash') { + color = new Color(e.color).getHex(); + } + const origin = opts.origin.clone(); + const fwd = (opts.forward || new Vector3(0, 0, 1)).clone(); + fwd.y = 0; + if (fwd.lengthSq() < 1e-6) fwd.set(0, 0, 1); + else fwd.normalize(); + + const sizeMul = opts.size ?? e.size ?? 1; + const aoeMul = opts.aoe ?? e.aoe ?? 1; + const cast = origin.clone(); + // When origin is already weapon tip (residual), do not lift to chest + if (opts.fromTip) { + /* keep cast = tip */ + } else if (effectId !== 'getsuga_slash') { + cast.y = origin.y + 1.15; + } + const front = cast.clone().addScaledVector(fwd, 1.15 * sizeMul); + const ground = new Vector3(origin.x, 0.04, origin.z); + const aim = opts.aim?.clone() || front.clone().addScaledVector(fwd, 6); + const intensity = + (opts.intensity ?? e.intensity ?? 1) * + (settings.global?.glow ?? 1) * + (settings.global?.explosionIntensity ?? 1); + + switch (effectId) { + case 'ice_lightning_burst': + this._frostPlate(ground, 3.2, color, intensity); + this._nova(front, color, intensity * 1.1); + this._burst(front, color, 32, 4.2, intensity); + this._sparks(front, 0x7ec8ff, 40, 5, intensity); + this.ctx.shake?.add(0.045 * intensity, 0.9, 22); + this._flash(color, 0.07); + break; + case 'moon_beam': { + const beam = aim.clone(); + beam.y = 0.08; + this._auraRing(beam, color, 1.7, intensity); + this._castAura(beam.clone().setY(1.3), color, intensity); + this._burst(beam.clone().setY(2.0), color, 22, 2.6, intensity); + this._nova(beam.clone().setY(1.5), 0xe8f4ff, intensity * 0.9); + this._flash(color, 0.05); + break; + } + case 'frost_wave': + this._frostPlate(ground, 4.4, color, intensity * 1.15); + this._shockwave(ground, color, 4.6, intensity); + this._burst(front, color, 26, 3.6, intensity); + this.ctx.shake?.add(0.035 * intensity, 0.85, 18); + break; + case 'fire_aura': + this._castAura(cast, color, intensity); + this._auraRing(ground, color, 2.5, intensity); + this._embers(cast, 50, intensity); + break; + case 'earth_surge': + this._shockwave(ground, 0xc4a574, 5.0, intensity); + this._shockwave(ground.clone().setY(0.07), 0x8b7355, 3.6, intensity * 0.85); + this._dustBurst(ground, 36, intensity); + this._burst(front, 0xc4a574, 28, 4.0, intensity); + this.ctx.shake?.add(0.055 * intensity, 0.95, 16); + break; + case 'fireball': + this._castAura(cast, color, intensity * 0.9); + this._embers(cast, 30, intensity); + this._projectileTell(cast, fwd, color, intensity); + break; + case 'inferno': + this._nova(front, color, intensity * 1.25); + this._burst(front, color, 48, 5.5, intensity * 1.2); + this._shockwave(ground, color, 3.8, intensity); + this._embers(front, 60, intensity); + this.ctx.shake?.add(0.06 * intensity, 1.0, 20); + this._flash(color, 0.08); + break; + case 'arcane_swirl': + this._castAura(cast, color, intensity); + this._auraRing(ground, color, 1.9, intensity * 0.85); + this._motes(cast, color, 40, intensity); + break; + case 'getsuga_slash': { + // Residual: tip-spawned slash + optional ground AOE from settings.residual + const r = settings.residual || {}; + const rInt = intensity * (r.intensity ?? 1) * sizeMul; + this._slashArc(cast, fwd, color, rInt); + this._burst(front, color, 18, 2.8 * (opts.speed ? opts.speed / 12 : 1), rInt); + const aoeR = (opts.aoe ?? r.aoeRadius ?? aoeMul) * sizeMul; + if (aoeR > 0.05) { + this._shockwave(ground, color, Math.max(1.2, aoeR * 2.2), rInt * 0.85); + } + this.ctx.shake?.add(0.03 * rInt, 0.7, 26); + break; + } + case 'fire_hand': + this._castAura(cast, color, intensity * 1.1); + this._embers(cast, 36, intensity); + break; + case 'lightning_bolt': + this._deployLightningBolt(cast, aim, color, intensity, { chain: false }); + break; + case 'chain_lightning': + this._deployLightningBolt(cast, aim, color, intensity, { chain: true }); + break; + default: + this._burst(front, color, 20, 3, intensity); + break; + } + } + + /** + * Layer cast + impact beauty for a DRC skill fire. + * @param {string} skillId + * @param {{ origin: Vector3, forward: Vector3, aim?: Vector3 }} pose + * @param {'cast'|'impact'|'full'} [phase] + */ + deploySkill(skillId, pose, phase = 'full') { + const bind = SKILL_VFX_BIND[skillId]; + if (!bind) { + this.deploy('arcane_swirl', pose); + return; + } + if (phase === 'cast' || phase === 'full') { + if (bind.cast) this.deploy(bind.cast, { ...pose, intensity: 1.05 }); + } + if (phase === 'impact' || phase === 'full') { + if (bind.impact) { + const impactPose = { + origin: pose.aim || pose.origin, + forward: pose.forward, + aim: pose.aim, + intensity: 1.15 + }; + this.deploy(bind.impact, impactPose); + } + } + } + + /** + * Element ability path start — cast tell at hand. + * Accepts product elements (fire|storm|ice|nature|holy|arcane) or legacy. + */ + deployElementCast(element, pose) { + const pres = presentationFor(element); + const map = ELEMENT_EFFECT_MAP[pres.abilityKey] || ELEMENT_EFFECT_MAP[element]; + const id = pres.castEffectId || map?.cast; + if (id) this.deploy(id, { ...pose, intensity: 1, color: pres.color }); + } + + /** + * Element ability impact — beauty layer on top of Ability impact. + */ + deployElementImpact(element, pose) { + const pres = presentationFor(element); + const map = ELEMENT_EFFECT_MAP[pres.abilityKey] || ELEMENT_EFFECT_MAP[element]; + const id = pres.impactEffectId || map?.impact; + if (id) this.deploy(id, { ...pose, intensity: 1.05, color: pres.color }); + } + + /** + * Creative presentation for a product element (volley, meteor, vines, shield, …). + * Uses existing particles/bursts/decals only — no second VFX engine. + * + * @param {string} element product element id + * @param {{ origin: import('three').Vector3, forward?: import('three').Vector3, aim?: import('three').Vector3, intensity?: number, kind?: string, skillId?: string }} pose + * @param {{ meteor?: boolean, volley?: boolean, pathKind?: string }} [opts] + */ + deployPresentation(element, pose, opts = {}) { + const el = normalizeElement(element); + const pres = presentationFor(el); + const p = settings.presentation || {}; + const intensity = pose.intensity ?? 1; + const origin = pose.origin?.clone?.() || new Vector3(); + const fwd = (pose.forward || new Vector3(0, 0, 1)).clone(); + fwd.y = 0; + if (fwd.lengthSq() < 1e-6) fwd.set(0, 0, 1); + else fwd.normalize(); + const aim = pose.aim?.clone?.() || origin.clone().addScaledVector(fwd, 8); + const ground = new Vector3(origin.x, 0.04, origin.z); + const cast = origin.clone(); + cast.y += 1.15; + + // Cast tell always (storm lightning uses a snappier tell below) + if (!(pres.lightning && el === 'storm' && opts.pathKind !== 'wall')) { + this.deploy(pres.castEffectId, { + origin, + forward: fwd, + aim, + intensity: intensity * 0.9, + color: pres.color + }); + } + + // Hybrid path spikes: earth motion already from Ability — beauty overlay by element + if (opts.pathKind === 'spikes') { + const hy = HYBRID_SPIKE_OVERLAY[el]; + if (hy) { + this.deploy(hy.beauty, { + origin: aim, + forward: fwd, + aim, + intensity: intensity * 0.9, + color: hy.color + }); + } + } + + if (pres.multiShot || opts.volley) { + this._deployVolley(pres, cast, fwd, aim, intensity, p); + } + + // Meteor: fire signature / explicit skill flag (sky shards + small ground blasts) + if (opts.meteor === true || (el === 'fire' && opts.pathKind === 'stream' && intensity >= 1.35)) { + this._deployMeteor(pres, aim, intensity, p); + } + + if (pres.groundFlood || (opts.pathKind === 'aoe' && el === 'ice')) { + this._deployGroundFlood(pres, ground, aim, intensity, p); + } + + // Nature school = EarthAbility green + vine/earth_surge (catalog skills only) + if (pres.style === 'vineLash' || el === 'nature' || opts.presentation === 'vineLash') { + this._deployVineLash(pres, ground, aim, fwd, intensity, p); + } + + // Storm: offense = narrow chain lightning + wind residual; wall/shield = defensive aura + if (el === 'storm' || pres.lightning) { + const wantShield = + opts.shield === true || + opts.pathKind === 'wall' || + (pres.shield && opts.pathKind === 'wall'); + if (wantShield) { + this._deployStormShield(pres, ground, cast, intensity, p); + } + if (!wantShield || opts.pathKind === 'stream' || opts.pathKind === 'spikes' || opts.pathKind === 'aoe') { + if (pres.lightning !== false && opts.lightning !== false) { + this._deployLightningBolt(cast, aim, pres.color, intensity, { + chain: pres.chain !== false && opts.chain !== false, + wind: true + }); + } + } + } + + if (pres.style === 'voidBolt' || el === 'arcane') { + this._deployArcaneVoid(pres, cast, fwd, aim, intensity, p); + } + + if (pres.style === 'radiance' || el === 'holy') { + this.deploy('moon_beam', { + origin: aim, + forward: fwd, + aim, + intensity: intensity * 0.95, + color: pres.color + }); + if (pres.healAura) { + this._auraRing(ground, pres.color, 2.2, intensity * 0.85); + this._castAura(cast, pres.colorB || pres.color, intensity * 0.7); + } + } + + // Default soft impact when style did not already schedule one + if ( + !pres.multiShot && + el !== 'storm' && + el !== 'arcane' && + el !== 'nature' && + el !== 'ice' + ) { + this.deploy(pres.impactEffectId, { + origin: aim, + forward: fwd, + aim, + intensity: intensity * 0.85, + color: pres.color + }); + } + } + + /** + * Lightning / chain lightning — narrow, fast electric motion + optional wind residual. + * Design: white-hot core, cyan glow, zigzag segments, hop chain, short life (not fat orbs). + * + * @param {Vector3} from cast hand / chest + * @param {Vector3} to primary aim + * @param {number} color + * @param {number} intensity + * @param {{ chain?: boolean, wind?: boolean }} [opts] + */ + _deployLightningBolt(from, to, color, intensity = 1, opts = {}) { + const L = settings.presentation?.lightning || {}; + const core = new Color(L.coreColor || '#eef9ff').getHex(); + const glow = typeof color === 'number' ? color : new Color(L.glowColor || '#6ec8ff').getHex(); + const arc = new Color(L.arcColor || '#a8e8ff').getHex(); + const segs = Math.max(3, Math.round(L.segments ?? 7)); + const zigzag = L.zigzag ?? 0.28; + const boltSpeed = L.boltSpeed ?? 42; + const boltLife = L.boltLife ?? 0.14; + const coreSize = L.coreSize ?? 0.045; + const glowSize = L.glowSize ?? 0.09; + const wind = opts.wind !== false && L.windResidual !== false; + const flashStr = (L.flash ?? 0.05) * intensity * (settings.global?.flashStrength ?? 1); + + // Hand crackle (tiny, not a ball) + this._emitDirected('spark', from, to.clone().sub(from).normalize(), 10 * intensity, boltSpeed * 0.35, core, { + size: coreSize * 0.7, + life: boltLife * 0.8, + spread: 0.22 + }); + this._castAura(from, glow, intensity * 0.45); + + // Primary bolt: zigzag polyline cast → aim (narrow directed sparks) + this._electricPath(from, to, segs, zigzag, core, glow, arc, intensity, { + boltSpeed, + boltLife, + coreSize, + glowSize, + wind + }); + + // Soft impact pin (small, not inferno) + this._sparks(to, core, 14 * intensity, 5.5, intensity * 0.85); + this._burst(to, glow, 10, 2.4, intensity * 0.55); + this._nova(to.clone().setY(Math.max(0.05, to.y * 0.2 + 0.05)), arc, intensity * 0.45); + if (flashStr > 0.001) this._flash(core, flashStr); + this.ctx.shake?.add(0.028 * intensity, 1.1, 32); + + // Chain hops — fast cascade to lateral virtual targets (until real combat targets exist) + if (opts.chain !== false) { + const hops = Math.max(0, Math.round(L.chainHops ?? 3)); + const hopDelay = L.hopDelayMs ?? 38; + const hopR = L.hopRadius ?? 3.2; + const hopRange = L.hopRange ?? 5.5; + let prev = to.clone(); + const baseFwd = to.clone().sub(from); + baseFwd.y = 0; + if (baseFwd.lengthSq() < 1e-6) baseFwd.set(0, 0, 1); + else baseFwd.normalize(); + const side = new Vector3(-baseFwd.z, 0, baseFwd.x); + + for (let h = 0; h < hops; h++) { + const t = (h + 1) * hopDelay; + const lat = (Math.random() * 2 - 1) * hopR; + const along = 0.6 + Math.random() * hopRange * 0.35; + const next = prev + .clone() + .addScaledVector(side, lat) + .addScaledVector(baseFwd, along * (0.4 + Math.random() * 0.4)); + next.y = 0.9 + Math.random() * 0.6; + const fromHop = prev.clone(); + setTimeout(() => { + this._electricPath(fromHop, next, Math.max(3, segs - 1 - h), zigzag * 0.85, core, glow, arc, intensity * (0.9 - h * 0.12), { + boltSpeed: boltSpeed * 1.08, + boltLife: boltLife * 0.9, + coreSize: coreSize * (0.95 - h * 0.08), + glowSize: glowSize * (0.95 - h * 0.08), + wind + }); + this._sparks(next, core, 8 * intensity, 4.5, intensity * 0.6); + this._burst(next, glow, 6, 2.0, intensity * 0.4); + }, t); + prev = next; + } + } + } + + /** + * Draw a zigzag electric polyline with directed narrow sparks + optional wind silk residual. + */ + _electricPath(from, to, segments, zigzag, core, glow, arc, intensity, p) { + const n = Math.max(2, segments | 0); + const dir = to.clone().sub(from); + const len = dir.length() || 1; + dir.multiplyScalar(1 / len); + // Perpendicular for zigzag (prefer world up cross for visible jag) + const side = new Vector3().crossVectors(dir, new Vector3(0, 1, 0)); + if (side.lengthSq() < 1e-6) side.set(1, 0, 0); + else side.normalize(); + const up = new Vector3().crossVectors(side, dir).normalize(); + + let prev = from.clone(); + for (let i = 1; i <= n; i++) { + const u = i / n; + const jag = i < n ? (Math.random() * 2 - 1) * zigzag * (0.5 + Math.random() * 0.5) : 0; + const jagY = i < n ? (Math.random() * 2 - 1) * zigzag * 0.35 : 0; + const pt = from + .clone() + .addScaledVector(dir, len * u) + .addScaledVector(side, jag) + .addScaledVector(up, jagY); + if (i === n) pt.copy(to); + + const segDir = pt.clone().sub(prev); + const segLen = segDir.length() || 0.01; + segDir.multiplyScalar(1 / segLen); + const mid = prev.clone().lerp(pt, 0.5); + + // Core: thin, fast, short life + this._emitDirected('spark', mid, segDir, Math.max(4, 7 * intensity), p.boltSpeed, core, { + size: p.coreSize, + life: p.boltLife, + spread: 0.08, + radius: 0.04 + }); + // Glow sheath + this._emitDirected('mote', mid, segDir, Math.max(3, 5 * intensity), p.boltSpeed * 0.75, glow, { + size: p.glowSize, + life: p.boltLife * 1.15, + spread: 0.14, + radius: 0.06 + }); + // Arc fringe + this._emitDirected('frost', mid, segDir, Math.max(2, 3 * intensity), p.boltSpeed * 0.55, arc, { + size: p.glowSize * 0.7, + life: p.boltLife * 1.3, + spread: 0.2, + radius: 0.05 + }); + + // Wind residual silk — soft cyan motes lagging the bolt (WindAbility family) + if (p.wind) { + this._emitDirected('mote', mid.clone().addScaledVector(segDir, -0.15), segDir, 3 * intensity, p.boltSpeed * 0.25, 0xc9f0ff, { + size: 0.07, + life: 0.35, + spread: 0.45, + radius: 0.1 + }); + } + + prev = pt; + } + } + + /** + * Directed narrow emission for electric/arrow-like travel (low spread, high speed, short life). + */ + _emitDirected(sysId, position, direction, count, speed, color, opts = {}) { + const pack = this._systems.get(sysId); + if (!pack || count < 1) return; + const n = Math.min(48, Math.round(count * (settings.global?.particleCount ?? 1))); + _c.set(typeof color === 'number' ? color : 0xffffff); + _f.copy(direction).normalize(); + _emit.position = position; + _emit.radius = opts.radius ?? 0.05; + _emit.direction = _f; + _emit.speed = speed; + _emit.speedVariance = opts.speedVariance ?? 0.18; + _emit.spread = opts.spread ?? 0.12; + _emit.inherit = null; + _emit.anchor = position; + _emit.size = opts.size ?? 0.05; + _emit.sizeVariance = opts.sizeVariance ?? 0.25; + _emit.life = opts.life ?? 0.16; + _emit.lifeVariance = opts.lifeVariance ?? 0.2; + _emit.spin = opts.spin ?? 6; + _emit.tint = _c; + _emit.time = frame.uTime?.value ?? 0; + pack.sys.emit(n, _emit); + } + + /** Fire/arcane micro volley — first shot is bullet-sized for cheap reads. */ + _deployVolley(pres, cast, fwd, aim, intensity, p) { + const n = Math.max(1, Math.round(p.fireVolleyCount ?? 5)); + const delay = p.fireVolleyDelayMs ?? 65; + const micro = p.microBulletSize ?? 0.14; + const body = p.fireVolleySize ?? 0.32; + for (let i = 0; i < n; i++) { + const t = i * delay; + const size = i === 0 && pres.microFirst ? micro : body * (0.85 + (i % 3) * 0.08); + const lateral = ((i % 3) - 1) * 0.35; + const side = new Vector3(-fwd.z, 0, fwd.x).multiplyScalar(lateral); + const from = cast.clone().add(side).addScaledVector(fwd, 0.4 + i * 0.15); + const to = aim.clone().add(side).addScaledVector(fwd, i * 0.4); + setTimeout(() => { + this._projectileTell(from, fwd, pres.color, intensity * (0.55 + size)); + this._emitBurst('ember', from, 8 * intensity, 3 + size * 4, pres.color); + if (i === n - 1) { + this.deploy(pres.impactEffectId, { + origin: to, + forward: fwd, + aim: to, + intensity: intensity * 0.75, + color: pres.color, + size + }); + } + }, t); + } + } + + /** Sky meteor: small falling shards + staggered ground explosions (render-friendly). */ + _deployMeteor(pres, aim, intensity, p) { + const h = p.meteorHeight ?? 14; + const shards = Math.max(1, Math.round(p.meteorShards ?? 4)); + const delay = p.meteorDelayMs ?? 90; + for (let i = 0; i < shards; i++) { + const ox = (Math.random() - 0.5) * 2.4; + const oz = (Math.random() - 0.5) * 2.4; + const sky = new Vector3(aim.x + ox, h, aim.z + oz); + const hit = new Vector3(aim.x + ox * 0.4, 0.08, aim.z + oz * 0.4); + setTimeout(() => { + // Thin projectile tell from sky (not full FireAbility volume) + this._sparks(sky, pres.color, 12, 2, intensity * 0.6); + this._embers(sky, 16, intensity * 0.5); + this.deploy('fireball', { + origin: sky, + forward: new Vector3(0, -1, 0), + aim: hit, + intensity: intensity * 0.55, + color: pres.color, + size: 0.4 + }); + this.deploy('inferno', { + origin: hit, + forward: new Vector3(0, 1, 0), + aim: hit, + intensity: intensity * 0.55, + color: pres.color, + size: 0.55 + }); + }, 120 + i * delay); + } + } + + /** Ice: frost plate crawl then erupt (earth timing, water shaders). */ + _deployGroundFlood(pres, ground, aim, intensity, p) { + const r = p.iceFloodRadius ?? 4.2; + const delay = p.iceEruptDelayMs ?? 280; + this._frostPlate(ground, r * 0.55, pres.color, intensity * 0.9); + this._shockwave(ground, pres.color, r * 0.7, intensity * 0.75); + setTimeout(() => { + const up = aim.clone(); + up.y = 1.4; + this.deploy('frost_wave', { + origin: ground, + forward: new Vector3(0, 1, 0), + aim: up, + intensity: intensity * 1.05, + color: pres.color + }); + this._burst(up, pres.color, 28, 3.8, intensity); + // "swallow" ring — expanding water plate + this._frostPlate(ground, r, pres.colorB || pres.color, intensity * 1.1); + }, delay); + } + + /** Nature school (catalog): green earth_surge + vine lashes + heal aura. */ + _deployVineLash(pres, ground, aim, fwd, intensity, p) { + const n = Math.max(1, Math.round(p.natureVineCount ?? 3)); + const green = pres.color; + const dark = pres.colorB || 0x2d6b3a; + // Underground rumble (soft) + this.deploy('earth_surge', { + origin: ground, + forward: fwd, + aim, + intensity: intensity * 0.85, + color: green + }); + for (let i = 0; i < n; i++) { + const t = 80 + i * 110; + const side = new Vector3(-fwd.z, 0, fwd.x).multiplyScalar((i - (n - 1) / 2) * 0.9); + const root = ground.clone().add(side).addScaledVector(fwd, 1.2 + i * 0.8); + setTimeout(() => { + // Lash: shockwave + burst rising like water jet but earth/green + this._shockwave(root, green, 1.6, intensity * 0.8); + this._dustBurst(root, 18, intensity * 0.7); + this._burst(root.clone().setY(1.1), green, 16, 3.2, intensity); + this._emitBurst('mote', root.clone().setY(0.8), 14, 2.4, dark); + }, t); + } + if (pres.healAura && p.natureHealAura !== false) { + this._auraRing(ground, green, 2.6, intensity * 0.7); + this._castAura(ground.clone().setY(1.0), green, intensity * 0.55); + } + } + + /** Storm: defensive wind shield + spark rim. */ + _deployStormShield(pres, ground, cast, intensity, p) { + const r = p.stormShieldRadius ?? 2.4; + this._auraRing(ground, pres.color, r, intensity * 0.95); + this._auraRing(ground.clone().setY(0.08), pres.colorB || 0xe8f7ff, r * 0.72, intensity * 0.7); + this._castAura(cast, pres.color, intensity * 0.8); + this._sparks(cast, pres.color, 24, 3.5, intensity * 0.75); + this.deploy('arcane_swirl', { + origin: cast, + forward: new Vector3(0, 1, 0), + intensity: intensity * 0.65, + color: pres.color + }); + } + + /** Arcane: purple core + void black secondary. */ + _deployArcaneVoid(pres, cast, fwd, aim, intensity, p) { + const purple = pres.color; + const voidC = typeof p.arcaneCore === 'string' ? new Color(p.arcaneCore).getHex() : pres.colorB || 0x1a0a28; + this._castAura(cast, purple, intensity); + this._auraRing(new Vector3(cast.x, 0.04, cast.z), voidC, 1.6, intensity * 0.9); + this._motes(cast, purple, 36, intensity); + this._motes(cast, voidC, 18, intensity * 0.7); + this._projectileTell(cast, fwd, purple, intensity * 0.85); + setTimeout(() => { + this.deploy('inferno', { + origin: aim, + forward: fwd, + aim, + intensity: intensity * 0.7, + color: purple + }); + this._burst(aim, voidC, 20, 3.5, intensity * 0.8); + }, 220); + } + + /** Alt+hotkey sandbox preview (vfxgrudge.puter.site). */ + deployFromHotkey(code) { + const row = VFX_SANDBOX_SHORTCUTS.find((s) => s.code === code); + return row?.effectId || null; + } + + /* ---- primitives (compose beauty) ---- */ + + _flash(color, strength = 0.15) { + this.ctx.flash?.trigger?.(new Color(color), strength, 0.0005); + } + + _burst(pos, color, count, speed, intensity = 1) { + const mode = + color === 0xff6a1e || color === 0xff5510 || color === 0xff6020 + ? BurstMode.FIRE + : color === 0x9fdcff || color === 0x7ec8ff || color === 0x5fd6ff + ? BurstMode.WATER + : color === 0xc4a574 || color === 0x4ecf6a || color === 0x2d6b3a || color === 0x6bbf4a + ? BurstMode.EARTH + : color === 0xb070ff || color === 0x1a0a28 + ? BurstMode.AIR + : BurstMode.AIR; + this.ctx.bursts?.spawn(mode, pos, { + radius: 0.35 * intensity, + endRadius: 2.8 * intensity, + life: 0.55, + intensity: 1.1 * intensity * (settings.global?.glow ?? 1), + fresnel: 1.4, + displace: 0.28, + colorA: new Color(color), + colorB: new Color(color).lerp(new Color(0xffffff), 0.35), + colorC: new Color(0xffffff) + }); + this._emitBurst('ember', pos, count * intensity, speed, color); + } + + _nova(pos, color, intensity = 1) { + this.ctx.decals?.spawn(DecalType.SHOCKWAVE, _p.copy(pos).setY(0.03), { + radius: 2.4 * intensity, + life: 0.7, + width: 0.06, + intensity: 0.85 * intensity, + colorA: new Color(color), + colorB: new Color(0xffffff) + }); + } + + _shockwave(ground, color, radius, intensity = 1) { + this.ctx.decals?.spawn(DecalType.SHOCKWAVE, ground, { + radius: radius * intensity, + life: 0.85, + width: 0.08, + intensity: 0.9 * intensity, + colorA: new Color(color), + colorB: new Color(color).multiplyScalar(0.6) + }); + } + + _frostPlate(ground, radius, color, intensity = 1) { + this.ctx.decals?.spawn(DecalType.DUSTRING, ground, { + radius: radius * intensity, + life: 1.2, + intensity: 0.75 * intensity, + colorA: new Color(color), + colorB: new Color(0xffffff) + }); + this._emitBurst('frost', ground.clone().setY(0.2), 28 * intensity, 2.2, color); + } + + _auraRing(ground, color, radius, intensity = 1) { + this.ctx.decals?.spawn(DecalType.DUSTRING, ground, { + radius: radius * intensity, + life: 0.9, + intensity: 0.7 * intensity, + colorA: new Color(color), + colorB: new Color(color) + }); + } + + _castAura(pos, color, intensity = 1) { + const light = this.ctx.lights?.acquire?.(); + if (light) { + this.ctx.lights.set( + light, + pos, + new Color(color), + 8 * intensity * (settings.global?.lightIntensity ?? 1), + 5, + 0.016 + ); + // release next frames via pool decay — schedule soft release + setTimeout(() => this.ctx.lights?.release?.(light), 420); + } + this._emitBurst('mote', pos, 24 * intensity, 1.8, color); + } + + _embers(pos, count, intensity = 1) { + this._emitBurst('ember', pos, count * intensity, 2.8, 0xff6a1e); + } + + _sparks(pos, color, count, speed, intensity = 1) { + this._emitBurst('spark', pos, count * intensity, speed, color); + } + + _motes(pos, color, count, intensity = 1) { + this._emitBurst('mote', pos, count * intensity, 1.6, color); + } + + _dustBurst(pos, count, intensity = 1) { + this._emitBurst('dust', pos, count * intensity, 3.5, 0xc4a574); + } + + _projectileTell(pos, fwd, color, intensity = 1) { + _f.copy(fwd).normalize(); + this._emitBurst('ember', pos.clone().addScaledVector(_f, 0.4), 20 * intensity, 4, color); + } + + _slashArc(hand, fwd, color, intensity = 1) { + _f.copy(fwd).normalize(); + const tip = hand.clone().addScaledVector(_f, 1.4); + tip.y += 0.2; + this._emitBurst('spark', tip, 28 * intensity, 4.5, color); + this.ctx.bursts?.spawn(BurstMode.AIR, tip, { + radius: 0.2, + endRadius: 1.8 * intensity, + life: 0.35, + intensity: 0.95 * intensity, + fresnel: 1.6, + displace: 0.2, + colorA: new Color(color), + colorB: new Color(0xffffff), + colorC: new Color(color) + }); + } + + /** + * Motion-math dodge afterimage: path-spaced trailing copies of the hero. + * Wind residual palette (cyan additive) — same family as post-cast air trails. + * @param {import('three').Object3D} source model + * @param {import('three').Vector3} from + * @param {import('three').Vector3} dir + * @param {number} distanceM + * @param {object} [opts] + */ + afterimage(source, from, dir, distanceM, opts = {}) { + if (settings.drc?.afterimage?.enabled === false) return; + this.afterimages?.spawnPath(source, from, dir, distanceM, opts); + // Soft wind-like dust along the path start + if (from) { + this._emitBurst('mote', from.clone().setY((from.y || 0) + 0.9), 18, 2.4, 0xc9f0ff); + this._emitBurst('frost', from.clone().setY((from.y || 0) + 0.4), 10, 1.6, 0xaee6ff); + } + } + + /** + * Continuous trail while dodge invuln is live. + * @param {number} dt + * @param {boolean} active + * @param {import('three').Object3D|null} source + * @param {import('three').Vector3|null} worldPos + * @param {number} [yaw] + */ + updateDodgeTrail(dt, active, source, worldPos, yaw) { + this.afterimages?.updateTrail(dt, active, source, worldPos, yaw); + } + + _emitBurst(sysId, position, count, speed, color) { + const pack = this._systems.get(sysId); + if (!pack || count < 1) return; + const n = Math.min(80, Math.round(count * (settings.global?.particleCount ?? 1))); + _c.set(typeof color === 'number' ? color : 0xffffff); + _emit.position = position; + _emit.radius = 0.15; + _emit.direction = null; + _emit.speed = speed; + _emit.speedVariance = 0.55; + _emit.spread = 1.1; + _emit.inherit = null; + _emit.anchor = position; + _emit.size = 0.1; + _emit.sizeVariance = 0.5; + _emit.life = 0.7; + _emit.lifeVariance = 0.4; + _emit.spin = 2; + _emit.tint = _c; + _emit.time = frame.uTime?.value ?? 0; + pack.sys.emit(n, _emit); + } +} diff --git a/src/vfx/effectPrefab.js b/src/vfx/effectPrefab.js new file mode 100644 index 0000000..f4f92c5 --- /dev/null +++ b/src/vfx/effectPrefab.js @@ -0,0 +1,362 @@ +/** + * EffectPrefab — isolatable shared effect primitives for Warlords skill export. + * + * Lab authoring path: + * settings.effect + settings.residual (+ element blocks) + * → buildEffectPrefab(skillId) + * → JSON download / paste into Open · Warlords skill vfx arrays + * + * Do not invent a second VFX engine. Primitives compose existing systems: + * PathTrail · BurstSphere · GroundDecals · VfxDirector · Ability path + * + * @see docs/CASTING_LAB_SSOT.md + * @see gameopen/docs/MELEE_SLASH_FX.md + * @see gameopen/docs/vfx/STRAWBERRY_STRIKE_MULTI_FX.md + */ + +import { settings } from '../config/settings.js'; +import { CASTING_ELEMENT_PHASE_VFX } from '../combat/elementWeaponSkills.js'; +import { SKILL_VFX_BIND, vfxCatalogById } from './vfxCatalog.js'; +import { skillById } from '../combat/drcSkills.js'; + +/** @typedef {'trail'|'travel'|'cast'|'impact'|'residual'|'decal'|'aura'} EffectKind */ + +/** + * @typedef {object} EffectPrimitive + * @property {EffectKind} kind + * @property {number} intensity 0..2 + * @property {number} aoe metres + * @property {number} speed m/s + * @property {number} size SI scale + * @property {string} color #rrggbb + * @property {string} [meshId] + * @property {number} [duration] + * @property {'R_hand'|'L_hand'|'root'|'feet'|'weapon_tip'} [attach] + * @property {string} [effectId] catalog id when composed by VfxDirector + */ + +/** + * @typedef {object} EffectPrefab + * @property {string} id + * @property {string} label + * @property {string} [skillId] + * @property {string} [weaponId] + * @property {'melee'|'spell'|'ranged'} [style] + * @property {string} source casting-lab + * @property {string} version + * @property {EffectPrimitive[]} primitives + * @property {object} [anim] + * @property {object} [economy] + * @property {object} [meta] + */ + +export const EFFECT_MESH_IDS = Object.freeze([ + 'none', + 'slashblue', + 'slashred', + 'slashpurple', + 'slashyellow', + 'orb-fire', + 'orb-ember', + 'orb-core', + 'orb-flare', + 'orb-ice', + 'orb-nature', + 'orb-storm', + 'orb-holy', + 'orb-arcane', + 'staff-charge', + 'rock-0', + 'rock-1', + 'rock-2', + 'arrow-path', + 'arrow-loft', + 'summon-fire-fist', + 'summon-ice-shard' +]); + +export const EFFECT_KINDS = Object.freeze([ + 'trail', + 'travel', + 'cast', + 'impact', + 'residual', + 'decal', + 'aura' +]); + +/** CDN / Open mesh path hints (not loaded here — document for ship). */ +export const MESH_PATH_HINT = Object.freeze({ + none: null, + slashblue: 'models/vfx/slash/slashblue.glb', + slashred: 'models/vfx/slash/slashred.glb', + slashpurple: 'models/vfx/slash/slashpurple.glb', + slashyellow: 'models/vfx/slash/slashyellow.glb', + 'orb-fire': 'models/vfx/orbs/orb-fire.glb', + 'orb-ember': 'models/vfx/orbs/orb-ember.glb', + 'orb-core': 'models/vfx/orbs/orb-core.glb', + 'orb-flare': 'models/vfx/orbs/orb-flare.glb', + 'orb-ice': 'models/vfx/orbs/orb-ice.glb', + 'orb-nature': 'models/vfx/orbs/orb-nature.glb', + 'orb-storm': 'models/vfx/orbs/orb-storm.glb', + 'orb-holy': 'models/vfx/orbs/orb-holy.glb', + 'orb-arcane': 'models/vfx/orbs/orb-arcane.glb', + 'staff-charge': 'models/vfx/charge/staff-charge.glb', + 'rock-0': 'models/vfx/rocks/rock-0.glb', + 'rock-1': 'models/vfx/rocks/rock-1.glb', + 'rock-2': 'models/vfx/rocks/rock-2.glb', + 'arrow-path': 'models/vfx/arrows/arrow-path.glb', + 'arrow-loft': 'models/vfx/arrows/arrow-loft.glb', + 'summon-fire-fist': 'models/vfx/summons/summon-fire-fist.glb', + 'summon-ice-shard': 'models/vfx/summons/summon-ice-shard.glb' +}); + +/** + * Strawberry multi-mode → isolated primitives (learning map). + * One mesh, many FX by scale+tint+attach — never whole fireball.glb. + */ +export const STRAWBERRY_MODE_PRESETS = Object.freeze({ + force: { kind: 'impact', size: 1.2, intensity: 1.25, aoe: 1.4, meshId: 'none' }, + trail: { kind: 'trail', size: 0.9, intensity: 0.9, aoe: 0, meshId: 'none', attach: 'weapon_tip' }, + slash: { kind: 'residual', size: 2.2, intensity: 1.1, aoe: 0.8, meshId: 'slashblue' }, + cool: { kind: 'impact', size: 1.6, intensity: 1.15, aoe: 1.2, meshId: 'none', color: '#88e0ff' } +}); + +/** + * Build a residual primitive from live `settings.residual` + `settings.effect`. + * @returns {EffectPrimitive} + */ +export function residualFromSettings() { + const r = settings.residual || {}; + const e = settings.effect || {}; + return { + kind: 'residual', + intensity: r.intensity ?? e.intensity ?? 1, + aoe: r.aoeRadius ?? e.aoe ?? 0.8, + speed: r.speed ?? e.speed ?? 14, + size: r.meshScale ?? e.size ?? 0.9, + color: r.color || e.color || '#7dd3fc', + meshId: r.variant || e.meshId || 'slashblue', + duration: e.duration ?? 0.45, + attach: 'weapon_tip', + effectId: 'getsuga_slash', + range: r.range ?? 3.2, + contactRadius: r.contactRadius ?? 0.65, + tipOffset: r.tipOffset ?? 0.55, + hitFrameDelay: r.hitFrameDelay ?? 0.18, + beyondBladeM: r.beyondBladeM ?? 0.38, + trailEnabled: r.trailEnabled !== false, + fireTrail: r.fireTrail !== false, + trailDuration: r.trailDuration ?? 0.34 + }; +} + +/** + * Universal primitive from `settings.effect` for the active editor kind. + * @param {EffectKind} [kind] + * @returns {EffectPrimitive} + */ +export function primitiveFromSettings(kind) { + const e = settings.effect || {}; + const k = kind || e.activeKind || 'impact'; + if (k === 'residual') return residualFromSettings(); + + const trail = settings.trail || {}; + const base = { + kind: k, + intensity: e.intensity ?? 1, + aoe: e.aoe ?? 1.2, + speed: e.speed ?? 12, + size: e.size ?? 1, + color: e.color || '#7dd3fc', + meshId: e.meshId || 'none', + duration: e.duration ?? 0.45, + attach: e.attach || 'R_hand' + }; + + if (k === 'trail') { + return { + ...base, + kind: 'trail', + size: trail.width ?? base.size, + speed: trail.flowSpeed ?? base.speed, + color: trail.colorOuter || base.color, + intensity: trail.glow ?? base.intensity + }; + } + return base; +} + +/** + * Layer cast / travel / impact for a DRC or element skill. + * @param {string} skillId + * @returns {EffectPrefab} + */ +export function buildEffectPrefab(skillId) { + const skill = skillById(skillId); + const bind = SKILL_VFX_BIND[skillId]; + const element = skill?.element; + const phase = element ? CASTING_ELEMENT_PHASE_VFX[element] : null; + + /** @type {EffectPrimitive[]} */ + const primitives = []; + + if (skill?.style === 'melee' || skillId === 'drc_melee_strike') { + primitives.push(residualFromSettings()); + primitives.push({ + kind: 'trail', + intensity: 0.85, + aoe: 0, + speed: settings.residual?.speed ?? 14, + size: 0.55, + color: settings.residual?.color || '#7dd3fc', + attach: 'weapon_tip', + duration: 0.25 + }); + } else { + const castId = bind?.cast || phase?.cast || skill?.castEffectId; + const travelId = bind?.travel || phase?.travel || skill?.travelEffectId; + const impactId = bind?.impact || phase?.impact || skill?.impactEffectId; + const e = settings.effect || {}; + + if (castId) { + const cat = vfxCatalogById(castId); + primitives.push({ + kind: 'cast', + intensity: e.intensity ?? 1, + aoe: 0, + speed: 0, + size: e.size ?? 1, + color: e.color || (cat ? `#${cat.color.toString(16).padStart(6, '0')}` : '#ffffff'), + meshId: 'none', + duration: 0.35, + attach: skill?.attachToHand ? 'R_hand' : 'root', + effectId: castId + }); + } + if (travelId) { + const cat = vfxCatalogById(travelId); + const elCfg = element && settings[element] ? settings[element] : {}; + primitives.push({ + kind: 'travel', + intensity: e.intensity ?? 1, + aoe: 0, + speed: elCfg.speed ?? e.speed ?? 12, + size: e.size ?? 1, + color: e.color || (cat ? `#${cat.color.toString(16).padStart(6, '0')}` : '#ff6a1e'), + meshId: e.meshId?.startsWith?.('orb') ? e.meshId : element === 'fire' ? 'orb-fire' : 'none', + duration: elCfg.lifetime ?? e.duration ?? 2, + attach: 'R_hand', + effectId: travelId + }); + } + if (impactId) { + const cat = vfxCatalogById(impactId); + primitives.push({ + kind: 'impact', + intensity: (e.intensity ?? 1) * (settings.global?.explosionIntensity ?? 1), + aoe: e.aoe ?? 1.2, + speed: 0, + size: e.size ?? 1, + color: e.color || (cat ? `#${cat.color.toString(16).padStart(6, '0')}` : '#ffffff'), + meshId: 'none', + duration: 0.7, + attach: 'root', + effectId: impactId + }); + } + } + + return { + id: `prefab_${skillId || 'custom'}`, + label: skill?.label || skillId || 'Custom effect', + skillId: skillId || null, + weaponId: skill?.weaponId || phase?.staffWeaponId || null, + style: skill?.style || (skillId === 'drc_melee_strike' ? 'melee' : 'spell'), + source: 'casting-lab', + version: '1.0.0', + primitives, + anim: { + role: skill?.animRole || (skill?.style === 'melee' ? 'attack' : 'cast'), + castDuration: skill?.castDuration ?? 0.85 + }, + economy: skill + ? { + cooldown: skill.cooldown, + staminaCost: skill.staminaCost, + rangeM: skill.rangeM + } + : undefined, + meta: { + meshPaths: primitives + .map((p) => (p.meshId ? MESH_PATH_HINT[p.meshId] : null)) + .filter(Boolean), + bans: ['whole_fireball.glb', 'space_getsuga', 'second_mixer'], + exportedAt: new Date().toISOString() + } + }; +} + +/** Prefab for the editor's active kind (no skill bind). */ +export function buildActiveKindPrefab() { + const prim = primitiveFromSettings(); + return { + id: `prefab_${prim.kind}_solo`, + label: `Solo ${prim.kind}`, + skillId: null, + weaponId: null, + style: prim.kind === 'residual' ? 'melee' : 'spell', + source: 'casting-lab', + version: '1.0.0', + primitives: [prim], + meta: { + meshPaths: prim.meshId && MESH_PATH_HINT[prim.meshId] ? [MESH_PATH_HINT[prim.meshId]] : [], + exportedAt: new Date().toISOString() + } + }; +} + +/** + * Download JSON file in browser. + * @param {object} data + * @param {string} filename + */ +export function downloadJson(data, filename = 'effect-prefab.json') { + const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + a.click(); + URL.revokeObjectURL(url); +} + +/** + * Apply intensity/aoe/speed/size/color/mesh from a prefab primitive into settings. + * @param {EffectPrimitive} prim + */ +export function applyPrimitiveToSettings(prim) { + if (!prim || !settings.effect) return; + const e = settings.effect; + if (prim.intensity != null) e.intensity = prim.intensity; + if (prim.aoe != null) e.aoe = prim.aoe; + if (prim.speed != null) e.speed = prim.speed; + if (prim.size != null) e.size = prim.size; + if (prim.color) e.color = prim.color; + if (prim.meshId) e.meshId = prim.meshId; + if (prim.duration != null) e.duration = prim.duration; + if (prim.attach) e.attach = prim.attach; + if (prim.kind) e.activeKind = prim.kind; + + if (prim.kind === 'residual' && settings.residual) { + const r = settings.residual; + if (prim.intensity != null) r.intensity = prim.intensity; + if (prim.aoe != null) r.aoeRadius = prim.aoe; + if (prim.speed != null) r.speed = prim.speed; + if (prim.size != null) r.meshScale = prim.size; + if (prim.color) r.color = prim.color; + if (prim.meshId && String(prim.meshId).startsWith('slash')) r.variant = prim.meshId; + if (prim.range != null) r.range = prim.range; + if (prim.contactRadius != null) r.contactRadius = prim.contactRadius; + } +} diff --git a/src/vfx/effectVariants.js b/src/vfx/effectVariants.js new file mode 100644 index 0000000..6c8f859 --- /dev/null +++ b/src/vfx/effectVariants.js @@ -0,0 +1,186 @@ +/** + * Effect texture / presentation variants — one base texture, many cast looks. + * + * Product ask: spells of one texture usable with different size, speed, angle; + * a second texture for additional usage (e.g. travel vs impact, core vs arc). + * Does not invent a second VFX engine — multiplies existing prefab knobs. + * + * @see effectPrefab.js + * @see skillshot/linearSettings.js (thunder electric look) + */ + +/** + * @typedef {object} EffectVariant + * @property {string} id + * @property {string} label + * @property {string} textureKey primary atlas / procedural channel + * @property {string} [textureAlt] second texture (impact / residual / AOE rim) + * @property {number} size SI scale multiplier (1 = base) + * @property {number} speed travel / anim speed multiplier + * @property {number} angleDeg spawn / fan angle offset (degrees) + * @property {number} [aoe] AOE radius metres (overrides base when set) + * @property {number} [intensity] 0..2 + * @property {string} [color] + */ + +/** Electric family — single bolt look, many sizes/speeds/angles. */ +export const ELECTRIC_VARIANTS = Object.freeze([ + { + id: 'arc_bolt', + label: 'Arc Bolt', + textureKey: 'electric_core', + textureAlt: 'electric_branch', + size: 1.0, + speed: 1.0, + angleDeg: 0, + aoe: 0, + intensity: 1.0, + color: '#9fd0ff' + }, + { + id: 'arc_wide', + label: 'Wide Fan', + textureKey: 'electric_core', + textureAlt: 'electric_branch', + size: 1.45, + speed: 0.85, + angleDeg: 18, + aoe: 1.2, + intensity: 1.15, + color: '#b8e0ff' + }, + { + id: 'arc_fast', + label: 'Snap Bolt', + textureKey: 'electric_core', + textureAlt: 'electric_branch', + size: 0.72, + speed: 1.55, + angleDeg: -8, + aoe: 0, + intensity: 0.9, + color: '#d0f0ff' + }, + { + id: 'arc_storm', + label: 'Storm Field', + textureKey: 'electric_core', + textureAlt: 'electric_ground', + size: 1.8, + speed: 0.7, + angleDeg: 0, + aoe: 4.5, + intensity: 1.4, + color: '#7fb4ff' + } +]); + +/** AOE showcase set — zone footprint + rim texture. */ +export const AOE_VARIANTS = Object.freeze([ + { + id: 'aoe_frost', + label: 'Frost Crown', + textureKey: 'frost_plate', + textureAlt: 'frost_rim', + size: 1.0, + speed: 1.0, + angleDeg: 0, + aoe: 5.0, + intensity: 1.0, + color: '#8ee8ff' + }, + { + id: 'aoe_meteor', + label: 'Cinder Ring', + textureKey: 'ember_core', + textureAlt: 'scorch_rim', + size: 1.2, + speed: 0.9, + angleDeg: 0, + aoe: 6.5, + intensity: 1.25, + color: '#ff8a3c' + }, + { + id: 'aoe_snare', + label: 'Voltaic Snare', + textureKey: 'electric_core', + textureAlt: 'electric_ground', + size: 0.95, + speed: 1.1, + angleDeg: 12, + aoe: 3.8, + intensity: 1.1, + color: '#a98bff' + }, + { + id: 'aoe_holy', + label: 'Light Disk', + textureKey: 'holy_core', + textureAlt: 'holy_rim', + size: 1.1, + speed: 1.0, + angleDeg: 0, + aoe: 4.2, + intensity: 1.05, + color: '#ffe9a0' + } +]); + +/** + * Apply variant multipliers onto a live settings-like block (in place). + * @param {Record} block e.g. settings.thunder + * @param {EffectVariant} variant + * @param {{ baseRange?: number, baseSpeed?: number, baseWidth?: number }} [base] + */ +export function applyVariantToBlock(block, variant, base = {}) { + if (!block || !variant) return block; + const size = variant.size ?? 1; + const speed = variant.speed ?? 1; + if (typeof block.speed === 'number') { + block.speed = (base.baseSpeed ?? block.speed) * speed; + } + if (typeof block.width === 'number') { + block.width = (base.baseWidth ?? block.width) * size; + } + if (typeof block.spread === 'number') { + block.spread = Math.max(0.02, (block.spread || 0.5) * size); + } + if (typeof block.zoneRadius === 'number' && variant.aoe > 0) { + block.zoneRadius = variant.aoe; + } + if (typeof block.range === 'number' && base.baseRange) { + /* range stays product; size affects body of effect not max range */ + } + if (variant.color && typeof block.colorCore === 'string') { + block.colorCore = variant.color; + } + if (variant.color && typeof block.color === 'string') { + block.color = variant.color; + } + // Angle: store for launch fan (AbilityManager / LinearSkillBridge read) + block._variantAngleDeg = variant.angleDeg ?? 0; + block._variantTexture = variant.textureKey; + block._variantTextureAlt = variant.textureAlt || null; + block._variantId = variant.id; + return block; +} + +/** + * Resolve variant by id from electric + aoe catalogs. + * @param {string} id + * @returns {EffectVariant|null} + */ +export function getEffectVariant(id) { + return ( + ELECTRIC_VARIANTS.find((v) => v.id === id) || + AOE_VARIANTS.find((v) => v.id === id) || + null + ); +} + +/** All showcase variants for lab / editor dropdowns. */ +export const ALL_EFFECT_VARIANTS = Object.freeze([ + ...ELECTRIC_VARIANTS, + ...AOE_VARIANTS +]); diff --git a/src/vfx/elementAttackVfx.js b/src/vfx/elementAttackVfx.js new file mode 100644 index 0000000..586b8a2 --- /dev/null +++ b/src/vfx/elementAttackVfx.js @@ -0,0 +1,259 @@ +/** + * Element attack mesh + pattern SSOT (Casting lab). + * + * Extends skillDelivery / SkillProjectileSystem — does not invent skill rows. + * + * | Asset | Role | + * |-------|------| + * | bubbles (procedural) | Water travel / freeze field animation | + * | freeze AOE | Radial blast from caster — freezes hostiles in radius | + * | rock-0..7 | Earth: emerge from below terrain next to caster → linear or aimed | + * | charge shell | kamehameha bake — earth emerge tell + staff charge | + * | arrow-path | Linear attack path; distance → end event (explode/aoe/blink/return) | + * | arrow-loft | Lofted throw / place device / trap / summon | + * + * @see docs/ELEMENT_ATTACK_MESHES_SSOT.md + * @see scripts/split-element-attack-meshes.mjs + */ + +import { Color, DoubleSide, MeshPhysicalMaterial, MeshStandardMaterial } from 'three'; + +/** Earth rocks — split from assorted_rock_pack (never load pack whole as one projectile). */ +export const EARTH_ROCK_MESHES = Object.freeze([ + './models/vfx/rocks/rock-0.glb', + './models/vfx/rocks/rock-1.glb', + './models/vfx/rocks/rock-2.glb', + './models/vfx/rocks/rock-3.glb', + './models/vfx/rocks/rock-4.glb', + './models/vfx/rocks/rock-5.glb', + './models/vfx/rocks/rock-6.glb', + './models/vfx/rocks/rock-7.glb' +]); + +export const EARTH_ROCK_DIAMETER_M = 0.55; + +/** Charge shell used as earth rise tell (same bake as staff charge). */ +export const EARTH_EMERGE_CHARGE = './models/vfx/charge/staff-charge.glb'; + +/** + * Two arrow systems (author art scaled SI). + * - path (teleport_arrow): flat linear — attack path; size/distance → end event + * - loft (arrow_curved): more loft — throw / place / trap / summon + */ +export const ARROW_SYSTEMS = Object.freeze({ + path: Object.freeze({ + id: 'arrow-path', + mesh: './models/vfx/arrows/arrow-path.glb', + source: 'teleport_arrow.glb', + lengthM: 1.1, + loft: 0, + gravity: 0, + role: 'linear_attack_path', + /** Distance along path selects where the end event fires */ + endEvents: Object.freeze(['impact', 'explode', 'aoe', 'blink', 'return']), + sizeScalesWithDistance: true + }), + loft: Object.freeze({ + id: 'arrow-loft', + mesh: './models/vfx/arrows/arrow-loft.glb', + source: 'arrow_curved.glb', + lengthM: 0.95, + loft: 0.42, + gravity: -12, + role: 'throw_place_trap_summon', + endEvents: Object.freeze(['throw', 'place_device', 'trap', 'summon']), + sizeScalesWithDistance: false + }) +}); + +/** Water bubble VFX — procedural (bubbles_2.glb is morph multipack; do not load whole). */ +export const WATER_BUBBLE = Object.freeze({ + sourceNote: 'D:/Games/Models/bubbles_2.glb — sample morphs later; runtime uses procedural SI spheres', + diameterM: 0.22, + countTravel: 6, + countNova: 14, + color: 0x6ec8ff, + opacity: 0.55 +}); + +/** Freeze nova defaults (m, s). */ +export const FREEZE_NOVA = Object.freeze({ + radiusM: 5.5, + expandSec: 0.45, + freezeSec: 2.5, + damageScale: 0.85, + color: 0xa8e8ff, + bubbleCount: 16 +}); + +/** + * @param {number} [seed] + * @returns {string} rock mesh url + */ +export function pickEarthRock(seed = Math.random()) { + const i = Math.floor(seed * EARTH_ROCK_MESHES.length) % EARTH_ROCK_MESHES.length; + return EARTH_ROCK_MESHES[i]; +} + +/** + * @param {number} count + * @returns {string[]} + */ +export function pickEarthRocks(count = 3) { + const n = Math.max(1, Math.min(EARTH_ROCK_MESHES.length, count | 0)); + const out = []; + const used = new Set(); + while (out.length < n) { + const i = Math.floor(Math.random() * EARTH_ROCK_MESHES.length); + if (used.has(i) && used.size < EARTH_ROCK_MESHES.length) continue; + used.add(i); + out.push(EARTH_ROCK_MESHES[i]); + } + return out; +} + +/** + * Infer element attack presentation from catalog skill (no new skill ids). + * @param {object} skill + * @returns {{ + * kind: 'freeze_nova'|'earth_rocks'|'water_bubbles'|'arrow_path'|'arrow_loft'|'orb'|null, + * rockCount?: number, + * aimMode?: 'linear'|'aimed', + * endEvent?: string, + * freeze?: boolean, + * aoeM?: number + * }} + */ +export function inferElementAttackKind(skill) { + if (!skill) return { kind: null }; + const blob = [ + skill.id, + skill.label, + skill.name, + skill.description, + skill.catalogSkillId, + skill.pathMode, + skill.presentation, + skill.element, + skill.abilityElement, + ...(skill.effects || []) + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + + // Freeze AOE — ice nova / absolute zero / freeze blast from self + if ( + /freeze|absolute.?zero|ice.?nova|frost.?nova|glacial.?shield|blizzard/.test(blob) || + (skill.pathMode === 'aoe' && (skill.element === 'ice' || skill.element === 'frost')) || + (skill.presentation === 'groundFlood' && /nova|aoe/.test(blob)) + ) { + if (/shield|ward/.test(blob) && !/nova|blast|zero/.test(blob)) { + /* ward is aura, not freeze nova */ + } else if (/nova|blast|zero|freeze|aoe|around/.test(blob) || skill.pathMode === 'aoe') { + return { + kind: 'freeze_nova', + freeze: true, + aoeM: skill.rangeM ? Math.min(8, Math.max(3.5, skill.rangeM * 0.4)) : FREEZE_NOVA.radiusM + }; + } + } + + // Earth rocks — nature/earth quake/spike/meteor of stone + if ( + /earth|quake|rock|stone|boulder|spike|upheaval|nature.?fury|vine.?lash|practice.?root/.test(blob) || + ((skill.element === 'nature' || skill.element === 'earth') && + (skill.pathMode === 'stream' || skill.pathMode === 'spikes' || skill.slotType === 'primary')) + ) { + const multi = /quake|meteor|barrage|several|volley|hellstorm|cataclysm/.test(blob); + const linear = + skill.pathMode === 'stream' || + skill.slotType === 'primary' || + /bolt|shot|linear|line/.test(blob); + return { + kind: 'earth_rocks', + rockCount: multi ? 4 : skill.pathMode === 'spikes' ? 3 : 1, + aimMode: linear ? 'linear' : 'aimed' + }; + } + + // Water bubbles — water spray / stream / ice travel soft + if ( + /water|bubble|spray|foam|tide|wave/.test(blob) || + ((skill.element === 'ice' || skill.element === 'water') && + skill.pathMode === 'stream' && + !/nova|freeze/.test(blob)) + ) { + return { kind: 'water_bubbles' }; + } + + // Arrow systems — catalog / presentation hooks + if (/blink|teleport|return.?arrow|path.?arrow|linear.?arrow/.test(blob)) { + const end = /blink|teleport/.test(blob) + ? 'blink' + : /return/.test(blob) + ? 'return' + : /explode|boom/.test(blob) + ? 'explode' + : /aoe|zone/.test(blob) + ? 'aoe' + : 'impact'; + return { kind: 'arrow_path', endEvent: end }; + } + if (/trap|place.?device|summon.?arrow|lob|throw.?arrow|curved.?arrow/.test(blob)) { + const end = /trap/.test(blob) + ? 'trap' + : /summon/.test(blob) + ? 'summon' + : /place|device/.test(blob) + ? 'place_device' + : 'throw'; + return { kind: 'arrow_loft', endEvent: end }; + } + + return { kind: null }; +} + +/** + * Procedural water bubble material (SI sphere clones). + * @param {{ color?: number, opacity?: number }} [opts] + */ +export function createWaterBubbleMaterial(opts = {}) { + const color = new Color(opts.color ?? WATER_BUBBLE.color); + try { + return new MeshPhysicalMaterial({ + color, + transparent: true, + opacity: opts.opacity ?? WATER_BUBBLE.opacity, + roughness: 0.15, + metalness: 0.05, + transmission: 0.55, + thickness: 0.2, + ior: 1.33, + side: DoubleSide, + depthWrite: false + }); + } catch { + return new MeshStandardMaterial({ + color, + transparent: true, + opacity: opts.opacity ?? WATER_BUBBLE.opacity, + roughness: 0.2, + metalness: 0.05, + emissive: color.clone().multiplyScalar(0.25), + emissiveIntensity: 0.4, + side: DoubleSide, + depthWrite: false + }); + } +} + +/** + * @param {string} endEvent + * @param {'path'|'loft'} system + */ +export function resolveArrowEndEvent(endEvent, system = 'path') { + const sys = ARROW_SYSTEMS[system] || ARROW_SYSTEMS.path; + if (endEvent && sys.endEvents.includes(endEvent)) return endEvent; + return system === 'loft' ? 'throw' : 'impact'; +} diff --git a/src/vfx/pistolBulletVfx.js b/src/vfx/pistolBulletVfx.js new file mode 100644 index 0000000..8858920 --- /dev/null +++ b/src/vfx/pistolBulletVfx.js @@ -0,0 +1,146 @@ +/** + * Flintlock / handgun bullet VFX SSOT (Casting lab). + * + * Mesh: Styloo bullet1.glb (SI ~0.027 m raw — already bullet-scale) + * Weapon: flintlock.glb → public/models/weapons/t0-flintlock.glb for t0-gun + * Anims: grudgepistolzio (loco/kneel author) + Open baked pistol combat + * + * Trail: 20% of default magic-trail length + * Speed: game-bullet (~90 m/s lab — true 300+ m/s is unreadable) + * Impact: + * - living (hostile/npc/player/boss) → red liquid blood splatter + * - terrain / aim / props → micro explosion, no blood + * + * @see docs/PISTOL_FLINTLOCK_SSOT.md + */ + +export const PISTOL_BULLET = Object.freeze({ + meshUrl: './models/vfx/projectiles/bullet1.glb', + /** SI length of visual bullet (m) — Styloo pack already ~cm; normalize if needed */ + lengthM: 0.028, + diameterM: 0.008, + /** Lab ballistic speed (m/s) */ + speed: 90, + /** Max life before despawn (s) — 90 m/s × 1.2 s ≈ 108 m */ + life: 1.2, + gravity: -2.5, + contactRadius: 0.12, + force: 6, + knockbackMm: 80, + knockupVy: 0.4, + aoe: 0.35, + /** Trail length as fraction of default staff trail (~1.0 → 0.2) */ + trailLengthFrac: 0.2, + trailWidthM: 0.018, + trailColor: 0xffcc88, + muzzleFlashColor: 0xffaa44, + muzzleFlashLife: 0.08 +}); + +export const FLINTLOCK_WEAPON = Object.freeze({ + catalogId: 't0-gun', + label: 'Flintlock Pistol', + localMesh: './models/weapons/t0-flintlock.glb', + /** SI hand length after attach normalize */ + handLengthM: 0.48, + animPack: 'pistol', + meshSlot: 'pistol' +}); + +/** Living target kinds for blood (CombatFocus + fleet) */ +export const LIVING_TARGET_KINDS = Object.freeze([ + 'hostile', + 'npc', + 'player', + 'enemy', + 'boss', + 'creature', + 'unit', + 'ally' +]); + +/** + * @param {{ kind?: string, id?: string, mesh?: object }|null|undefined} target + */ +export function isLivingTarget(target) { + if (!target) return false; + const kind = String(target.kind || target.mesh?.userData?.kind || '').toLowerCase(); + if (LIVING_TARGET_KINDS.includes(kind)) return true; + if (target.mesh?.userData?.isLiving === true) return true; + if (target.mesh?.userData?.isBoss === true) return true; + if (target.mesh?.userData?.isNpc === true) return true; + if (target.mesh?.userData?.isPlayer === true) return true; + // Soft-lock hostiles from DevIsland dummies + if (target.mesh?.userData?.hostile === true) return true; + if (kind === 'aim' || kind === 'terrain' || kind === 'prop' || kind === 'ground') return false; + return false; +} + +/** + * Skill looks like flintlock / handgun bullet shot (Practice · Burst · Suppress). + * Buffs (Take Cover) are excluded by callers via isWard / damage 0. + * @param {object} skill + */ +export function isPistolBulletSkill(skill) { + if (!skill) return false; + if (skill.isWard || skill.isFocus || skill.skillKind === 'buff') return false; + if (Number(skill.damage) === 0 && skill.skillKind !== 'ranged') return false; + if ( + skill.projectileKind === 'bullet' || + skill.projectile === 'bullet' || + skill.useBulletProjectile === true + ) { + return true; + } + const blob = [ + skill.id, + skill.label, + skill.catalogSkillId, + skill.weaponId, + skill.weaponTypeId, + skill.animPack, + skill.description + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + // Catalog ids t0_gun_* match t0_gun + if (/flint|pistol|handgun|t0-gun|t0_gun|gunplay|musket|revolver/.test(blob)) { + // Exclude pure utility names without shot/fire language when damage is 0 + if (Number(skill.damage) <= 0) return false; + return true; + } + if ( + skill.style === 'ranged' && + /physical|pierce|bullet/.test(String(skill.damageType || skill.element || '').toLowerCase()) + ) { + if ( + skill.slotType === 'primary' || + skill.slotType === 'ability' || + skill.slot === 0 || + skill.slot === 2 || + skill.slot === -1 || + skill.isWeaponPrimary || + skill.animPack === 'pistol' + ) { + return /gun|pistol|flint|burst|suppress|shot/i.test(blob) || skill.animPack === 'pistol'; + } + } + return false; +} + +/** + * Burst / multi-round count for pistol skills (catalog-first). + * @param {object} skill + * @returns {number} 1 = single shot + */ +export function pistolBulletCount(skill) { + if (!skill) return 1; + const n = Number(skill.multiHit); + if (n > 1) return Math.min(8, Math.floor(n)); + const blob = `${skill.id || ''} ${skill.label || ''} ${skill.description || ''} ${(skill.effects || []).join(' ')}`.toLowerCase(); + if (/three.?round|3.?round|triple/i.test(blob)) return 3; + if (/double|twin|2.?hit|two.?round/i.test(blob)) return 2; + if (/burst|multi.?hit/i.test(blob)) return 3; + return 1; +} diff --git a/src/vfx/staffOrbVfx.js b/src/vfx/staffOrbVfx.js new file mode 100644 index 0000000..aa72a48 --- /dev/null +++ b/src/vfx/staffOrbVfx.js @@ -0,0 +1,209 @@ +/** + * Staff magic orbs + charge shell SSOT (Casting lab). + * + * Assets (split — never load multipack whole as projectile): + * - public/models/vfx/orbs/orb-{fire,ice,nature,storm,holy,arcane}.glb ← gd_orbs_pack + * - public/models/vfx/charge/staff-charge.glb ← kamehameha_charging (small) + * + * All weapon staffs share **slot 1 normal attack** = stream orb projectile. + * Focus + LMB fires that primary (slot index 0 / hotbar 1). + * + * @see scripts/split-gd-orbs-and-charge.mjs + * @see grudge-vfx-orbs-strike skill + */ + +import { Color, AdditiveBlending, DoubleSide } from 'three'; +import { CASTING_ELEMENT_PHASE_VFX, normalizeElement } from '../combat/elementWeaponSkills.js'; + +/** SI diameters after split bake */ +export const STAFF_ORB_DIAMETER_M = 0.45; +export const STAFF_CHARGE_DIAMETER_M = 0.35; + +/** Per-element orb mesh + tint (individually managed by staff attacks). */ +export const STAFF_ORB_BY_ELEMENT = Object.freeze({ + fire: { + id: 'orb-fire', + path: './models/vfx/orbs/orb-fire.glb', + color: 0xff6a1e, + emissive: 0xff4008, + emissiveIntensity: 0.85 + }, + ice: { + id: 'orb-ice', + path: './models/vfx/orbs/orb-ice.glb', + color: 0x5fd6ff, + emissive: 0x2a8cff, + emissiveIntensity: 0.75 + }, + frost: { + id: 'orb-ice', + path: './models/vfx/orbs/orb-ice.glb', + color: 0x5fd6ff, + emissive: 0x2a8cff, + emissiveIntensity: 0.75 + }, + nature: { + id: 'orb-nature', + path: './models/vfx/orbs/orb-nature.glb', + color: 0x6bbf4a, + emissive: 0x2e9a28, + emissiveIntensity: 0.7 + }, + storm: { + id: 'orb-storm', + path: './models/vfx/orbs/orb-storm.glb', + color: 0x9fdcff, + emissive: 0x4aa8ff, + emissiveIntensity: 0.9 + }, + holy: { + id: 'orb-holy', + path: './models/vfx/orbs/orb-holy.glb', + color: 0xffe08a, + emissive: 0xffc94a, + emissiveIntensity: 0.95 + }, + arcane: { + id: 'orb-arcane', + path: './models/vfx/orbs/orb-arcane.glb', + color: 0xb070ff, + emissive: 0x7a28e8, + emissiveIntensity: 0.9 + } +}); + +/** Small charge shell — tinted per element at runtime. */ +export const STAFF_CHARGE = Object.freeze({ + id: 'staff-charge', + path: './models/vfx/charge/staff-charge.glb', + diameterM: STAFF_CHARGE_DIAMETER_M, + tipScale: 0.85, + pulseHz: 3.2 +}); + +/** + * Shared **normal attack** contract for every staff / wand primary (hotbar slot 1). + * Catalog skill ids stay per-weapon; delivery/VFX is this pattern. + */ +export const STAFF_NORMAL_ATTACK = Object.freeze({ + slot: 0, + slotType: 'primary', + hotkey: '1', + style: 'spell', + pathMode: 'stream', + presentation: 'volley', + animRole: 'cast', + animPack: 'magic', + castClip: 'magic/standing 1h cast spell 01', + rangeM: 14, + cooldown: 0.45, + castDuration: 0.5, + projectileDiameterM: STAFF_ORB_DIAMETER_M, + chargeMesh: STAFF_CHARGE.path, + useOrbProjectile: true, + label: 'Normal Attack', + hint: '1 / focus LMB — staff normal (orb stream)' +}); + +/** + * @param {string} [element] + * @returns {{ id: string, path: string, color: number, emissive: number, emissiveIntensity: number }} + */ +export function staffOrbForElement(element) { + const el = normalizeElement(element || 'arcane'); + return STAFF_ORB_BY_ELEMENT[el] || STAFF_ORB_BY_ELEMENT.arcane; +} + +/** + * @param {string} [element] + * @returns {string} mesh URL for projectile + */ +export function staffProjectileMeshUrl(element) { + return staffOrbForElement(element).path; +} + +/** + * @param {string} [element] + * @returns {number} hex color + */ +export function staffElementColor(element) { + const phase = CASTING_ELEMENT_PHASE_VFX[normalizeElement(element || 'arcane')]; + if (phase?.color != null) return phase.color; + return staffOrbForElement(element).color; +} + +/** + * Apply elemental materials to an orb / charge mesh clone. + * @param {import('three').Object3D} root + * @param {string} [element] + * @param {{ additive?: boolean, intensity?: number }} [opts] + */ +export function applyElementalOrbMaterials(root, element, opts = {}) { + if (!root) return; + const def = staffOrbForElement(element); + const intensity = opts.intensity ?? 1; + const col = new Color(def.color); + const em = new Color(def.emissive); + root.traverse((o) => { + if (!o.isMesh || !o.material) return; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + const next = mats.map((m) => { + const mat = m.clone(); + if (mat.color) mat.color.copy(col); + if (mat.emissive) { + mat.emissive.copy(em); + mat.emissiveIntensity = (def.emissiveIntensity || 0.8) * intensity; + } + if (opts.additive) { + mat.transparent = true; + mat.depthWrite = false; + mat.blending = AdditiveBlending; + mat.opacity = Math.min(1, 0.55 + 0.35 * intensity); + } else if (mat.opacity != null && mat.opacity < 1) { + mat.transparent = true; + } + if (mat.side !== undefined) mat.side = DoubleSide; + mat.needsUpdate = true; + return mat; + }); + o.material = next.length === 1 ? next[0] : next; + }); +} + +/** + * True when skill is a staff/wand normal (slot-1 primary stream bolt). + * @param {object} skill + */ +export function isStaffNormalAttack(skill) { + if (!skill) return false; + if (skill.useOrbProjectile === true) return true; + if (skill.isWeaponPrimary && skill.style === 'spell') return true; + const slot = skill.slot; + const isPrimary = + slot === 0 || + slot === -1 || + skill.slotType === 'primary' || + skill.hotkey === '1' || + skill.hotkey === 'f'; + if (!isPrimary) return false; + const style = skill.style || skill.labStyle; + if (style === 'melee') return false; + if (skill.isFocus || skill.isWard || skill.skillKind === 'buff') return false; + // Staff / wand / magic catalog + const blob = `${skill.id || ''} ${skill.weaponId || ''} ${skill.weaponTypeId || ''} ${skill.catalogSkillId || ''}`.toLowerCase(); + if (/staff|wand|magic|tome|apprentice|practice.?bolt|practice.?root|fire.?bolt|frost.?bolt|arcane.?bolt/.test(blob)) { + return true; + } + if (skill.pathMode === 'stream' && (skill.element || skill.abilityElement)) return true; + return false; +} + +/** + * Paths to warm (preload). + * @returns {string[]} + */ +export function staffOrbWarmUrls() { + const urls = new Set([STAFF_CHARGE.path]); + for (const o of Object.values(STAFF_ORB_BY_ELEMENT)) urls.add(o.path); + return [...urls]; +} diff --git a/src/vfx/vfxCatalog.js b/src/vfx/vfxCatalog.js new file mode 100644 index 0000000..1893c9d --- /dev/null +++ b/src/vfx/vfxCatalog.js @@ -0,0 +1,156 @@ +/** + * Fantasy VFX catalog — mirrors https://vfxgrudge.puter.site/ + * Stable effectIds for DRC weapon skill binds (Open vfxEffectCatalog SSOT). + */ + +/** @typedef {'fire'|'ice'|'lightning'|'arcane'|'light'|'poison'|'slash'|'earth'} VfxCategory */ + +/** + * @typedef {object} VfxCatalogEntry + * @property {string} id + * @property {string} name + * @property {VfxCategory} category + * @property {number} color + * @property {string} description + * @property {string[]} tags + */ + +/** @type {readonly VfxCatalogEntry[]} */ +export const VFX_CATALOG = Object.freeze([ + { + id: 'ice_lightning_burst', + name: 'Ice Serpent', + category: 'ice', + color: 0x9fdcff, + description: 'Frost + lightning impact burst (sandbox V).', + tags: ['ice', 'lightning', 'burst', 'impact'] + }, + { + id: 'moon_beam', + name: 'Moon Beam', + category: 'light', + color: 0xd0e8ff, + description: 'Vertical holy / moon light beam (sandbox B).', + tags: ['light', 'holy', 'beam'] + }, + { + id: 'frost_wave', + name: 'Frost Wave', + category: 'ice', + color: 0x9fdcff, + description: 'Expanding ground frost wave (sandbox F).', + tags: ['ice', 'wave', 'slam', 'shockwave'] + }, + { + id: 'fire_aura', + name: 'Fire Aura', + category: 'fire', + color: 0xff5510, + description: 'Ring of fire around the caster (sandbox G).', + tags: ['fire', 'aura', 'buff', 'ring'] + }, + { + id: 'earth_surge', + name: 'Earth Surge', + category: 'earth', + color: 0xc4a574, + description: 'Ground-read quake surge (sandbox T).', + tags: ['earth', 'wave', 'slam'] + }, + { + id: 'fireball', + name: 'Fireball', + category: 'fire', + color: 0xff6a1e, + description: 'Classic fire projectile (sandbox C).', + tags: ['fire', 'projectile', 'bolt'] + }, + { + id: 'inferno', + name: 'Inferno Blast', + category: 'fire', + color: 0xff6a1e, + description: 'Wide fire explosion with heat.', + tags: ['fire', 'aoe', 'blast'] + }, + { + id: 'arcane_swirl', + name: 'Arcane Swirl', + category: 'arcane', + color: 0xb070ff, + description: 'Orbiting arcane motes / cast channel.', + tags: ['arcane', 'swirl', 'channel'] + }, + { + id: 'getsuga_slash', + name: 'Getsuga Slash', + category: 'slash', + color: 0x7dd3fc, + description: 'Melee residual slash — attack frames only, not free hotkey.', + tags: ['slash', 'melee-residual', 'getsuga'] + }, + { + id: 'fire_hand', + name: 'Fire Hand', + category: 'fire', + color: 0xff6020, + description: 'Flaming hand cast tell.', + tags: ['fire', 'hand', 'cast'] + }, + { + id: 'chain_lightning', + name: 'Chain Lightning', + category: 'lightning', + color: 0x7ec8ff, + description: + 'Narrow fast electric bolts with chain hops + wind residual (storm presentation).', + tags: ['lightning', 'chain', 'bolt', 'electric', 'wind', 'narrow', 'fast'] + }, + { + id: 'lightning_bolt', + name: 'Lightning Bolt', + category: 'lightning', + color: 0xa8e8ff, + description: 'Single narrow electric projectile — core white, cyan glow, wind trail.', + tags: ['lightning', 'bolt', 'electric', 'projectile', 'narrow', 'fast'] + } +]); + +/** Alt+key sandbox shortcuts (fleet: never steal bare C/G combat keys). */ +export const VFX_SANDBOX_SHORTCUTS = Object.freeze([ + { key: 'V', code: 'KeyV', label: 'Ice Serpent', effectId: 'ice_lightning_burst' }, + { key: 'B', code: 'KeyB', label: 'Moon Beam', effectId: 'moon_beam' }, + { key: 'F', code: 'KeyF', label: 'Frost Wave', effectId: 'frost_wave' }, + { key: 'G', code: 'KeyG', label: 'Aura Ring', effectId: 'fire_aura' }, + { key: 'T', code: 'KeyT', label: 'Earth Surge', effectId: 'earth_surge' }, + { key: 'C', code: 'KeyC', label: 'Fireball', effectId: 'fireball' }, + { key: 'L', code: 'KeyL', label: 'Chain Lightning', effectId: 'chain_lightning' } +]); + +const BY_ID = new Map(VFX_CATALOG.map((e) => [e.id, e])); + +export function vfxCatalogById(id) { + return BY_ID.get(id) || null; +} + +/** Map elemental ability → primary beauty effectId for layered deploy. */ +export const ELEMENT_EFFECT_MAP = Object.freeze({ + fire: { cast: 'fire_hand', impact: 'inferno', path: 'fireball' }, + water: { cast: 'arcane_swirl', impact: 'frost_wave', path: 'moon_beam' }, + earth: { cast: 'earth_surge', impact: 'earth_surge', path: 'earth_surge' }, + wind: { cast: 'arcane_swirl', impact: 'ice_lightning_burst', path: 'chain_lightning' } +}); + +/** DRC skill id → layered sandbox effectIds */ +export const SKILL_VFX_BIND = Object.freeze({ + drc_fire_bolt: { cast: 'fire_hand', travel: 'fireball', impact: 'inferno' }, + drc_water_lash: { cast: 'arcane_swirl', travel: 'moon_beam', impact: 'frost_wave' }, + drc_earth_spike: { cast: 'earth_surge', travel: 'earth_surge', impact: 'earth_surge' }, + drc_wind_tempest: { cast: 'arcane_swirl', travel: 'chain_lightning', impact: 'ice_lightning_burst' }, + drc_melee_strike: { cast: 'getsuga_slash', travel: null, impact: 'getsuga_slash' }, + // Arcane tree (optional) + arcane_bolt: { cast: 'arcane_swirl', travel: 'chain_lightning', impact: 'arcane_swirl' }, + arcane_gale: { cast: 'arcane_swirl', travel: 'chain_lightning', impact: 'ice_lightning_burst' }, + void_burst: { cast: 'arcane_swirl', travel: 'fireball', impact: 'inferno' }, + storm_arcane: { cast: 'chain_lightning', travel: 'chain_lightning', impact: 'inferno' } +}); diff --git a/src/vfx/weaponTipTrail.js b/src/vfx/weaponTipTrail.js new file mode 100644 index 0000000..05924df --- /dev/null +++ b/src/vfx/weaponTipTrail.js @@ -0,0 +1,328 @@ +/** + * Weapon-tip trail — samples grip→tip each frame during the attack window. + * + * SSOT stack (do not fork): + * getWeaponTip / WeaponAttach muzzle · settings.residual · VfxDirector getsuga + * · SkillProjectileSystem for physics residual at apex + * + * Visual: translucent ribbon + optional fire blur (distortion-style fade). + * Hit: melee residual projectile starts slightly beyond tip (beyondBladeM). + */ + +import { + BufferAttribute, + BufferGeometry, + CatmullRomCurve3, + Color, + DoubleSide, + MathUtils, + Mesh, + MeshBasicMaterial, + Vector3 +} from 'three'; +import { settings } from '../config/settings.js'; + +const _p = new Vector3(); +const _q = new Vector3(); +const _side = new Vector3(); +const _fwd = new Vector3(); +const _up = new Vector3(0, 1, 0); + +/** + * @typedef {object} TipTrailSession + * @property {number} endsAt + * @property {number} apexAt + * @property {boolean} apexFired + * @property {Vector3[]} points + * @property {Mesh|null} mesh + * @property {string} color + * @property {number} width + * @property {number} beyondBladeM + * @property {import('three').Vector3} forward + * @property {object|null} skill + * @property {object|null} hit + * @property {number} rangeM + * @property {boolean} fireBlur + */ + +export class WeaponTipTrailSystem { + /** + * @param {{ + * scene: import('three').Scene, + * character: { getWeaponTip?: Function, getWeaponForward?: Function, facing?: number }, + * projectiles?: import('../combat/SkillProjectileSystem.js').SkillProjectileSystem|null, + * vfx?: { deploy?: Function }|null, + * abilities?: { cast?: Function, selected?: string }|null + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.character = opts.character; + this.projectiles = opts.projectiles || null; + this.vfx = opts.vfx || null; + this.abilities = opts.abilities || null; + /** @type {TipTrailSession[]} */ + this._sessions = []; + this._elapsed = 0; + } + + /** + * Begin trail on attack start; apex residual fires after hitFrameDelay. + * @param {{ + * duration?: number, + * hitFrameDelay?: number, + * forward?: Vector3, + * skill?: object, + * hit?: { kind?: string, step?: number }, + * rangeM?: number, + * color?: string, + * fireBlur?: boolean, + * width?: number, + * beyondBladeM?: number + * }} [opts] + */ + beginSwing(opts = {}) { + const r = settings.residual || {}; + if (r.trailEnabled === false) return null; + + const duration = opts.duration ?? r.trailDuration ?? 0.32; + const hitFrameDelay = opts.hitFrameDelay ?? r.hitFrameDelay ?? 0.18; + const color = + opts.color || + r.trailColor || + (opts.fireBlur || r.fireTrail ? '#ff6a22' : r.color) || + '#7dd3fc'; + const fireBlur = opts.fireBlur ?? !!r.fireTrail; + const width = opts.width ?? r.trailWidth ?? 0.12; + const beyondBladeM = opts.beyondBladeM ?? r.beyondBladeM ?? 0.35; + + const forward = (opts.forward?.clone?.() || new Vector3(0, 0, 1)).normalize(); + const session = { + endsAt: this._elapsed + duration, + apexAt: this._elapsed + hitFrameDelay, + apexFired: false, + points: [], + mesh: null, + color, + width, + beyondBladeM, + forward, + skill: opts.skill || null, + hit: opts.hit || null, + rangeM: opts.rangeM ?? r.range ?? 3.2, + fireBlur + }; + + // Seed first tip sample immediately + this._sampleTip(session); + this._ensureMesh(session); + this._sessions.push(session); + return session; + } + + /** + * @param {number} dt + * @param {number} [elapsed] absolute combat clock if available + */ + update(dt, elapsed) { + if (Number.isFinite(elapsed)) this._elapsed = elapsed; + else this._elapsed += dt; + + for (let i = this._sessions.length - 1; i >= 0; i--) { + const s = this._sessions[i]; + if (this._elapsed < s.endsAt) { + this._sampleTip(s); + this._rebuildRibbon(s); + } + + // Apex of this swing / combo hit + if (!s.apexFired && this._elapsed >= s.apexAt) { + s.apexFired = true; + this._fireApex(s); + } + + if (this._elapsed >= s.endsAt + 0.08) { + this._disposeSession(s); + this._sessions.splice(i, 1); + } else if (this._elapsed >= s.endsAt && s.mesh?.material) { + // Fade tail after window + const fade = 1 - MathUtils.clamp((this._elapsed - s.endsAt) / 0.08, 0, 1); + s.mesh.material.opacity = (s.fireBlur ? 0.55 : 0.72) * fade; + } + } + } + + clear() { + for (const s of this._sessions) this._disposeSession(s); + this._sessions.length = 0; + } + + _sampleTip(session) { + const ch = this.character; + if (!ch?.getWeaponTip) return; + const tipOff = settings.residual?.tipOffset ?? 0.55; + ch.getWeaponTip(_p, tipOff); + // Keep short dense trail (blur feel) + const maxPts = settings.residual?.trailMaxPoints ?? 18; + session.points.push(_p.clone()); + while (session.points.length > maxPts) session.points.shift(); + } + + _ensureMesh(session) { + if (session.mesh) return; + const geo = new BufferGeometry(); + // 2 verts per point (ribbon strip) + const maxPts = settings.residual?.trailMaxPoints ?? 18; + const pos = new Float32Array(maxPts * 2 * 3); + geo.setAttribute('position', new BufferAttribute(pos, 3)); + geo.setDrawRange(0, 0); + + const mat = new MeshBasicMaterial({ + color: new Color(session.color), + transparent: true, + opacity: session.fireBlur ? 0.55 : 0.72, + depthWrite: false, + side: DoubleSide, + toneMapped: false + }); + // Fire blur: warmer + additive-ish via higher opacity mid samples + if (session.fireBlur) { + mat.color.set(session.color || '#ff6a22'); + mat.opacity = 0.48; + } + + const mesh = new Mesh(geo, mat); + mesh.name = 'WeaponTipTrail'; + mesh.frustumCulled = false; + mesh.renderOrder = 12; + mesh.userData.weaponTipTrail = true; + this.scene.add(mesh); + session.mesh = mesh; + } + + _rebuildRibbon(session) { + const pts = session.points; + if (!session.mesh || pts.length < 2) return; + const pos = session.mesh.geometry.attributes.position; + const halfW = session.width * 0.5; + let vi = 0; + for (let i = 0; i < pts.length; i++) { + const a = pts[i]; + const b = pts[Math.min(i + 1, pts.length - 1)]; + _fwd.copy(b).sub(a); + if (_fwd.lengthSq() < 1e-10) { + _fwd.copy(session.forward); + } else _fwd.normalize(); + _side.crossVectors(_up, _fwd); + if (_side.lengthSq() < 1e-10) _side.set(1, 0, 0); + else _side.normalize(); + // Taper: head wider, tail thinner + more transparent feel via width + const t = i / Math.max(1, pts.length - 1); + const w = halfW * (0.35 + 0.65 * t) * (session.fireBlur ? 1.35 : 1); + pos.setXYZ(vi++, a.x + _side.x * w, a.y + _side.y * w, a.z + _side.z * w); + pos.setXYZ(vi++, a.x - _side.x * w, a.y - _side.y * w, a.z - _side.z * w); + } + pos.needsUpdate = true; + session.mesh.geometry.setDrawRange(0, vi); + session.mesh.geometry.computeBoundingSphere?.(); + } + + /** + * Attack apex: residual slash VFX + physics projectile a bit past the blade. + * @param {TipTrailSession} session + */ + _fireApex(session) { + const r = settings.residual || {}; + if (r.enabled === false) return; + + const ch = this.character; + const tipOff = r.tipOffset ?? 0.55; + if (ch?.getWeaponTip) ch.getWeaponTip(_p, tipOff); + else return; + + // Live blade direction if available + if (ch?.getWeaponForward) { + ch.getWeaponForward(_fwd); + if (_fwd.lengthSq() > 1e-8) session.forward.copy(_fwd).normalize(); + } + + // Beyond tip: residual starts past blade (melee hit volume) + const beyond = session.beyondBladeM ?? 0.35; + _q.copy(_p).addScaledVector(session.forward, beyond); + + const range = MathUtils.clamp(session.rangeM ?? r.range ?? 3.2, 1, 12); + const pathEnd = _q.clone().addScaledVector(session.forward, range); + pathEnd.y = Math.max(0.12, _q.y * 0.45); + const mid = _q.clone().lerp(pathEnd, 0.45); + mid.y = Math.max(_q.y, mid.y) + range * 0.04; + + const intensity = + (r.intensity ?? 1) * + (settings.effect?.intensity ?? 1) * + (session.hit?.kind === 'finisher' || session.hit?.kind === 'finisherAir' ? 1.25 : 1); + + // Contact radius from weapon mesh cylinder (+ pad) when available + const vol = ch.weaponVolume; + const meshRadius = vol?.radiusM ?? 0.05; + const contact = + Math.max(r.contactRadius ?? 0.65, meshRadius + beyond * 0.5); + + // Beauty residual (Getsuga / slash) from beyond-blade point + this.vfx?.deploy?.('getsuga_slash', { + origin: _q.clone(), + forward: session.forward.clone(), + aim: pathEnd.clone(), + fromTip: true, + intensity, + aoe: r.aoeRadius ?? 0.8, + size: r.meshScale ?? 0.9, + speed: r.speed ?? 14, + color: session.fireBlur ? session.color || r.color : r.color + }); + + // Fire-style blur residual path (Ability trail primitive — same FireAbility family) + if (settings.residual?.pathTrail !== false && this.abilities?.cast) { + const curve = new CatmullRomCurve3([_q.clone(), mid, pathEnd], false, 'catmullrom', 0.5); + const el = + session.fireBlur || r.fireTrail + ? 'fire' + : this.abilities.selected || 'storm'; + try { + this.abilities.cast(curve, el); + } catch { + /* optional */ + } + } + + // Physics projectile along blade axis — radius from mesh cylinder + if (this.projectiles?.spawn && r.physicsProjectile !== false) { + void this.projectiles.spawn({ + origin: _q.clone(), + target: pathEnd.clone(), + forward: session.forward.clone(), + element: session.fireBlur || r.fireTrail ? 'fire' : 'arcane', + speed: r.speed ?? 16, + gravity: 0, + contactRadius: contact, + life: Math.min(1.2, range / Math.max(6, r.speed ?? 16) + 0.15), + force: 10 * intensity, + knockbackMm: 160, + aoe: r.aoeRadius ?? 0.8, + size: Math.max(0.12, meshRadius * 2.2), + color: session.color || r.color || '#ff8844', + meshUrl: null, + explodeOnHit: true + }); + } + } + + _disposeSession(session) { + if (session.mesh) { + this.scene.remove(session.mesh); + session.mesh.geometry?.dispose?.(); + session.mesh.material?.dispose?.(); + session.mesh = null; + } + session.points.length = 0; + } +} diff --git a/src/world/DevIslandHarvest.js b/src/world/DevIslandHarvest.js new file mode 100644 index 0000000..f053bed --- /dev/null +++ b/src/world/DevIslandHarvest.js @@ -0,0 +1,772 @@ +/** + * Training Room · DevIsland harvest + training-dummy runtime (Casting lab). + * + * Same map as /devnode authoring — one island, not a second world. + * - Spawns baked rock/ore/pebble GLBs + herb stubs on WORLD.islandRadius pad + * - F / tryInteract: harvest nearest alive node within HARVEST_RANGE_M (5 m) + * - Tool gate via equipped weapon (TOOL / t0-tool / pick tags) + * - Swing → attack one-shot anim → HP drain → bag loot + world drop splash + * - Training dummies register with CombatFocus as hostiles + * - applyNodeLayout() consumes DevNode / Training Room JSON + * + * Extends App worldHarvest hook — does not fork combat or invent a second mixer. + * @see trainingRoomMap.js · docs/TRAINING_ROOM_SSOT.md + */ + +import { + Box3, + BoxGeometry, + Color, + CylinderGeometry, + Group, + Mesh, + MeshBasicMaterial, + MeshStandardMaterial, + RingGeometry, + SphereGeometry, + Vector3, + DoubleSide +} from 'three'; +import { WORLD } from '../config/worldScale.js'; +import { + DEFAULT_DECOR_LAYOUT, + DEFAULT_DUMMY_LAYOUT, + DEFAULT_HARVEST_LAYOUT, + DECOR_MESH_POOL, + HARVEST_NODE_DEFS, + HARVEST_RANGE_M, + HARVEST_SWING_CD, + pickMeshUrl, + rollNodeLoot, + toolLabel, + toolMatches +} from './devIslandCatalog.js'; +import { getEquippedWeapon } from '../combat/equippedWeaponRuntime.js'; +import { + TRAINING_ROOM_LABEL, + TRAINING_ROOM_MAP_ID, + paletteIdToHarvestDef +} from './trainingRoomMap.js'; +import { resolveTrainingRoomMeshUrl } from './trainingRoomDeploy.js'; +import { bagAdd } from '../ui/mainPanelSlots.js'; +import { bagItemFromLoot } from '../ui/iconResolve.js'; + +const _v = new Vector3(); +const _box = new Box3(); + +/** + * @typedef {object} HarvestNode + * @property {string} id + * @property {import('./devIslandCatalog.js').HarvestNodeDef} def + * @property {Group} root + * @property {number} hp + * @property {number} maxHp + * @property {boolean} alive + * @property {number} respawnAt + * @property {number} x + * @property {number} z + * @property {Mesh|null} highlight + * @property {import('three').Object3D|null} model + */ + +export class DevIslandHarvest { + /** + * @param {{ + * scene: import('three').Scene, + * assets: import('../loaders/AssetLoader.js').AssetLoader, + * character?: object, + * combatFocus?: import('../combat/CombatFocus.js').CombatFocus|null, + * worldDrops?: import('./WorldDrops.js').WorldDrops|null, + * dropBag?: { add: (item: object) => void }|null, + * onToast?: (s: string) => void, + * islandRadius?: number, + * rangeM?: number + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.assets = opts.assets; + this.character = opts.character || null; + this.combatFocus = opts.combatFocus || null; + this.worldDrops = opts.worldDrops || null; + this.dropBag = opts.dropBag || null; + this.onToast = opts.onToast || (() => {}); + this.islandRadius = opts.islandRadius ?? WORLD.islandRadius ?? 51; + this.rangeM = opts.rangeM ?? HARVEST_RANGE_M; + /** Terrain height sample (IslandHeightfield) — feet on hills */ + this.heightSample = + typeof opts.heightSample === 'function' ? opts.heightSample : (x, z) => 0; + + this.group = new Group(); + this.group.name = 'TrainingRoomHarvest'; + this.group.userData.mapId = TRAINING_ROOM_MAP_ID; + this.scene.add(this.group); + + this.dummyGroup = new Group(); + this.dummyGroup.name = 'TrainingRoomDummies'; + this.scene.add(this.dummyGroup); + + /** Active layout source: default | storage | import */ + this.layoutSource = 'default'; + + /** @type {HarvestNode[]} */ + this.nodes = []; + /** @type {Group[]} */ + this.dummies = []; + this._meshCache = new Map(); + this._swingCd = 0; + this._highlightNode = null; + this._ready = false; + this._busy = false; + } + + get ready() { + return this._ready; + } + + get nodeCount() { + return this.nodes.filter((n) => n.alive).length; + } + + /** + * Boot Training Room map: optional DevNode layout, else default harvest+decor+dummies. + * @param {{ layout?: object|null }} [opts] + */ + async init(opts = {}) { + const layout = opts.layout || null; + if (layout?.nodes?.length) { + await this.applyNodeLayout(layout); + this.layoutSource = layout.source || 'import'; + } else { + await this.spawnDefaultLayout(); + await this.spawnDecor(); + this.spawnTrainingDummies(); + this.layoutSource = 'default'; + } + this._ready = true; + console.info( + `[${TRAINING_ROOM_LABEL}] harvest=${this.nodes.length} decor=${this.decorCount} dummies=${this.dummies.length} src=${this.layoutSource} range=${this.rangeM}m padR=${this.islandRadius.toFixed(1)}` + ); + return this; + } + + /** + * Apply DevNode / Training Room layout JSON (cartesian nodes). + * Harvestable palette → spawnNode; cliffs → decor; pve_dummy → dummies. + * @param {object} layout + */ + async applyNodeLayout(layout) { + this.clearNodes(); + this.clearDummies(); + if (!this._decor) this._decor = []; + for (const d of this._decor) { + this.group.remove(d); + this._disposeObject(d); + } + this._decor.length = 0; + + const nodes = layout?.nodes || []; + const jobs = []; + let di = 0; + for (const n of nodes) { + const pid = n.paletteId || ''; + const defId = paletteIdToHarvestDef(pid); + if (defId && HARVEST_NODE_DEFS[defId]) { + jobs.push( + this.spawnNode(HARVEST_NODE_DEFS[defId], n.x, n.z, di++).then((node) => { + if (node?.root && n.yaw != null) node.root.rotation.y = n.yaw; + if (node?.root && n.scale != null && n.scale !== 1) { + node.root.scale.setScalar(n.scale); + } + }) + ); + continue; + } + if (pid === 'node.pve_dummy' || pid.includes('pve_dummy')) { + const dummy = this._makeDummy(n.label || 'Training dummy'); + const y = this.heightSample(n.x, n.z) || 0; + dummy.position.set(n.x, y, n.z); + if (n.yaw != null) dummy.rotation.y = n.yaw; + this.dummyGroup.add(dummy); + this.dummies.push(dummy); + this.combatFocus?.addSelectable?.(dummy, 'hostile'); + continue; + } + if ( + pid.startsWith('node.cliff') || + pid.includes('cliff') || + pid.includes('wall') || + pid.includes('arch') || + pid.includes('column') + ) { + jobs.push(this._spawnDecorAt(n, di++)); + } + // tree / flower / animal / hemp = play preview via forest/grass layers — skip mesh here + } + await Promise.all(jobs.filter(Boolean)); + // If layout had no dummies, keep a minimal combat pad + if (!this.dummies.length) this.spawnTrainingDummies(); + } + + /** + * @param {{ x: number, z: number, yaw?: number, scale?: number, paletteId?: string }} n + * @param {number} seed + */ + async _spawnDecorAt(n, seed = 0) { + const url = + DECOR_MESH_POOL[seed % DECOR_MESH_POOL.length] || DECOR_MESH_POOL[0]; + if (!url) return; + try { + const model = await this._loadModel(url, n.scale ?? 1.2); + if (!model) return; + const root = new Group(); + root.name = `decor_layout_${seed}`; + root.userData.decor = true; + const landY = this.heightSample(n.x, n.z) || 0; + root.position.set(n.x, landY, n.z); + root.rotation.y = n.yaw ?? 0; + root.add(model); + try { + _box.setFromObject(root); + if (Number.isFinite(_box.min.y)) root.position.y += landY - _box.min.y; + } catch { + root.position.y = landY; + } + this.group.add(root); + if (!this._decor) this._decor = []; + this._decor.push(root); + } catch (err) { + console.warn('[TrainingRoom] decor layout fail', url, err?.message || err); + } + } + + get decorCount() { + return this._decor?.length || 0; + } + + /** + * Shore rockforms / walls — map silhouette, not harvestable. + */ + async spawnDecor() { + if (!this._decor) this._decor = []; + for (const d of this._decor) { + this.group.remove(d); + this._disposeObject(d); + } + this._decor.length = 0; + const jobs = DEFAULT_DECOR_LAYOUT.map(async (slot, i) => { + const url = DECOR_MESH_POOL[slot.mesh % DECOR_MESH_POOL.length]; + if (!url) return; + try { + const model = await this._loadModel(url, slot.scale ?? 1.2); + if (!model) return; + const root = new Group(); + root.name = `decor_${i}`; + root.userData.decor = true; + root.position.set( + Math.cos(slot.angle) * this.islandRadius * (slot.r ?? 0.82), + 0, + Math.sin(slot.angle) * this.islandRadius * (slot.r ?? 0.82) + ); + root.rotation.y = slot.yaw ?? 0; + root.add(model); + try { + _box.setFromObject(root); + if (Number.isFinite(_box.min.y)) root.position.y -= _box.min.y; + } catch { + /* ok */ + } + this.group.add(root); + this._decor.push(root); + } catch (err) { + console.warn('[DevIsland] decor fail', url, err?.message || err); + } + }); + await Promise.all(jobs); + } + + /** + * Clear + re-place default harvest ring. + */ + async spawnDefaultLayout() { + this.clearNodes(); + const layout = DEFAULT_HARVEST_LAYOUT; + const jobs = layout.map((slot, i) => { + const def = HARVEST_NODE_DEFS[slot.defId]; + if (!def) return null; + const r = this.islandRadius * (slot.r ?? 0.4); + const x = Math.cos(slot.angle) * r; + const z = Math.sin(slot.angle) * r; + return this.spawnNode(def, x, z, i); + }); + await Promise.all(jobs.filter(Boolean)); + } + + /** + * @param {import('./devIslandCatalog.js').HarvestNodeDef|string} defOrId + * @param {number} x + * @param {number} z + * @param {number} [seed] + */ + async spawnNode(defOrId, x, z, seed = 0) { + const def = + typeof defOrId === 'string' ? HARVEST_NODE_DEFS[defOrId] : defOrId; + if (!def) return null; + + const root = new Group(); + root.name = `harvest_${def.id}_${this.nodes.length}`; + const landY = this.heightSample(x, z) || 0; + root.position.set(x, landY, z); + root.userData.harvestNode = true; + root.userData.defId = def.id; + root.userData.classId = def.classId; + + let model = null; + const url = pickMeshUrl(def, seed + def.id.length); + if (url) { + try { + model = await this._loadModel(url, def.scale ?? 1); + if (model) root.add(model); + } catch (err) { + console.warn('[DevIsland] mesh fail', url, err?.message || err); + } + } + if (!model) { + model = this._makeStub(def); + root.add(model); + } + + // Snap mesh soles to terrain surface (local min.y → landY) + try { + _box.setFromObject(root); + if (Number.isFinite(_box.min.y)) { + root.position.y += landY - _box.min.y; + } + } catch { + root.position.y = landY; + } + + // Soft interact ring (hidden until nearest) + const ring = new Mesh( + new RingGeometry(0.55, 0.72, 32), + new MeshBasicMaterial({ + color: new Color(def.tint || '#88ccff'), + transparent: true, + opacity: 0, + depthWrite: false, + side: DoubleSide + }) + ); + ring.rotation.x = -Math.PI / 2; + ring.position.y = 0.04; + root.add(ring); + + this.group.add(root); + + /** @type {HarvestNode} */ + const node = { + id: root.name, + def, + root, + hp: def.hp, + maxHp: def.hp, + alive: true, + respawnAt: 0, + x, + z, + highlight: ring, + model + }; + this.nodes.push(node); + return node; + } + + clearNodes() { + for (const n of this.nodes) { + this.group.remove(n.root); + this._disposeObject(n.root); + } + this.nodes.length = 0; + this._highlightNode = null; + } + + /** + * Training dummies — simple SI targets for combat focus / Tab cycle. + */ + spawnTrainingDummies() { + this.clearDummies(); + for (const slot of DEFAULT_DUMMY_LAYOUT) { + const r = this.islandRadius * (slot.r ?? 0.32); + const x = Math.cos(slot.angle) * r; + const z = Math.sin(slot.angle) * r; + const dummy = this._makeDummy(slot.label || 'Training dummy'); + const y = this.heightSample(x, z) || 0; + dummy.position.set(x, y, z); + this.dummyGroup.add(dummy); + this.dummies.push(dummy); + this.combatFocus?.addSelectable?.(dummy, 'hostile'); + } + } + + clearDummies() { + for (const d of this.dummies) { + this.combatFocus?.removeSelectable?.(d); + this.dummyGroup.remove(d); + this._disposeObject(d); + } + this.dummies.length = 0; + } + + /** + * Nearest alive node within range (XZ). + * @param {Vector3|{x:number,z:number}} pos + * @param {number} [range] + * @returns {{ node: HarvestNode, dist: number }|null} + */ + nearestAlive(pos, range = this.rangeM) { + let best = null; + let bestD = range; + const px = pos.x ?? 0; + const pz = pos.z ?? 0; + for (const n of this.nodes) { + if (!n.alive) continue; + const dx = n.x - px; + const dz = n.z - pz; + const d = Math.hypot(dx, dz); + if (d <= bestD) { + bestD = d; + best = n; + } + } + return best ? { node: best, dist: bestD } : null; + } + + /** + * App.worldHarvest.tryInteract — F best-next-action hook. + * @param {Vector3} playerPos + * @param {number} [radius] ignored if smaller than product 5 m — use this.rangeM + * @returns {string|false} + */ + tryInteract(playerPos, radius) { + const r = Math.max(radius || 0, this.rangeM); + return this.tryHarvestNearest(playerPos, r); + } + + /** + * Harvest nearest node within range with tool in hand (if required). + * @param {Vector3|{x:number,z:number}} playerPos + * @param {number} [range] + * @returns {string|false} toast message or false + */ + tryHarvestNearest(playerPos, range = this.rangeM) { + if (this._busy || this._swingCd > 0) return false; + const hit = this.nearestAlive(playerPos, range); + if (!hit) return false; + + const { node, dist } = hit; + const weapon = getEquippedWeapon(); + if (!toolMatches(weapon, node.def)) { + this.onToast(`Need ${toolLabel(node.def)} · ${node.def.label} (${dist.toFixed(1)} m)`); + return `Need ${toolLabel(node.def)}`; + } + + // Face node + play tool / attack swing + this._faceToward(playerPos, node); + this._playHarvestAnim(weapon); + + this._swingCd = HARVEST_SWING_CD; + node.hp -= 1; + + if (node.hp > 0) { + this._pulseHighlight(node, 0.55); + const msg = `${node.def.label} · ${node.hp}/${node.maxHp} · ${dist.toFixed(1)} m`; + this.onToast(msg); + return msg; + } + + // Depleted + this._breakNode(node); + const loot = rollNodeLoot(node.def); + for (const raw of loot) { + // Icons: lab minerals + CDN 496 fallbacks + const bag = bagItemFromLoot(raw); + this.dropBag?.add?.(bag); + // Also Main Panel bag (I inventory) for equip / deposit / RMB menu + try { + bagAdd({ + id: bag.id, + name: bag.name, + kind: bag.kind || 'mat', + category: bag.category, + tier: bag.tier, + qty: bag.qty || 1, + icon: bag.icon, + iconUrl: bag.iconUrl + }); + } catch { + /* bag full — drop bag still has it */ + } + // Splash one unit as world drop near node for pickup demo + if (this.worldDrops && bag.iconUrl) { + const ox = (Math.random() - 0.5) * 0.8; + const oz = (Math.random() - 0.5) * 0.8; + const landY = this.heightSample(node.x, node.z) || 0; + this.worldDrops + .spawn( + { + id: bag.id, + name: bag.name, + tier: bag.tier, + qty: 1, + iconUrl: bag.iconUrl, + category: bag.category, + glowColor: 0xc9a227, + borderColor: 0xe8d48b + }, + { x: node.x + ox, y: landY, z: node.z + oz }, + { skipModel: true } + ) + .catch(() => {}); + } + } + const names = loot.map((l) => `${l.name}×${l.qty}`).join(', ') || 'loot'; + const msg = `Harvested ${node.def.label} → ${names}`; + this.onToast(msg); + return msg; + } + + /** + * Frame tick — CD, respawn, nearest highlight. + * @param {number} dt + * @param {Vector3|{x:number,z:number}|null} [playerPos] + */ + update(dt, playerPos = null) { + if (this._swingCd > 0) this._swingCd = Math.max(0, this._swingCd - dt); + const now = performance.now() / 1000; + + for (const n of this.nodes) { + if (!n.alive && n.respawnAt > 0 && now >= n.respawnAt) { + this._respawnNode(n); + } + // Subtle idle bob on live models + if (n.alive && n.model) { + n.model.rotation.y += dt * 0.05; + } + } + + // Highlight nearest in range + if (playerPos) { + const hit = this.nearestAlive(playerPos, this.rangeM); + const next = hit?.node || null; + if (this._highlightNode !== next) { + if (this._highlightNode?.highlight) { + this._highlightNode.highlight.material.opacity = 0; + } + this._highlightNode = next; + } + if (next?.highlight) { + const pulse = 0.35 + 0.25 * Math.sin(now * 4); + next.highlight.material.opacity = pulse; + const ok = toolMatches(getEquippedWeapon(), next.def); + next.highlight.material.color.set(ok ? next.def.tint || '#88ccff' : '#ff6644'); + } + } + } + + /* ── internals ─────────────────────────────────────────────── */ + + /** + * @param {string} url + * @param {number} scale + */ + async _loadModel(url, scale = 1) { + // Same-origin public/ on casting deploy; optional CDN when preferCdn + url = resolveTrainingRoomMeshUrl(url) || url; + let tpl = this._meshCache.get(url); + if (!tpl) { + const gltf = await this.assets.loadGLTF(url); + tpl = gltf.scene || gltf.scenes?.[0]; + if (!tpl) return null; + this._meshCache.set(url, tpl); + } + const clone = tpl.clone(true); + clone.traverse((c) => { + if (c.isMesh) { + c.castShadow = true; + c.receiveShadow = true; + if (c.material) { + c.material = c.material.clone?.() || c.material; + } + } + }); + // Fit to SI prop height (~0.9–1.8 m) — author packs may be cm or unitless + _box.setFromObject(clone); + const size = new Vector3(); + _box.getSize(size); + const maxDim = Math.max(size.x, size.y, size.z, 0.01); + // If mesh already ~1–2 m, only apply scale; if huge (cm), compress hard + let target = 1.35 * scale; + if (maxDim > 8) target = 1.5 * scale; // classic 100× / big export + else if (maxDim < 0.25) target = 1.1 * scale; // tiny author + const s = target / maxDim; + clone.scale.setScalar(s); + clone.position.set(0, 0, 0); + clone.updateMatrixWorld(true); + return clone; + } + + /** + * @param {import('./devIslandCatalog.js').HarvestNodeDef} def + */ + _makeStub(def) { + const g = new Group(); + g.name = `stub_${def.classId}`; + if (def.classId === 'herb' || def.classId === 'fiber') { + const stem = new Mesh( + new CylinderGeometry(0.04, 0.06, 0.55, 6), + new MeshStandardMaterial({ color: 0x3d6b3a, roughness: 0.7 }) + ); + stem.position.y = 0.28; + const leaf = new Mesh( + new SphereGeometry(0.22, 8, 6), + new MeshStandardMaterial({ + color: new Color(def.tint || '#4caf6a'), + roughness: 0.85 + }) + ); + leaf.position.y = 0.55; + leaf.scale.set(1.2, 0.55, 1); + g.add(stem, leaf); + } else { + const rock = new Mesh( + new BoxGeometry(0.9, 0.7, 0.85), + new MeshStandardMaterial({ + color: new Color(def.tint || '#888'), + roughness: 0.95 + }) + ); + rock.position.y = 0.35; + rock.rotation.y = 0.4; + g.add(rock); + } + return g; + } + + /** + * @param {string} label + */ + _makeDummy(label) { + const root = new Group(); + root.name = label; + root.userData.displayName = label; + root.userData.selectable = 'hostile'; + root.userData.hp01 = 1; + root.userData.kind = 'hostile'; + root.userData.trainingDummy = true; + + const body = new Mesh( + new CylinderGeometry(0.28, 0.32, 1.5, 10), + new MeshStandardMaterial({ color: 0x8b5a2b, roughness: 0.85 }) + ); + body.position.y = 0.9; + body.castShadow = true; + + const head = new Mesh( + new SphereGeometry(0.22, 10, 8), + new MeshStandardMaterial({ color: 0xc4a574, roughness: 0.7 }) + ); + head.position.y = 1.85; + + const post = new Mesh( + new CylinderGeometry(0.08, 0.1, 0.35, 6), + new MeshStandardMaterial({ color: 0x5c4033, roughness: 0.9 }) + ); + post.position.y = 0.15; + + root.add(post, body, head); + return root; + } + + /** + * @param {HarvestNode} node + */ + _breakNode(node) { + node.alive = false; + node.hp = 0; + node.root.visible = false; + const delay = node.def.respawnS ?? 30; + node.respawnAt = performance.now() / 1000 + delay; + } + + /** + * @param {HarvestNode} node + */ + _respawnNode(node) { + node.alive = true; + node.hp = node.maxHp; + node.respawnAt = 0; + node.root.visible = true; + if (node.highlight) node.highlight.material.opacity = 0; + } + + /** + * @param {HarvestNode} node + * @param {number} opacity + */ + _pulseHighlight(node, opacity) { + if (node.highlight) node.highlight.material.opacity = opacity; + } + + /** + * @param {Vector3|{x:number,z:number}} playerPos + * @param {HarvestNode} node + */ + _faceToward(playerPos, node) { + const ch = this.character; + if (!ch) return; + const dx = node.x - (playerPos.x ?? 0); + const dz = node.z - (playerPos.z ?? 0); + if (Math.hypot(dx, dz) < 0.01) return; + const yaw = Math.atan2(dx, dz); + if (typeof ch.facing === 'number') ch.facing = yaw; + if (ch.root) ch.root.rotation.y = yaw; + } + + /** + * Tool swing = attack one-shot (sword_shield / TOOL pack). + * @param {object|null} weapon + */ + _playHarvestAnim(weapon) { + const ch = this.character; + if (!ch) return; + // Prefer attack role (tool skills map mine/chop → attack1) + if (typeof ch.playWeaponAttack === 'function') { + ch.playWeaponAttack(); + return; + } + if (typeof ch.requestOneShot === 'function') { + ch.requestOneShot('attack') || ch.requestOneShot('attack1'); + } + void weapon; + } + + /** + * @param {import('three').Object3D} obj + */ + _disposeObject(obj) { + obj.traverse((c) => { + if (c.geometry) c.geometry.dispose?.(); + if (c.material) { + if (Array.isArray(c.material)) c.material.forEach((m) => m.dispose?.()); + else c.material.dispose?.(); + } + }); + } + + dispose() { + this.clearNodes(); + this.clearDummies(); + this.scene.remove(this.group); + this.scene.remove(this.dummyGroup); + } +} diff --git a/src/world/Environment.js b/src/world/Environment.js index 6580a18..24ed514 100644 --- a/src/world/Environment.js +++ b/src/world/Environment.js @@ -12,11 +12,12 @@ import { import { settings } from '../config/settings.js'; import { getColor } from '../utils/color.js'; import { patchOnBeforeCompile } from '../utils/shaderPatch.js'; +import { WORLD } from '../config/worldScale.js'; const _sunDir = new Vector3(); -/** Half-width of the shadowed area, in metres, centred on the action. */ -const SHADOW_EXTENT = 26; +/** Half-width of the shadowed area, in metres, centred on the action (SI). */ +const SHADOW_EXTENT = WORLD.shadowExtent; /** * Scene, atmosphere and lighting. diff --git a/src/world/Ground.js b/src/world/Ground.js index 42a16ab..538ccbe 100644 --- a/src/world/Ground.js +++ b/src/world/Ground.js @@ -3,20 +3,22 @@ import { settings } from '../config/settings.js'; import { getColor } from '../utils/color.js'; import { noiseGLSL } from '../shaders/lib/noise.glsl.js'; import { LAYER } from '../core/Layers.js'; +import { WORLD } from '../config/worldScale.js'; /** - * The stage floor. + * Stage island terrain pad (lab shell). * - * Kept perfectly flat (y = 0) on purpose: the path-drawing raycast, every - * ability and the earth eruptions all assume a planar surface, and a flat plane - * makes those interactions exact. + * **Hard (production-world aligned):** keep collision / raycast surface **planar y=0**. + * Path draw, earth crust, CCT feet, and ability ground all require a flat field. + * Do not raise mesh vertices for "hills" here — use visual shading only. * - * Visually this is a dark polished slab rather than grass and soil — the whole - * scene is lit like a stage, so the floor only has to hold shadows, catch a thin - * specular sheen from the key light and then fall off into the fog colour. All - * of that is procedural, so it still costs no geometry: broad mottling, fine - * grain, a radial light pool centred on the stage, and a roughness break-up - * that turns the sheen into scattered highlights instead of a mirror. + * **Visual island best practices (lab):** + * - Dark stone pad under hero / cast range + * - Shore band near WORLD.islandRadius → sand tint + foam grain (still flat) + * - Radial light pool; falloff matches fog so pad dissolves into void + * - StageWater cuts a hole under the pad so ocean rings the island + * + * @see docs/ISLAND_STAGE_SSOT.md · WORLD in worldScale.js */ export class Ground { constructor(environment) { @@ -32,17 +34,25 @@ export class Ground { this.uniforms = { uFloorColor: { value: getColor(settings.environment.floorColor).clone() }, uFloorTint: { value: getColor(settings.environment.floorTint).clone() }, + uShoreColor: { value: getColor(settings.environment.shoreColor || '#3d4a3a').clone() }, uSheen: { value: settings.environment.floorSheen }, uPool: { value: settings.environment.floorPool }, - uTime: { value: 0 } + uTime: { value: 0 }, + uIslandR: { value: WORLD.islandRadius }, + uShoreBand: { value: WORLD.shoreBand }, + uShoreTint: { value: WORLD.shoreTint } }; environment.registerShadowCasterWithPatch(this.material, (shader) => { shader.uniforms.uFloorColor = this.uniforms.uFloorColor; shader.uniforms.uFloorTint = this.uniforms.uFloorTint; + shader.uniforms.uShoreColor = this.uniforms.uShoreColor; shader.uniforms.uSheen = this.uniforms.uSheen; shader.uniforms.uPool = this.uniforms.uPool; shader.uniforms.uTime = this.uniforms.uTime; + shader.uniforms.uIslandR = this.uniforms.uIslandR; + shader.uniforms.uShoreBand = this.uniforms.uShoreBand; + shader.uniforms.uShoreTint = this.uniforms.uShoreTint; shader.vertexShader = shader.vertexShader .replace('#include ', `#include \nvarying vec3 vGroundWorld;`) @@ -58,9 +68,13 @@ export class Ground { varying vec3 vGroundWorld; uniform vec3 uFloorColor; uniform vec3 uFloorTint; + uniform vec3 uShoreColor; uniform float uSheen; uniform float uPool; uniform float uTime; + uniform float uIslandR; + uniform float uShoreBand; + uniform float uShoreTint; ${noiseGLSL}` ) .replace( @@ -68,27 +82,33 @@ export class Ground { `#include { vec3 wp = vGroundWorld; + float dist = length(wp.xz); - // Broad, deliberately smooth variation — dark stone with a warmer - // wash drifting through it. Anything higher frequency than this - // reads as gravel and fights the clean stage look. + // Broad stone mottling (low frequency — not gravel) float macro = fbm3(wp * 0.018); float tintMask = smoothstep(-0.5, 0.6, macro); vec3 base = mix(uFloorColor, uFloorTint, tintMask * 0.5); - // Faint patina. Both terms are multiplicative and low frequency on - // purpose: an additive or fine-grained term is a small number in - // absolute terms but a *large* fraction of a floor this dark, and - // it immediately reads as gravel. base *= 1.0 + fbm3(wp * 0.09 + 11.0) * 0.05; base *= 1.0 + (snoise01(wp * 0.7) - 0.5) * 0.06; - // Radial light pool: the stage centre stays readable and the floor - // sinks toward the backdrop long before the plane's edge. - float dist = length(wp.xz); - float pool = mix(1.0, smoothstep(40.0, 5.0, dist), clamp(uPool, 0.0, 1.0)); + // Shore band: warm sand/stone toward water (island best practice, visual only) + float shoreInner = max(0.0, uIslandR - uShoreBand); + float shore = smoothstep(shoreInner, uIslandR * 0.98, dist); + float foam = smoothstep(0.35, 0.95, snoise01(wp * 0.55 + uTime * 0.04)) * shore; + base = mix(base, uShoreColor, shore * clamp(uShoreTint, 0.0, 1.0)); + base = mix(base, vec3(0.72, 0.78, 0.74), foam * 0.22); + + // Radial light pool — SI extents from WORLD + float poolOuter = ${WORLD.floorPoolOuter.toFixed(1)}; + float poolInner = ${WORLD.floorPoolInner.toFixed(1)}; + float pool = mix(1.0, smoothstep(poolOuter, poolInner, dist), clamp(uPool, 0.0, 1.0)); base *= mix(0.18, 1.0, pool); + // Beyond island: darken into fog/water ring (StageWater owns ocean) + float offIsland = smoothstep(uIslandR * 0.96, uIslandR * 1.12, dist); + base *= mix(1.0, 0.42, offIsland); + diffuseColor.rgb *= base; }` ) @@ -104,7 +124,8 @@ export class Ground { ); }); - this.mesh = new Mesh(new PlaneGeometry(400, 400, 1, 1), this.material); + const gSize = WORLD.groundSize; + this.mesh = new Mesh(new PlaneGeometry(gSize, gSize, 1, 1), this.material); this.mesh.rotation.x = -Math.PI / 2; this.mesh.receiveShadow = true; this.mesh.castShadow = false; @@ -121,8 +142,12 @@ export class Ground { this.uniforms.uTime.value = elapsed; this.uniforms.uFloorColor.value.copy(getColor(env.floorColor)); this.uniforms.uFloorTint.value.copy(getColor(env.floorTint)); + this.uniforms.uShoreColor.value.copy(getColor(env.shoreColor || '#3d4a3a')); this.uniforms.uSheen.value = env.floorSheen; this.uniforms.uPool.value = env.floorPool; + this.uniforms.uIslandR.value = WORLD.islandRadius; + this.uniforms.uShoreBand.value = WORLD.shoreBand; + this.uniforms.uShoreTint.value = WORLD.shoreTint; this.material.roughness = env.floorRoughness; } diff --git a/src/world/GrowingForest.js b/src/world/GrowingForest.js new file mode 100644 index 0000000..33e8bee --- /dev/null +++ b/src/world/GrowingForest.js @@ -0,0 +1,281 @@ +/** + * Growing forest + harvestable trees — forestoutline / snakey trees patterns. + * + * - Instanced / grouped trees with growth stages (seed → sapling → mature) + * - Harvest chops mature trees (tool gate wood/hatchet); respawn regrows over time + * - Placed on IslandHeightfield.sample for SI feet + * + * Does not invent a second harvest engine — feeds DevIslandHarvest-compatible nodes + * or runs standalone via tryHarvest. + * + * @see Desktop forestoutline.html (instanced trees, SI scatter) + * @see snakey-locomotion trees.ts + */ + +import { + Color, + ConeGeometry, + CylinderGeometry, + Group, + InstancedMesh, + Matrix4, + MeshStandardMaterial, + Quaternion, + Vector3 +} from 'three'; +import { WORLD } from '../config/worldScale.js'; +import { heightAt, isDryLand, terrainOpts } from './IslandHeightfield.js'; + +const _m = new Matrix4(); +const _q = new Quaternion(); +const _p = new Vector3(); +const _s = new Vector3(); +const _up = new Vector3(0, 1, 0); + +function mulberry(seed) { + let s = seed >>> 0; + return () => { + s = (s + 0x6d2b79f5) | 0; + let t = Math.imul(s ^ (s >>> 15), 1 | s); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +/** + * @typedef {object} GrowTree + * @property {number} x + * @property {number} z + * @property {number} y + * @property {number} age 0..1 growth + * @property {number} growRate /s + * @property {number} hp + * @property {number} maxHp + * @property {boolean} alive + * @property {number} respawnAt + * @property {number} yaw + * @property {number} scaleBase + * @property {number} instanceIndex + */ + +export class GrowingForest { + /** + * @param {{ + * scene: import('three').Scene, + * heightSample?: (x: number, z: number) => number, + * count?: number, + * islandRadius?: number, + * clearRadius?: number, + * seed?: number, + * onToast?: (s: string) => void + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.heightSample = opts.heightSample || ((x, z) => heightAt(x, z)); + this.count = opts.count ?? 48; + this.islandRadius = opts.islandRadius ?? WORLD.islandRadius * 0.92; + this.clearRadius = opts.clearRadius ?? 10; + this.seed = opts.seed ?? 1997; + this.onToast = opts.onToast || (() => {}); + + this.group = new Group(); + this.group.name = 'GrowingForest'; + this.scene.add(this.group); + + /** @type {GrowTree[]} */ + this.trees = []; + this._elapsed = 0; + + // Trunk + crown templates (snakey-style simple SI proportions) + const trunkGeo = new CylinderGeometry(0.12, 0.28, 1, 6, 1); + trunkGeo.translate(0, 0.5, 0); + const crownGeo = new ConeGeometry(0.55, 1.4, 7); + crownGeo.translate(0, 1.35, 0); + + this.trunkMat = new MeshStandardMaterial({ + color: new Color(0.28, 0.18, 0.1), + roughness: 1 + }); + // forestoutline-style leaf hue range (green canopy + slight variance via tint) + this.leafMat = new MeshStandardMaterial({ + color: new Color().setHSL(0.30, 0.55, 0.38), + roughness: 0.9 + }); + this.leafMat.userData.forestOutline = true; + // Wind sway on leaves (vertex) — mild + this._uTime = { value: 0 }; + this.leafMat.onBeforeCompile = (shader) => { + shader.uniforms.uTime = this._uTime; + shader.vertexShader = shader.vertexShader + .replace('#include ', '#include \nuniform float uTime;') + .replace( + '#include ', + `#include +#ifdef USE_INSTANCING +{ + float ph = instanceMatrix[3].x * 0.13 + instanceMatrix[3].z * 0.17; + float amt = smoothstep(0.4, 1.2, transformed.y) * 0.06; + transformed.x += sin(uTime * 1.15 + ph) * amt; + transformed.z += cos(uTime * 0.95 + ph * 1.2) * amt; +} +#endif` + ); + }; + this.leafMat.customProgramCacheKey = () => 'grow-forest-leaf-v1'; + + this.trunkMesh = new InstancedMesh(trunkGeo, this.trunkMat, this.count); + this.crownMesh = new InstancedMesh(crownGeo, this.leafMat, this.count); + this.trunkMesh.castShadow = true; + this.crownMesh.castShadow = true; + this.trunkMesh.frustumCulled = false; + this.crownMesh.frustumCulled = false; + this.group.add(this.trunkMesh, this.crownMesh); + + this._scatter(); + } + + _scatter() { + const rnd = mulberry(this.seed); + const placed = []; + let guard = 0; + while (this.trees.length < this.count && guard++ < this.count * 80) { + const ang = rnd() * Math.PI * 2; + const r = + this.clearRadius + + rnd() * Math.max(2, this.islandRadius - this.clearRadius - 2); + const x = Math.cos(ang) * r; + const z = Math.sin(ang) * r; + if (!isDryLand(x, z, terrainOpts(), 0.08)) continue; + if (Math.hypot(x, z) < this.clearRadius) continue; + let ok = true; + for (const p of placed) { + if ((p[0] - x) ** 2 + (p[1] - z) ** 2 < 3.2 * 3.2) { + ok = false; + break; + } + } + if (!ok) continue; + placed.push([x, z]); + const y = this.heightSample(x, z); + const idx = this.trees.length; + this.trees.push({ + x, + z, + y, + age: 0.35 + rnd() * 0.65, // start partially grown + growRate: 0.02 + rnd() * 0.03, + hp: 40, + maxHp: 40, + alive: true, + respawnAt: 0, + yaw: rnd() * Math.PI * 2, + scaleBase: 0.85 + rnd() * 0.45, + instanceIndex: idx + }); + } + this._writeInstances(); + } + + _writeInstances() { + for (let i = 0; i < this.count; i++) { + const t = this.trees[i]; + if (!t || !t.alive || t.age < 0.05) { + _s.set(0, 0, 0); + _m.compose(_p.set(0, -100, 0), _q.identity(), _s); + } else { + const g = Math.min(1, t.age); + const s = t.scaleBase * (0.25 + 0.75 * g); + _p.set(t.x, t.y, t.z); + _q.setFromAxisAngle(_up, t.yaw); + _s.set(s, s * (0.7 + 0.3 * g), s); + _m.compose(_p, _q, _s); + } + this.trunkMesh.setMatrixAt(i, _m); + this.crownMesh.setMatrixAt(i, _m); + } + this.trunkMesh.instanceMatrix.needsUpdate = true; + this.crownMesh.instanceMatrix.needsUpdate = true; + this.trunkMesh.count = this.trees.length; + this.crownMesh.count = this.trees.length; + } + + /** + * @param {number} dt + * @param {number} [now] performance.now()/1000 or elapsed + */ + update(dt, now = this._elapsed) { + this._elapsed += dt; + this._uTime.value = this._elapsed; + let dirty = false; + for (const t of this.trees) { + if (!t.alive) { + if (now >= t.respawnAt) { + t.alive = true; + t.age = 0.08; + t.hp = t.maxHp; + t.y = this.heightSample(t.x, t.z); + dirty = true; + } + continue; + } + if (t.age < 1) { + t.age = Math.min(1, t.age + t.growRate * dt); + dirty = true; + } + } + if (dirty) this._writeInstances(); + } + + /** + * Nearest mature harvestable tree within range. + * @param {Vector3|{x:number,z:number}} pos + * @param {number} rangeM + */ + findNearest(pos, rangeM = 5) { + let best = null; + let bestD = rangeM * rangeM; + for (const t of this.trees) { + if (!t.alive || t.age < 0.55) continue; // must grow enough + const d = (t.x - pos.x) ** 2 + (t.z - pos.z) ** 2; + if (d < bestD) { + bestD = d; + best = t; + } + } + return best; + } + + /** + * Chop tree — returns loot roll or null. + * @param {GrowTree} tree + * @param {number} damage + */ + damage(tree, damage = 12) { + if (!tree?.alive) return null; + tree.hp -= damage; + if (tree.hp > 0) { + this.onToast(`Tree · ${Math.ceil(tree.hp)} hp`); + return { chopped: false, hp: tree.hp }; + } + tree.alive = false; + tree.age = 0; + tree.respawnAt = this._elapsed + 45; // regrow ~45 s + this._writeInstances(); + this.onToast('Wood harvested · tree will regrow'); + return { + chopped: true, + loot: [ + { id: 'wood', label: 'Wood', qty: 2 + ((Math.random() * 3) | 0), icon: '🪵' } + ] + }; + } + + dispose() { + this.group.removeFromParent(); + this.trunkMesh.geometry.dispose(); + this.crownMesh.geometry.dispose(); + this.trunkMat.dispose(); + this.leafMat.dispose(); + } +} diff --git a/src/world/IslandHeightfield.js b/src/world/IslandHeightfield.js new file mode 100644 index 0000000..74453c4 --- /dev/null +++ b/src/world/IslandHeightfield.js @@ -0,0 +1,367 @@ +/** + * Island heightfield SSOT — land terrain + water layer (Casting lab). + * + * Patterns learned (ported, not forked engines): + * - snakey-locomotion `heightAt` — FBM rolling + medium + micro (CPU = mesh = feet) + * - three-stylized Terrain — warped FBM, dirt/meadow, SI cell density + * - three.js Rapier terrain example — Float32 heights → heightfield collider + * + * **One height source** for mesh, Rapier, foot IK, harvest Y, path aim. + * **Water** is a second layer: StageWater.sampleHeight outside the pad; + * land height fades to water at shore (no cliff at edge). + * + * SI: 1 unit = 1 m; island radius from WORLD; height amp modest (≤ ~1.2 m hills). + * + * @see docs/ISLAND_STAGE_SSOT.md · grudge-rapier · worldScale.js + */ + +import { + BufferAttribute, + Color, + Float32BufferAttribute, + Mesh, + MeshStandardMaterial, + PlaneGeometry, + Vector3 +} from 'three'; +import { WORLD } from '../config/worldScale.js'; +import { settings } from '../config/settings.js'; +import { getColor } from '../utils/color.js'; +import { LAYER } from '../core/Layers.js'; + +function hash(x, z, seed) { + const v = Math.sin(x * 127.1 + z * 311.7 + seed * 74.7) * 43758.5453123; + return v - Math.floor(v); +} + +function valueNoise(x, z, seed) { + const x0 = Math.floor(x); + const z0 = Math.floor(z); + const tx = x - x0; + const tz = z - z0; + const sx = tx * tx * (3 - 2 * tx); + const sz = tz * tz * (3 - 2 * tz); + const a = hash(x0, z0, seed); + const b = hash(x0 + 1, z0, seed); + const c = hash(x0, z0 + 1, seed); + const d = hash(x0 + 1, z0 + 1, seed); + return ( + a * (1 - sx) * (1 - sz) + + b * sx * (1 - sz) + + c * (1 - sx) * sz + + d * sx * sz + ); +} + +function fbm(x, z, seed, octaves = 4) { + let total = 0; + let amp = 0.5; + let norm = 0; + let freq = 1; + for (let o = 0; o < octaves; o++) { + total += valueNoise(x * freq, z * freq, seed + o * 101) * amp; + norm += amp; + freq *= 2.03; + amp *= 0.5; + } + return total / Math.max(1e-6, norm); +} + +function smooth01(v, e0, e1) { + const t = Math.min(1, Math.max(0, (v - e0) / (e1 - e0))); + return t * t * (3 - 2 * t); +} + +/** + * Terrain knobs (settings.terrain overrides when present). + */ +export function terrainOpts() { + const t = settings.terrain || {}; + return { + seed: t.seed ?? 17, + /** Peak hill height on pad interior (m) — keep modest for SI human yardstick */ + amp: t.amp ?? 0.85, + /** Segments for mesh (higher = smoother) */ + segments: t.segments ?? 128, + /** Heightfield grid resolution (verts per side) for Rapier */ + grid: t.grid ?? 97, + /** Shore drop band (m) inside islandRadius — slow then sharp to shelf */ + shoreBand: t.shoreBand ?? WORLD.shoreBand ?? 6.5, + islandRadius: t.islandRadius ?? WORLD.islandRadius, + waterY: t.waterY ?? WORLD.waterY ?? 0, + /** Island shelf weld (−5 m) */ + seafloorY: t.seafloorY ?? WORLD.seafloorY ?? -5, + /** Deep ocean floor (−50 m) */ + oceanFloorY: t.oceanFloorY ?? WORLD.oceanFloorY ?? -50, + /** Radial metres past pad to reach oceanFloorY */ + oceanDepthBand: t.oceanDepthBand ?? WORLD.oceanDepthBand ?? 80, + /** Flat pad radius (spawn / path cast comfort) before hills rise */ + flatCore: t.flatCore ?? 8, + /** + * Shore ease power (>1 = slow first, sharper later into −5 m). + * 1.0 = linear · 1.75 = preferred beach drop + */ + shoreEasePow: t.shoreEasePow ?? 1.75 + }; +} + +/** + * Shore / ocean bathymetry only (no land FBM). + * - Inside shore band: slow then sharp drop from near waterY → seafloorY (−5) + * - Past pad: deepen seafloorY → oceanFloorY (−50) + * @param {number} r radial metres from origin + * @param {ReturnType} opts + * @param {number} [landH] interior land height at this xz (for shore start) + */ +export function bathymetryAt(r, opts = terrainOpts(), landH = 0) { + const pad = opts.islandRadius; + const waterY = opts.waterY ?? 0; + const shelfY = opts.seafloorY ?? WORLD.seafloorY ?? -5; + const deepY = opts.oceanFloorY ?? WORLD.oceanFloorY ?? -50; + const shoreBand = Math.max(1.5, opts.shoreBand ?? 6.5); + const oceanBand = Math.max(8, opts.oceanDepthBand ?? 80); + const pow = Math.max(1.05, opts.shoreEasePow ?? 1.75); + + // Open ocean beyond island shelf weld + if (r >= pad) { + const t = Math.min(1, (r - pad) / oceanBand); + // Slight ease into deep — most drop early past shelf, then approach −50 + const u = 1 - Math.pow(1 - t, 1.35); + return shelfY * (1 - u) + deepY * u; + } + + const shoreInner = Math.max(0, pad - shoreBand); + if (r <= shoreInner) { + // Dry interior — caller uses land FBM + return landH; + } + + // Entering water: t=0 at dry edge, t=1 at pad (shelf weld −5 m) + const t = (r - shoreInner) / Math.max(1e-4, pad - shoreInner); + // Slow first, sharper later (power > 1) + const u = Math.pow(Math.min(1, Math.max(0, t)), pow); + // Start just under waterline from land (or waterY if land already low) + const startY = Math.min(Math.max(landH, waterY - 0.02), waterY + 0.08); + // Mid-shore stays shallow, then drops hard toward shelf + return startY * (1 - u) + shelfY * u; +} + +/** + * Exact CPU land/seafloor height at world XZ (metres). Same function as mesh bake. + * Continuous into water: shore slope → −5 m weld → deep ocean to −50 m. + * Water surface is WORLD.waterY (0) — separate StageWater layer. + * @param {number} x + * @param {number} z + * @param {ReturnType} [opts] + */ +export function heightAt(x, z, opts = terrainOpts()) { + const r = Math.hypot(x, z); + const pad = opts.islandRadius; + const shoreInner = Math.max(0, pad - Math.max(1.5, opts.shoreBand ?? 6.5)); + + // Open ocean — no land FBM + if (r >= pad) { + return bathymetryAt(r, opts, opts.seafloorY ?? -5); + } + + // Snakey-style multi-band FBM (scaled down for lab SI) + const rolling = (fbm(x * 0.012 + 13.7, z * 0.012 + 71.3, opts.seed, 3) - 0.5) * 2.0; + const medium = (fbm(x * 0.05 + 3.1, z * 0.05 + 9.4, opts.seed + 7, 3) - 0.5) * 0.55; + const micro = (fbm(x * 0.22, z * 0.22, opts.seed + 13, 2) - 0.5) * 0.12; + let h = (rolling + medium + micro) * opts.amp; + + // Flat core for spawn / combat pad (smooth rise outward) + const core = smooth01(r, opts.flatCore * 0.65, opts.flatCore * 1.35); + h *= core; + + // Shore / underwater: continuous bathymetry (slow→sharp to −5 m) + if (r > shoreInner) { + return bathymetryAt(r, opts, h); + } + + return h; +} + +/** + * True if (x,z) is dry land (above water with margin). + */ +export function isDryLand(x, z, opts = terrainOpts(), margin = 0.04) { + return heightAt(x, z, opts) > (opts.waterY ?? 0) + margin; +} + +/** + * Bilinear sample from a pre-baked height grid (matches Rapier heightfield). + * @param {Float32Array} heights (n+1)*(n+1) column-major? we use row-major j*stride+i + * @param {number} n segments (ncols = nrows = n) + * @param {number} size full extent metres + * @param {number} x + * @param {number} z + */ +export function heightAtFromGrid(heights, n, size, x, z) { + const verts = n + 1; + const half = size * 0.5; + const u = (x + half) / size; + const v = (z + half) / size; + if (u < 0 || u > 1 || v < 0 || v > 1) { + // Outside baked field — full bathymetry (shelf → deep ocean) + const half = size * 0.5; + const wx = (u - 0.5) * size; + const wz = (v - 0.5) * size; + // u/v out of range: use world xz from caller context — fall back deep + return terrainOpts().oceanFloorY ?? WORLD.oceanFloorY ?? -50; + } + const fx = u * n; + const fz = v * n; + const i0 = Math.floor(fx); + const j0 = Math.floor(fz); + const i1 = Math.min(n, i0 + 1); + const j1 = Math.min(n, j0 + 1); + const tx = fx - i0; + const tz = fz - j0; + const h00 = heights[j0 * verts + i0]; + const h10 = heights[j0 * verts + i1]; + const h01 = heights[j1 * verts + i0]; + const h11 = heights[j1 * verts + i1]; + const hx0 = h00 * (1 - tx) + h10 * tx; + const hx1 = h01 * (1 - tx) + h11 * tx; + return hx0 * (1 - tz) + hx1 * tz; +} + +/** + * Bake Float32 height grid for Rapier + mesh. + * @returns {{ heights: Float32Array, n: number, size: number, verts: number }} + */ +export function bakeHeightGrid(opts = terrainOpts()) { + const n = Math.max(8, (opts.grid | 0) - 1); // segments + const verts = n + 1; + // Cover island + shore weld + ocean depth band so Rapier walks into water / deep shelf + const oceanBand = opts.oceanDepthBand ?? WORLD.oceanDepthBand ?? 80; + const size = (opts.islandRadius + oceanBand * 0.85) * 2; + const half = size * 0.5; + const heights = new Float32Array(verts * verts); + for (let j = 0; j < verts; j++) { + for (let i = 0; i < verts; i++) { + const x = -half + (i / n) * size; + const z = -half + (j / n) * size; + heights[j * verts + i] = heightAt(x, z, opts); + } + } + return { heights, n, size, verts }; +} + +/** + * Visual + sample terrain mesh for the island pad. + */ +export class IslandHeightfield { + constructor(opts = {}) { + this.opts = { ...terrainOpts(), ...opts }; + const baked = bakeHeightGrid(this.opts); + this.heights = baked.heights; + this.n = baked.n; + this.size = baked.size; + this.verts = baked.verts; + + const segs = this.opts.segments; + const geo = new PlaneGeometry(this.size, this.size, segs, segs); + geo.rotateX(-Math.PI / 2); + const pos = geo.attributes.position; + const colors = new Float32Array(pos.count * 3); + /** + * Lab-readable meadow — NEVER use environment.floorColor (#14181d-class void). + * Dark stage slab color is for Ground dissolve only; island must read as grass/dirt + * or the whole playfield is pitch black against fog/backdrop (live bug 2026-08). + */ + const t = settings.terrain || {}; + const meadow = getColor(t.meadowColor || '#3f6b3a').clone(); + // Warm sand shore (WORLD.sandColor) — never white/snow + const shore = getColor( + t.shoreColor || WORLD.sandColor || settings.environment?.shoreColor || '#c2a86a' + ).clone(); + const dirt = getColor(t.dirtColor || '#6f5435').clone(); + const sandDeep = getColor(WORLD.seafloorColor || '#8a7350').clone(); + const deepOcean = getColor('#4a3a28').clone(); + const waterY = this.opts.waterY ?? 0; + const shelfY = this.opts.seafloorY ?? -5; + for (let i = 0; i < pos.count; i++) { + const x = pos.getX(i); + const z = pos.getZ(i); + const h = this.sample(x, z); + pos.setY(i, h); + const r = Math.hypot(x, z); + const shoreAmt = smooth01(r, this.opts.islandRadius - this.opts.shoreBand, this.opts.islandRadius); + const wetSand = smooth01(r, this.opts.islandRadius - this.opts.shoreBand * 0.45, this.opts.islandRadius); + const under = h < waterY - 0.02 ? smooth01(waterY - h, 0, Math.abs(shelfY) + 2) : 0; + const deepAmt = h < shelfY + 1 ? smooth01(shelfY - h, 0, 40) : 0; + const dirtAmt = fbm(x * 0.27, z * 0.27, this.opts.seed + 3, 3); + const sandCol = shore.clone().lerp(sandDeep, wetSand * 0.55).lerp(deepOcean, deepAmt * 0.7); + const c = meadow + .clone() + .lerp(dirt, dirtAmt * 0.35 * (1 - shoreAmt)) + .lerp(sandCol, Math.max(shoreAmt, under) * 0.95); + // Guard: never allow near-white terrain (snow bug) + const lum = (c.r + c.g + c.b) / 3; + if (lum > 0.72) c.lerp(sandDeep, 0.55); + colors[i * 3] = c.r; + colors[i * 3 + 1] = c.g; + colors[i * 3 + 2] = c.b; + } + geo.setAttribute('color', new Float32BufferAttribute(colors, 3)); + geo.computeVertexNormals(); + geo.computeBoundingSphere(); + + this.material = new MeshStandardMaterial({ + vertexColors: true, + roughness: 0.85, + metalness: 0.0, + dithering: true, + // Readable on cinematic stage even if fill light is low + emissive: new Color(0x1a2e14), + emissiveIntensity: 0.28 + }); + this.mesh = new Mesh(geo, this.material); + this.mesh.name = 'IslandHeightfield'; + this.mesh.receiveShadow = true; + this.mesh.castShadow = false; + this.mesh.userData.terrain = true; + this.mesh.userData.heightfield = true; + // Render LAYER.WORLD (bit 0) — not TERRAIN_LAYER L0–L3 (authoring labels only) + this.mesh.layers.set(LAYER.WORLD); + } + + /** + * Height at world XZ (grid bilinear inside field, formula outside). + * @param {number} x + * @param {number} z + */ + sample(x, z) { + const half = this.size * 0.5; + if (x < -half || x > half || z < -half || z > half) { + return heightAt(x, z, this.opts); + } + return heightAtFromGrid(this.heights, this.n, this.size, x, z); + } + + /** + * Water surface Y at xz (StageWater should own waves; this is static base). + */ + waterBaseY() { + return this.opts.waterY; + } + + /** Data for PhysicsWorld.addHeightfield */ + rapierDesc() { + // Rapier expects nrows/ncols cells → heights length (nrows+1)*(ncols+1) + // column-major in some versions; fleet uses row-major j*verts+i matching threejs example + return { + nrows: this.n, + ncols: this.n, + heights: this.heights, + scale: { x: this.size, y: 1, z: this.size } + }; + } + + dispose() { + this.mesh.geometry?.dispose?.(); + this.material?.dispose?.(); + } +} diff --git a/src/world/IslandTown.js b/src/world/IslandTown.js new file mode 100644 index 0000000..5bdb8b8 --- /dev/null +++ b/src/world/IslandTown.js @@ -0,0 +1,168 @@ +/** + * Small river-town style settlement planted ON the play island (heightfield). + * Not a snow map — warm timber / sand / thatch materials, SI scale. + * Replaces oversized white/snow GLBs that towered above terrain. + */ +import { + BoxGeometry, + ConeGeometry, + CylinderGeometry, + Group, + Mesh, + MeshStandardMaterial, + Color, + Vector3 +} from 'three'; +import { WORLD } from '../config/worldScale.js'; +import { LAYER } from '../core/Layers.js'; + +const MATS = { + timber: () => + new MeshStandardMaterial({ color: new Color('#6b4a2e'), roughness: 0.88, metalness: 0.02 }), + plaster: () => + new MeshStandardMaterial({ color: new Color('#c4b49a'), roughness: 0.9, metalness: 0 }), + thatch: () => + new MeshStandardMaterial({ color: new Color('#8a6b3a'), roughness: 0.95, metalness: 0 }), + stone: () => + new MeshStandardMaterial({ color: new Color('#6a6660'), roughness: 0.92, metalness: 0.05 }), + sand: () => + new MeshStandardMaterial({ color: new Color(WORLD.sandColor || '#c2a86a'), roughness: 0.97, metalness: 0 }), + dock: () => + new MeshStandardMaterial({ color: new Color('#4a3828'), roughness: 0.9, metalness: 0 }) +}; + +function house(mats, w, d, h) { + const g = new Group(); + const body = new Mesh(new BoxGeometry(w, h, d), mats.plaster); + body.position.y = h * 0.5; + body.castShadow = body.receiveShadow = true; + g.add(body); + const roof = new Mesh(new ConeGeometry(Math.max(w, d) * 0.72, h * 0.55, 4), mats.thatch); + roof.position.y = h + h * 0.22; + roof.rotation.y = Math.PI / 4; + roof.castShadow = true; + g.add(roof); + return g; +} + +/** + * @param {{ + * sampleHeight?: (x:number,z:number)=>number, + * islandRadius?: number + * }} opts + */ +export class IslandTown { + constructor(opts = {}) { + this.sampleHeight = opts.sampleHeight || ((x, z) => 0); + this.islandRadius = opts.islandRadius ?? WORLD.islandRadius; + this.group = new Group(); + this.group.name = 'IslandTown'; + this._mats = { + timber: MATS.timber(), + plaster: MATS.plaster(), + thatch: MATS.thatch(), + stone: MATS.stone(), + sand: MATS.sand(), + dock: MATS.dock() + }; + } + + /** + * Place a compact riverside hamlet on the NE shore of the pad (dry land). + * Heights come from heightfield so town sits ON terrain, not floating snow slab. + * No second ground plane, no second water mesh — pier rides WORLD.waterY only. + */ + build() { + const R = this.islandRadius; + // Village center: inland from shore so feet stay dry (on play island) + const cx = R * 0.38; + const cz = R * 0.22; + const baseY = this.sampleHeight(cx, cz); + + // Thin sand plaza flush to terrain (not a tall white slab) + const plaza = new Mesh( + new CylinderGeometry(6.5, 6.5, 0.08, 28), + this._mats.sand + ); + plaza.position.set(cx, baseY + 0.02, cz); + plaza.receiveShadow = true; + plaza.castShadow = false; + plaza.name = 'town_plaza_sand'; + this.group.add(plaza); + + // Shallow sand creek bed (visual only — not a second water plane) + const creek = new Mesh( + new BoxGeometry(1.1, 0.06, 9.5), + this._mats.sand + ); + const creekY = this.sampleHeight(cx + 1.2, cz - 1.0); + creek.position.set(cx + 1.2, creekY + 0.01, cz - 0.5); + creek.rotation.y = 0.35; + creek.receiveShadow = true; + creek.name = 'town_creek_sand'; + this.group.add(creek); + + // Human-scale houses (~1.7–2.3 m eaves) — never towering snow GLBs + const plots = [ + { x: -2.8, z: -1.8, w: 2.2, d: 1.9, h: 1.85, rot: 0.1 }, + { x: 2.4, z: -1.4, w: 1.9, d: 2.0, h: 1.75, rot: -0.2 }, + { x: -1.2, z: 2.4, w: 2.3, d: 1.85, h: 2.05, rot: 0.35 }, + { x: 2.9, z: 1.9, w: 1.7, d: 1.65, h: 1.65, rot: -0.45 }, + { x: 0.15, z: -3.0, w: 2.6, d: 1.9, h: 1.95, rot: 0.05 } + ]; + + for (const p of plots) { + const wx = cx + p.x; + const wz = cz + p.z; + const y = this.sampleHeight(wx, wz); + const h = house(this._mats, p.w, p.d, p.h); + h.position.set(wx, y, wz); + h.rotation.y = p.rot; + this.group.add(h); + } + + // Stone well + const well = new Mesh(new CylinderGeometry(0.5, 0.58, 0.55, 12), this._mats.stone); + well.position.set(cx + 0.35, this.sampleHeight(cx + 0.35, cz + 0.15) + 0.28, cz + 0.15); + well.castShadow = true; + this.group.add(well); + + // Short pier toward open sea — planks at waterY (0), not seafloor (−5) + const pier = new Group(); + pier.name = 'town_pier'; + const dockY = WORLD.waterY ?? 0; + for (let i = 0; i < 5; i++) { + const plank = new Mesh(new BoxGeometry(1.15, 0.1, 0.85), this._mats.dock); + const px = cx + 5.0 + i * 1.1; + const pz = cz + 3.6 + i * 0.32; + plank.position.set(px, dockY + 0.12, pz); + plank.rotation.y = 0.3; + plank.castShadow = true; + pier.add(plank); + const post = new Mesh(new CylinderGeometry(0.07, 0.09, 1.05, 6), this._mats.timber); + post.position.set(px + 0.35, dockY + 0.45, pz + 0.3); + pier.add(post); + } + this.group.add(pier); + + this.group.traverse((o) => { + if (o.isMesh) o.layers.set(LAYER.WORLD); + }); + + console.info( + `[IslandTown] riverside hamlet ON island @ (${cx.toFixed(1)}, ${baseY.toFixed(2)}, ${cz.toFixed(1)}) · waterY=${WORLD.waterY} seafloorY=${WORLD.seafloorY}` + ); + return this; + } + + dispose() { + this.group.traverse((o) => { + o.geometry?.dispose?.(); + if (o.material) { + if (Array.isArray(o.material)) o.material.forEach((m) => m.dispose()); + else o.material.dispose(); + } + }); + this.group.removeFromParent(); + } +} diff --git a/src/world/OpenSeaShells.js b/src/world/OpenSeaShells.js new file mode 100644 index 0000000..1b4bb8a --- /dev/null +++ b/src/world/OpenSeaShells.js @@ -0,0 +1,143 @@ +import { Box3, Group, Vector3, MeshStandardMaterial, Color } from 'three'; +import { clone as skeletonClone } from 'three/addons/utils/SkeletonUtils.js'; +import { WORLD } from '../config/worldScale.js'; +import { LAYER } from '../core/Layers.js'; + +/** + * Horizon island shells around the lab pad. + * Weld mesh bottoms to WORLD.seafloorY shelf (−5 m); deep ocean is −50 m. + * Water surface stays at waterY (0). Fix white/snow materials → sand/rock. + */ +const ISLAND_CDN = + 'https://assets.grudge-studio.com/models/worlds/small_island.glb'; + +const SAND = new Color(WORLD.sandColor || '#c2a86a'); +const ROCK = new Color('#6a655c'); +const DIRT = new Color('#5c4a32'); + +function fixIslandMaterials(root) { + root.traverse((o) => { + if (!o.isMesh || !o.material) return; + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (let i = 0; i < mats.length; i++) { + const m = mats[i]; + const hasMap = !!(m.map || m.emissiveMap); + // White / snow / untextured → sand & rock + const c = m.color; + const isWhite = + c && c.r > 0.85 && c.g > 0.85 && c.b > 0.85; + const name = String(m.name || o.name || '').toLowerCase(); + const looksSnow = /snow|ice|winter|white/.test(name); + if (!hasMap || isWhite || looksSnow) { + const repl = new MeshStandardMaterial({ + color: looksSnow || isWhite ? SAND : ROCK, + roughness: 0.94, + metalness: 0.02, + map: m.map || null, + normalMap: m.normalMap || null + }); + if (!repl.map) { + // tint variation by mesh + repl.color = o.id % 2 === 0 ? SAND.clone() : DIRT.clone(); + } + mats[i] = repl; + m.dispose?.(); + } else { + // Pull pure white albedo down toward sand + if (c && c.r > 0.9 && c.g > 0.9 && c.b > 0.9) { + c.copy(SAND); + } + m.roughness = Math.max(m.roughness ?? 0.8, 0.85); + } + } + o.material = Array.isArray(o.material) ? mats : mats[0]; + o.castShadow = true; + o.receiveShadow = true; + o.layers.set(LAYER.WORLD); + }); +} + +/** Plant so world-space bbox.min.y === seafloorY (weld to ocean floor). */ +function plantOnSeafloor(obj, seafloorY) { + obj.updateMatrixWorld(true); + const box = new Box3().setFromObject(obj); + const dy = seafloorY - box.min.y; + obj.position.y += dy; + obj.updateMatrixWorld(true); +} + +export class OpenSeaShells { + /** + * @param {object} opts + * @param {import('three').Scene} opts.scene + * @param {import('../loaders/AssetLoader.js').AssetLoader} opts.assets + */ + constructor(opts) { + this.scene = opts.scene; + this.assets = opts.assets; + this.group = new Group(); + this.group.name = 'OpenSeaShells'; + this.scene.add(this.group); + } + + async init() { + let gltf; + try { + gltf = await this.assets.loadGLTF(ISLAND_CDN); + } catch (err) { + console.warn('[OpenSeaShells] island CDN fail', err); + return this; + } + const src = gltf.scene || gltf.scenes?.[0]; + if (!src) return this; + + const R = WORLD.islandRadius; + const seafloorY = WORLD.seafloorY ?? -5; + // Target island diameters (m) — SI, not snow mountains + const placements = [ + { a: 0.4, d: R * 2.4, diameter: 14 }, + { a: 1.8, d: R * 2.9, diameter: 18 }, + { a: 3.4, d: R * 2.55, diameter: 12 }, + { a: 4.9, d: R * 3.2, diameter: 22 }, + { a: 5.7, d: R * 2.7, diameter: 11 } + ]; + + const boxTmp = new Box3(); + const size = new Vector3(); + + for (const p of placements) { + const island = skeletonClone(src); + island.name = 'HorizonIsland'; + fixIslandMaterials(island); + + island.updateMatrixWorld(true); + boxTmp.setFromObject(island); + boxTmp.getSize(size); + const maxXZ = Math.max(size.x, size.z, 1e-3); + // Scale so island XZ diameter matches target (prevents towering snow peaks) + const s = p.diameter / maxXZ; + // Cap vertical scale — never taller than ~4 m above seafloor after plant + island.scale.setScalar(s); + island.updateMatrixWorld(true); + boxTmp.setFromObject(island); + const h = boxTmp.max.y - boxTmp.min.y; + if (h > 6) { + island.scale.multiplyScalar(5.5 / h); + } + + island.position.set(Math.sin(p.a) * p.d, 0, Math.cos(p.a) * p.d); + island.rotation.y = p.a + 0.8; + plantOnSeafloor(island, seafloorY); + + this.group.add(island); + } + console.info( + `[OpenSeaShells] ${placements.length} horizon islands welded seafloorY=${seafloorY} waterY=${WORLD.waterY}` + ); + return this; + } + + dispose() { + this.group.removeFromParent(); + } +} diff --git a/src/world/Seafloor.js b/src/world/Seafloor.js new file mode 100644 index 0000000..fd6032f --- /dev/null +++ b/src/world/Seafloor.js @@ -0,0 +1,105 @@ +/** + * Deep ocean seafloor at WORLD.oceanFloorY (−50 m by default). + * Island shelf weld is WORLD.seafloorY (−5 m) — continuous slope is the + * IslandHeightfield bathymetry; this plane is the deep sand under open sea. + * + * Water surface stays at waterY = 0 (StageWater only). + */ +import { + Mesh, + PlaneGeometry, + MeshStandardMaterial, + Color, + RepeatWrapping, + TextureLoader, + CanvasTexture +} from 'three'; +import { WORLD } from '../config/worldScale.js'; +import { LAYER } from '../core/Layers.js'; + +function makeSandTexture() { + const c = document.createElement('canvas'); + c.width = c.height = 256; + const ctx = c.getContext('2d'); + ctx.fillStyle = '#8a6e48'; + ctx.fillRect(0, 0, 256, 256); + for (let i = 0; i < 4000; i++) { + const x = Math.random() * 256; + const y = Math.random() * 256; + const v = 100 + Math.random() * 70; + ctx.fillStyle = `rgb(${v},${v * 0.78},${v * 0.5})`; + ctx.fillRect(x, y, 1 + Math.random() * 2, 1 + Math.random() * 2); + } + const tex = new CanvasTexture(c); + tex.wrapS = tex.wrapT = RepeatWrapping; + tex.repeat.set(64, 64); + tex.needsUpdate = true; + return tex; +} + +export class Seafloor { + constructor() { + const size = WORLD.waterSize * 1.25; + // Deep ocean floor — island heightfield handles −5 m shelf + slope + const y = WORLD.oceanFloorY ?? WORLD.seafloorY ?? -50; + const sand = new Color(WORLD.seafloorColor || WORLD.sandColor || '#8a7350'); + + this.material = new MeshStandardMaterial({ + color: sand, + map: makeSandTexture(), + roughness: 0.97, + metalness: 0.0, + dithering: true + }); + + // Slight tessellation so fog/lighting doesn't read as infinite flat plastic + const segs = 8; + this.mesh = new Mesh(new PlaneGeometry(size, size, segs, segs), this.material); + this.mesh.rotation.x = -Math.PI / 2; + this.mesh.position.y = y; + this.mesh.name = 'Seafloor'; + this.mesh.receiveShadow = true; + this.mesh.castShadow = false; + this.mesh.layers.set(LAYER.WORLD); + this.mesh.renderOrder = -3; + this.mesh.userData.seafloor = true; + this.mesh.userData.oceanFloorY = y; + this.mesh.userData.shelfY = WORLD.seafloorY ?? -5; + this.group = this.mesh; + + this._tryLoadMap([ + 'https://assets.grudge-studio.com/textures/terrain/sand_albedo.jpg', + 'https://assets.grudge-studio.com/textures/ground/sand.jpg' + ]); + } + + _tryLoadMap(urls) { + const loader = new TextureLoader(); + loader.setCrossOrigin('anonymous'); + let i = 0; + const next = () => { + if (i >= urls.length) return; + const url = urls[i++]; + loader.load( + url, + (tex) => { + tex.wrapS = tex.wrapT = RepeatWrapping; + tex.repeat.set(48, 48); + tex.colorSpace = 'srgb'; + this.material.map = tex; + this.material.needsUpdate = true; + console.info('[Seafloor] sand map', url, 'y=', this.mesh.position.y); + }, + undefined, + () => next() + ); + }; + next(); + } + + dispose() { + this.mesh.geometry?.dispose?.(); + this.material.map?.dispose?.(); + this.material.dispose?.(); + } +} diff --git a/src/world/StageWater.js b/src/world/StageWater.js new file mode 100644 index 0000000..85aa0af --- /dev/null +++ b/src/world/StageWater.js @@ -0,0 +1,265 @@ +import { + Color, + Mesh, + PlaneGeometry, + ShaderMaterial, + DoubleSide, + Vector3, + RepeatWrapping, + TextureLoader +} from 'three'; +import { noiseGLSL } from '../shaders/lib/noise.glsl.js'; +import { frame } from '../core/FrameUniforms.js'; +import { LAYER } from '../core/Layers.js'; +import { WORLD } from '../config/worldScale.js'; +import { settings } from '../config/settings.js'; + +/** + * Open-sea stage water — freeride ring around Dev Island. + * + * Patterns aligned with water.grudge-studio.com open-ocean feel (waves, foam, + * storm amp) while keeping lab hole under islandRadius for Ground pad + nodes. + * CPU sampleHeight drives windsurf freeride Y follow. + * + * Optional normal map from fleet CDN / three examples (non-blocking). + */ +export class StageWater { + constructor() { + const size = WORLD.waterSize; + const segs = Math.min(160, Math.max(96, Math.floor(size / 2.5))); + + this._storm = 0; // 0 calm → 1 storm (drives amp/freq) + this._stormTarget = 0.35; + + this.uniforms = { + uTime: { value: 0 }, + uDeep: { value: new Color(0.01, 0.06, 0.14) }, + uShallow: { value: new Color(0.07, 0.38, 0.48) }, + uFoam: { value: new Color(0.88, 0.96, 1.0) }, + uOpacity: { value: 0.82 }, + uWaveAmp: { value: 0.14 }, + uWaveFreq: { value: 0.28 }, + uStorm: { value: 0.35 }, + uFresnel: { value: 1.45 }, + uEnvIntensity: { value: 0.65 }, + uIslandRadius: { value: WORLD.islandRadius }, + uEnvMap: frame.uEnvMap, + uSunDir: { value: new Vector3(-0.55, 0.72, -0.4).normalize() }, + uHasNormal: { value: 0 }, + uNormalMap: { value: null }, + uNormalScale: { value: 0.55 } + }; + + this.material = new ShaderMaterial({ + transparent: true, + depthWrite: false, + side: DoubleSide, + toneMapped: true, + uniforms: this.uniforms, + vertexShader: /* glsl */ ` + uniform float uTime; + uniform float uWaveAmp; + uniform float uWaveFreq; + uniform float uStorm; + varying vec3 vWorld; + varying vec3 vNormalW; + varying float vElev; + varying vec2 vUv; + ${noiseGLSL} + void main() { + vUv = uv; + vec3 p = position; + float amp = uWaveAmp * mix(1.0, 2.8, uStorm); + float freq = uWaveFreq * mix(1.0, 1.35, uStorm); + float t = uTime * mix(1.0, 1.55, uStorm); + float w1 = snoise(vec3(p.x * freq, t * 0.22, p.y * freq)); + float w2 = snoise(vec3(p.x * freq * 2.1 + 3.0, t * 0.35, p.y * freq * 1.7)); + float w3 = snoise(vec3(p.x * freq * 4.2, t * 0.55, p.y * freq * 3.8 + 1.7)); + float elev = (w1 * 0.55 + w2 * 0.3 + w3 * 0.15 * uStorm) * amp; + p.z += elev; + vElev = elev; + vec4 world = modelMatrix * vec4(p, 1.0); + vWorld = world.xyz; + float e = 0.4; + float hx = snoise(vec3((p.x + e) * freq, t * 0.22, p.y * freq)) + - snoise(vec3((p.x - e) * freq, t * 0.22, p.y * freq)); + float hy = snoise(vec3(p.x * freq, t * 0.22, (p.y + e) * freq)) + - snoise(vec3(p.x * freq, t * 0.22, (p.y - e) * freq)); + vec3 n = normalize(vec3(-hx * amp * 2.2, 1.0, -hy * amp * 2.2)); + vNormalW = normalize(mat3(modelMatrix) * n); + gl_Position = projectionMatrix * viewMatrix * world; + } + `, + fragmentShader: /* glsl */ ` + uniform vec3 uDeep; + uniform vec3 uShallow; + uniform vec3 uFoam; + uniform float uOpacity; + uniform float uFresnel; + uniform float uEnvIntensity; + uniform float uIslandRadius; + uniform float uStorm; + uniform vec3 uSunDir; + uniform sampler2D uEnvMap; + uniform float uHasNormal; + uniform sampler2D uNormalMap; + uniform float uNormalScale; + uniform float uTime; + varying vec3 vWorld; + varying vec3 vNormalW; + varying float vElev; + varying vec2 vUv; + ${noiseGLSL} + + vec3 sampleEnv(vec3 r) { + if (uEnvIntensity < 0.01) return vec3(0.04, 0.07, 0.1); + vec2 uv = vec2(atan(r.z, r.x), asin(clamp(r.y, -1.0, 1.0))); + uv *= vec2(0.1591549, 0.3183099); + uv += 0.5; + return texture2D(uEnvMap, uv).rgb; + } + + void main() { + float dist = length(vWorld.xz); + float islandMask = smoothstep(uIslandRadius * 0.92, uIslandRadius * 1.08, dist); + if (islandMask < 0.02) discard; + + vec3 N = normalize(vNormalW); + if (uHasNormal > 0.5) { + vec2 nuv = vUv * 28.0 + vec2(uTime * 0.03, uTime * -0.02); + vec3 nt = texture2D(uNormalMap, nuv).xyz * 2.0 - 1.0; + N = normalize(N + nt * uNormalScale * (0.5 + uStorm)); + } + vec3 V = normalize(cameraPosition - vWorld); + float ndv = max(0.0, dot(N, V)); + float fres = pow(1.0 - ndv, mix(2.0, 5.0, clamp(uFresnel * 0.5, 0.0, 1.0))); + + float depthT = smoothstep(uIslandRadius, uIslandRadius * 3.2, dist); + vec3 deepStorm = mix(uDeep, uDeep * 0.55, uStorm); + vec3 base = mix(uShallow, deepStorm, depthT); + + float foam = smoothstep(0.015, 0.09 * (1.0 + uStorm), vElev) * (1.0 - depthT * 0.45); + foam *= snoise01(vWorld * 2.4) * 0.7 + 0.3; + foam = mix(foam, foam * 1.4, uStorm); + base = mix(base, uFoam, foam * mix(0.5, 0.75, uStorm)); + + // Soft rain streaks when stormy + float rain = uStorm * step(0.72, snoise01(vWorld * 8.0 + uTime * 3.0)) * 0.12; + base += vec3(rain); + + vec3 R = reflect(-V, N); + vec3 env = sampleEnv(R) * uEnvIntensity; + float sun = pow(max(0.0, dot(R, normalize(uSunDir))), mix(72.0, 36.0, uStorm)) * mix(1.5, 0.7, uStorm); + + vec3 col = base * (0.5 + 0.5 * ndv) + env * fres + vec3(sun); + float alpha = uOpacity * islandMask * mix(0.55, 0.94, fres); + gl_FragColor = vec4(col, alpha); + } + ` + }); + + this.mesh = new Mesh(new PlaneGeometry(size, size, segs, segs), this.material); + this.mesh.rotation.x = -Math.PI / 2; + // Sea surface at 0 m — seafloor is a separate mesh at WORLD.seafloorY (−5) + this.mesh.position.y = WORLD.waterY ?? 0; + this.mesh.name = 'StageWater'; + this.mesh.userData.waterSurface = true; + this.mesh.receiveShadow = false; + this.mesh.castShadow = false; + this.mesh.layers.set(LAYER.WORLD); + this.mesh.renderOrder = -1; + this.group = this.mesh; + + // Non-blocking normal map (open-sea read) — fleet CDN or three.js example + this._loadNormalMap(); + } + + _loadNormalMap() { + // NEVER hit assets.grudge-studio.com or water.grudge-studio.com — both return + // 403 without Access-Control-Allow-Origin (CORS noise + failed water look). + // Prefer same-origin public copy, then jsDelivr / three.js examples (CORS OK). + const origin = + typeof window !== 'undefined' && window.location?.origin + ? window.location.origin + : 'https://casting.grudge.studio'; + const candidates = [ + `${origin}/textures/waternormals.jpg`, + 'https://cdn.jsdelivr.net/gh/mrdoob/three.js@r185/examples/textures/waternormals.jpg', + 'https://threejs.org/examples/textures/waternormals.jpg', + ]; + const loader = new TextureLoader(); + loader.setCrossOrigin('anonymous'); + let i = 0; + const tryNext = () => { + if (i >= candidates.length) { + console.warn('[StageWater] no water normal map — flat water'); + return; + } + const url = candidates[i++]; + loader.load( + url, + (tex) => { + tex.wrapS = tex.wrapT = RepeatWrapping; + tex.needsUpdate = true; + this.uniforms.uNormalMap.value = tex; + this.uniforms.uHasNormal.value = 1; + this._normalTex = tex; + console.info('[StageWater] normal map', url); + }, + undefined, + () => tryNext() + ); + }; + tryNext(); + } + + /** + * 0 = calm glass · 1 = storm (higher waves, foam, rain grain). + * @param {number} t + */ + setStorm(t) { + this._stormTarget = Math.max(0, Math.min(1, t)); + } + + /** + * CPU wave height (m) — mirrors vertex amp/storm for freeride. + */ + sampleHeight(x, z, time) { + const t = time ?? this.uniforms.uTime.value ?? 0; + const storm = this.uniforms.uStorm?.value ?? 0; + const amp = (this.uniforms.uWaveAmp?.value ?? 0.14) * (1 + storm * 1.8); + const freq = (this.uniforms.uWaveFreq?.value ?? 0.28) * (1 + storm * 0.35); + const tt = t * (1 + storm * 0.55); + const w1 = Math.sin(x * freq + tt * 0.22) * Math.cos(z * freq + tt * 0.18); + const w2 = Math.sin(x * freq * 2.1 + 3 + tt * 0.35) * Math.cos(z * freq * 1.7 + tt * 0.3); + const w3 = Math.sin(x * freq * 4.2 + tt * 0.55) * Math.cos(z * freq * 3.8 + 1.7); + const elev = (w1 * 0.55 + w2 * 0.3 + w3 * 0.15 * storm) * amp; + return (WORLD.waterY || 0) + elev; + } + + update(elapsed) { + this.uniforms.uTime.value = elapsed; + this.uniforms.uIslandRadius.value = WORLD.islandRadius; + // Soft breathe storm so freeride feels open ocean + this._storm += (this._stormTarget - this._storm) * 0.02; + // Mild automatic weather cycle (can override with setStorm) + const cycle = 0.25 + 0.45 * (0.5 + 0.5 * Math.sin(elapsed * 0.04)); + if (settings.walk?.oceanStorm == null) { + this._stormTarget = cycle; + } else { + this._stormTarget = settings.walk.oceanStorm; + } + this.uniforms.uStorm.value = this._storm; + const g = settings.global || {}; + this.uniforms.uOpacity.value = 0.78 * (g.opacity ?? 1); + this.uniforms.uEnvIntensity.value = + 0.5 + 0.45 * (g.glow ?? 1) * (settings.environment?.envIntensity ?? 0.3); + this.uniforms.uWaveAmp.value = settings.walk?.oceanWaveAmp ?? 0.14; + } + + dispose() { + this.mesh.geometry.dispose(); + this.material.dispose(); + this._normalTex?.dispose?.(); + } +} diff --git a/src/world/StylizedGrassLayer.js b/src/world/StylizedGrassLayer.js new file mode 100644 index 0000000..02f6b27 --- /dev/null +++ b/src/world/StylizedGrassLayer.js @@ -0,0 +1,242 @@ +/** + * Stylized instanced grass on L0 heightfield (three-stylized patterns). + * + * Learns from Steve245270533/three-stylized: + * - Instanced blades on generated or external surface + * - Wind uniforms (strength / speed / direction) + * - Blade height ~ SI (here max ~0.55 m under human 1.8 m) + * - Caller-owned height sample — never a second heightmap + * + * Does **not** vendor the full three-stylized package; ports the meadow layer + * pattern onto IslandHeightfield. + * + * @see docs/THREE_LAYER_TERRAIN_SSOT.md + * @see https://github.com/Steve245270533/three-stylized + */ + +import { + BufferAttribute, + BufferGeometry, + Color, + DoubleSide, + DynamicDrawUsage, + Group, + InstancedMesh, + Matrix4, + MeshStandardMaterial, + Quaternion, + Vector3 +} from 'three'; +import { isDryLand, terrainOpts } from './IslandHeightfield.js'; +import { WORLD } from '../config/worldScale.js'; +import { settings } from '../config/settings.js'; + +const _m = new Matrix4(); +const _q = new Quaternion(); +const _p = new Vector3(); +const _s = new Vector3(); +const _up = new Vector3(0, 1, 0); + +function mulberry(seed) { + let s = (seed >>> 0) || 1; + return () => { + s = (s + 0x6d2b79f5) | 0; + let t = Math.imul(s ^ (s >>> 15), 1 | s); + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +/** Simple 3-vert blade geometry (root → tip) for instancing. */ +function makeBladeGeo() { + const geo = new BufferGeometry(); + // Triangle strip: base left, base right, tip + const pos = new Float32Array([ + -0.5, 0, 0, + 0.5, 0, 0, + 0, 1, 0.02 + ]); + const uv = new Float32Array([0, 0, 1, 0, 0.5, 1]); + geo.setAttribute('position', new BufferAttribute(pos, 3)); + geo.setAttribute('uv', new BufferAttribute(uv, 2)); + geo.computeVertexNormals(); + return geo; +} + +export class StylizedGrassLayer { + /** + * @param {{ + * scene: import('three').Scene, + * heightSample: (x: number, z: number) => number, + * islandRadius?: number, + * clearRadius?: number, + * density?: number, + * seed?: number, + * bladeMaxHeight?: number, + * bladeMinHeight?: number + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.heightSample = opts.heightSample; + this.islandRadius = opts.islandRadius ?? WORLD.islandRadius * 0.88; + this.clearRadius = opts.clearRadius ?? 6; + this.density = opts.density ?? 28; // blades per unit along ring sampling + this.seed = opts.seed ?? 401; + this.bladeMaxHeight = opts.bladeMaxHeight ?? 0.55; + this.bladeMinHeight = opts.bladeMinHeight ?? 0.22; + + this.group = new Group(); + this.group.name = 'StylizedGrassLayer'; + this.group.userData.terrainLayer = 'L2_vegetation'; + this.scene.add(this.group); + + this._elapsed = 0; + this._uTime = { value: 0 }; + this._uWind = { value: 0.22 }; + this._uWindDir = { value: new Vector3(0.6, 0, 0.4).normalize() }; + + const t = settings.terrain || {}; + const bottom = new Color(t.grassColorBottom || '#3d6b1a'); + const top = new Color(t.grassColorTop || '#a8d44a'); + + this.material = new MeshStandardMaterial({ + color: 0xffffff, + roughness: 0.85, + metalness: 0, + side: DoubleSide, + vertexColors: false + }); + + // Wind lean on upper verts (three-stylized-like) + this.material.onBeforeCompile = (shader) => { + shader.uniforms.uTime = this._uTime; + shader.uniforms.uWind = this._uWind; + shader.uniforms.uWindDir = this._uWindDir; + shader.uniforms.uColorBottom = { value: bottom }; + shader.uniforms.uColorTop = { value: top }; + shader.vertexShader = shader.vertexShader + .replace( + '#include ', + `#include +uniform float uTime; +uniform float uWind; +uniform vec3 uWindDir; +varying float vBladeH;` + ) + .replace( + '#include ', + `#include +vBladeH = transformed.y; +#ifdef USE_INSTANCING +{ + float h = transformed.y; + float lean = h * h * uWind; + float ph = instanceMatrix[3].x * 0.21 + instanceMatrix[3].z * 0.17; + float wave = sin(uTime * 1.35 + ph) * 0.55 + sin(uTime * 0.7 + ph * 1.7) * 0.45; + transformed.x += uWindDir.x * lean * wave; + transformed.z += uWindDir.z * lean * wave; +} +#endif` + ); + shader.fragmentShader = shader.fragmentShader + .replace( + '#include ', + `#include +varying float vBladeH; +uniform vec3 uColorBottom; +uniform vec3 uColorTop;` + ) + .replace( + '#include ', + `#include +diffuseColor.rgb = mix(uColorBottom, uColorTop, clamp(vBladeH, 0.0, 1.0));` + ); + }; + this.material.customProgramCacheKey = () => 'stylized-grass-v1'; + + this._buildInstances(); + } + + _buildInstances() { + const rnd = mulberry(this.seed); + const positions = []; + // Disk sample with density (forestoutline / stylized scatter style) + const area = + Math.PI * + (this.islandRadius * this.islandRadius - this.clearRadius * this.clearRadius); + const target = Math.min(12000, Math.max(200, Math.floor(area * this.density * 0.08))); + let guard = 0; + while (positions.length < target && guard++ < target * 40) { + const ang = rnd() * Math.PI * 2; + const r = + this.clearRadius + + rnd() * Math.max(1, this.islandRadius - this.clearRadius); + const x = Math.cos(ang) * r; + const z = Math.sin(ang) * r; + if (!isDryLand(x, z, terrainOpts(), 0.06)) continue; + const y = this.heightSample(x, z); + const h = + this.bladeMinHeight + + rnd() * (this.bladeMaxHeight - this.bladeMinHeight); + const yaw = rnd() * Math.PI * 2; + const w = 0.04 + rnd() * 0.08; + positions.push({ x, y, z, h, yaw, w }); + } + + this.count = positions.length; + const geo = makeBladeGeo(); + this.mesh = new InstancedMesh(geo, this.material, Math.max(1, this.count)); + this.mesh.instanceMatrix.setUsage(DynamicDrawUsage); + this.mesh.castShadow = false; + this.mesh.receiveShadow = true; + this.mesh.frustumCulled = false; + this.mesh.name = 'StylizedGrassBlades'; + + for (let i = 0; i < this.count; i++) { + const p = positions[i]; + _p.set(p.x, p.y, p.z); + _q.setFromAxisAngle(_up, p.yaw); + _s.set(p.w, p.h, p.w); + _m.compose(_p, _q, _s); + this.mesh.setMatrixAt(i, _m); + } + this.mesh.instanceMatrix.needsUpdate = true; + this.mesh.count = this.count; + this.group.add(this.mesh); + } + + /** + * @param {number} dt + * @param {number} [windStrength] + */ + update(dt, windStrength) { + this._elapsed = (this._elapsed || 0) + dt; + this._uTime.value = this._elapsed; + if (windStrength != null) this._uWind.value = windStrength; + else { + const w = settings.terrain?.grassWind ?? 0.22; + this._uWind.value = w; + } + } + + /** + * Sync sun for future lighting uniforms (API parity with three-stylized). + * @param {import('three').DirectionalLight} [light] + */ + syncDirectionalLight(light) { + if (!light?.position) return; + this._uWindDir.value + .set(light.position.x, 0, light.position.z) + .normalize(); + if (this._uWindDir.value.lengthSq() < 1e-6) { + this._uWindDir.value.set(0.6, 0, 0.4).normalize(); + } + } + + dispose() { + this.mesh?.geometry?.dispose?.(); + this.material?.dispose?.(); + this.group.removeFromParent(); + } +} diff --git a/src/world/WorldDrops.js b/src/world/WorldDrops.js new file mode 100644 index 0000000..4335289 --- /dev/null +++ b/src/world/WorldDrops.js @@ -0,0 +1,378 @@ +/** + * World drops — unequipped prefabs on terrain / ocean. + * + * Presentation (in-game dropped state): + * - Ground ring (tier border color) + * - Soft glow disc (bloom-friendly additive) + * - Hovering icon sprite (billboard) + * - Optional miniature 3D model (prod gltf) + * - Bob + spin; Y snap to ground or water surface + * + * Not equipped. Pickup / throw-from-bag only. + */ + +import { + AdditiveBlending, + CircleGeometry, + DoubleSide, + Group, + Mesh, + MeshBasicMaterial, + MeshStandardMaterial, + RingGeometry, + Sprite, + SpriteMaterial, + SRGBColorSpace, + Vector3 +} from 'three'; +import { WORLD } from '../config/worldScale.js'; +import { presentPrefab, tierPresent } from '../loot/prefabAssets.js'; + +const _v = new Vector3(); + +/** + * @typedef {object} WorldDropItem + * @property {string} id + * @property {Group} root + * @property {object} present from presentPrefab / bagItem + * @property {number} bobPhase + * @property {number} spin + * @property {boolean} onWater + * @property {Mesh|null} model + * @property {Sprite|null} sprite + */ + +export class WorldDrops { + /** + * @param {{ + * scene: import('three').Scene, + * camera: import('three').Camera, + * assets: import('../loaders/AssetLoader.js').AssetLoader, + * waterY?: number, + * groundY?: number, + * onToast?: (s: string) => void + * }} opts + */ + constructor(opts) { + this.scene = opts.scene; + this.camera = opts.camera; + this.assets = opts.assets; + this.waterY = opts.waterY ?? WORLD.waterY ?? -0.04; + this.groundY = opts.groundY ?? 0; + /** Optional island heightfield sample */ + this.heightSample = + typeof opts.heightSample === 'function' ? opts.heightSample : null; + this.onToast = opts.onToast || (() => {}); + + /** @type {WorldDropItem[]} */ + this.items = []; + this.group = new Group(); + this.group.name = 'WorldDrops'; + this.scene.add(this.group); + + this._texCache = new Map(); + this._modelCache = new Map(); + this._hoverH = 0.55; + } + + /** + * Surface Y at xz — land heightfield on pad, water skirt beyond. + * @param {number} x + * @param {number} z + */ + surfaceY(x, z) { + const r = Math.hypot(x, z); + const pad = WORLD.islandRadius ?? 22; + if (r < pad && this.heightSample) { + const h = this.heightSample(x, z); + if (Number.isFinite(h)) return h + 0.04; + } + if (r < pad) return this.groundY + 0.02; + return Math.max(this.waterY + 0.06, this.groundY + 0.02); + } + + /** + * @param {object} present presentPrefab() result or bag item + * @param {Vector3|{x,y,z}} pos + * @param {{ skipModel?: boolean }} [opts] + */ + async spawn(present, pos, opts = {}) { + if (!present) return null; + const p = present.iconUrl ? present : presentPrefab(present.raw || present); + if (!p) return null; + + const root = new Group(); + root.name = `drop_${p.id}`; + root.position.set(pos.x, this.surfaceY(pos.x, pos.z), pos.z); + root.userData.worldDrop = true; + root.userData.present = p; + + const tier = Math.max(0, Math.min(8, p.tier ?? 0)); + const tp = tierPresent(tier); + const glowHex = p.glowColor ?? tp.glow; + const borderHex = p.borderColor ?? tp.border; + // Mythic+ slightly larger glow (still same prefab systems) + const glowScale = tier >= 6 ? 1.25 : 1; + + // Glow disc (additive — feeds bloom) + const glow = new Mesh( + new CircleGeometry(0.42 * glowScale, 32), + new MeshBasicMaterial({ + color: glowHex, + transparent: true, + opacity: tier >= 6 ? 0.55 : 0.45, + blending: AdditiveBlending, + depthWrite: false, + side: DoubleSide + }) + ); + glow.rotation.x = -Math.PI / 2; + glow.position.y = 0.015; + root.add(glow); + + // Border ring + const border = new Mesh( + new RingGeometry(0.38 * glowScale, 0.48 * glowScale, 40), + new MeshBasicMaterial({ + color: borderHex, + transparent: true, + opacity: 0.9, + depthWrite: false, + side: DoubleSide + }) + ); + border.rotation.x = -Math.PI / 2; + border.position.y = 0.02; + root.add(border); + + // Soft emissive halo for bloom (standard material) + const halo = new Mesh( + new CircleGeometry(0.55 * glowScale, 24), + new MeshStandardMaterial({ + color: 0x000000, + emissive: glowHex, + emissiveIntensity: tier >= 6 ? 2.0 : 1.4, + transparent: true, + opacity: 0.35, + depthWrite: false, + side: DoubleSide + }) + ); + halo.rotation.x = -Math.PI / 2; + halo.position.y = 0.01; + root.add(halo); + + // Icon sprite + let sprite = null; + try { + const tex = await this._loadTexture(p.iconUrl); + const mat = new SpriteMaterial({ + map: tex, + transparent: true, + depthWrite: false, + fog: false + }); + sprite = new Sprite(mat); + sprite.scale.set(0.55, 0.55, 0.55); + sprite.position.y = this._hoverH; + root.add(sprite); + } catch { + // placeholder diamond + const stub = new Mesh( + new CircleGeometry(0.2, 6), + new MeshBasicMaterial({ color: borderHex, transparent: true, opacity: 0.85 }) + ); + stub.position.y = this._hoverH; + root.add(stub); + } + + // Optional 3D model mini + let model = null; + if (!opts.skipModel && p.modelUrl) { + try { + model = await this._loadModelMini(p.modelUrl); + if (model) { + model.position.y = this._hoverH * 0.55; + root.add(model); + } + } catch { + /* sprite only */ + } + } + + this.group.add(root); + const item = { + id: `${p.id}_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`, + root, + present: p, + bobPhase: Math.random() * Math.PI * 2, + spin: 0.6 + Math.random() * 0.8, + onWater: Math.hypot(pos.x, pos.z) >= 22, + model, + sprite, + qty: present.qty || 1 + }; + this.items.push(item); + return item; + } + + /** + * Throw from inventory: arc from player hand toward aim point. + * @param {object} present + * @param {Vector3} from + * @param {Vector3} to + */ + async throwFrom(present, from, to) { + const item = await this.spawn(present, to, { skipModel: false }); + if (!item) return null; + // Start at thrower, animate to landing over ~0.45s + item.root.position.copy(from); + item.throw = { + from: from.clone(), + to: new Vector3(to.x, this.surfaceY(to.x, to.z), to.z), + t: 0, + dur: 0.48 + }; + return item; + } + + /** + * Pickup nearest within radius. Returns bag item or null. + * @param {Vector3} playerPos + * @param {number} [radius=2.2] + */ + tryPickup(playerPos, radius = 2.2) { + let best = null; + let bestD = radius * radius; + for (const it of this.items) { + if (it.throw) continue; + const dx = it.root.position.x - playerPos.x; + const dz = it.root.position.z - playerPos.z; + const d2 = dx * dx + dz * dz; + if (d2 < bestD) { + bestD = d2; + best = it; + } + } + if (!best) return null; + this._remove(best); + const bag = { + id: best.present.id, + uuid: best.present.uuid, + name: best.present.name, + tier: best.present.tier, + qty: best.qty || 1, + iconUrl: best.present.iconUrl, + modelUrl: best.present.modelUrl, + weaponType: best.present.weaponType, + category: best.present.category, + borderColor: best.present.borderColor, + glowColor: best.present.glowColor + }; + this.onToast(`Picked up ${bag.name} (T${bag.tier})`); + return bag; + } + + _remove(item) { + const i = this.items.indexOf(item); + if (i >= 0) this.items.splice(i, 1); + this.group.remove(item.root); + item.root.traverse((o) => { + if (o.geometry) o.geometry.dispose?.(); + if (o.material) { + const mats = Array.isArray(o.material) ? o.material : [o.material]; + for (const m of mats) { + if (m.map && !this._texCache.has(m.map.uuid)) m.map.dispose?.(); + m.dispose?.(); + } + } + }); + } + + /** + * @param {number} dt + */ + update(dt) { + const t = performance.now() * 0.001; + for (const it of this.items) { + if (it.throw) { + it.throw.t += dt; + const u = Math.min(1, it.throw.t / it.throw.dur); + const e = 1 - (1 - u) * (1 - u); + it.root.position.lerpVectors(it.throw.from, it.throw.to, e); + it.root.position.y = + it.throw.from.y * (1 - e) + + it.throw.to.y * e + + Math.sin(u * Math.PI) * 1.4; + if (u >= 1) { + it.root.position.copy(it.throw.to); + it.throw = null; + it.onWater = Math.hypot(it.root.position.x, it.root.position.z) >= 22; + } + continue; + } + + const baseY = this.surfaceY(it.root.position.x, it.root.position.z); + const bob = Math.sin(t * 2.2 + it.bobPhase) * 0.06; + it.root.position.y = baseY; + + if (it.sprite) { + it.sprite.position.y = this._hoverH + bob; + // face camera (Sprite already billboards) + } + if (it.model) { + it.model.position.y = this._hoverH * 0.5 + bob * 0.5; + it.model.rotation.y += dt * it.spin; + } + + // Pulse glow opacity slightly + const glow = it.root.children.find((c) => c.material?.blending === AdditiveBlending); + if (glow?.material) { + glow.material.opacity = 0.35 + 0.15 * (0.5 + 0.5 * Math.sin(t * 3 + it.bobPhase)); + } + } + } + + async _loadTexture(url) { + if (!url) throw new Error('no icon'); + if (this._texCache.has(url)) return this._texCache.get(url); + const tex = await this.assets.loadTexture(url); + tex.colorSpace = SRGBColorSpace; + this._texCache.set(url, tex); + return tex; + } + + async _loadModelMini(url) { + if (this._modelCache.has(url)) { + return this._modelCache.get(url).clone(true); + } + try { + const gltf = await this.assets.loadGLTF(url); + const root = gltf.scene.clone(true); + // Normalize size ~0.45 m + root.updateMatrixWorld(true); + let box = null; + root.traverse((o) => { + if (o.isMesh) { + o.castShadow = true; + o.frustumCulled = false; + } + }); + // crude scale + root.scale.setScalar(0.35); + this._modelCache.set(url, root); + return root.clone(true); + } catch { + return null; + } + } + + clear() { + while (this.items.length) this._remove(this.items[0]); + } + + dispose() { + this.clear(); + this.scene.remove(this.group); + } +} diff --git a/src/world/devIslandCatalog.js b/src/world/devIslandCatalog.js new file mode 100644 index 0000000..d5f459e --- /dev/null +++ b/src/world/devIslandCatalog.js @@ -0,0 +1,338 @@ +/** + * Dev Island harvest catalog SSOT (Casting Warlords lab). + * + * Production-baked rocks live under public/models/dev-island/. + * Icons: public/icons/dev-island/{minerals,inventory}/. + * + * Does not invent a second harvest engine — node classes + tool gates match + * Mine-Loader / Warlords profession patterns (pickaxe→rock/ore, axe→wood, hand→herb). + * + * @see docs/ISLAND_STAGE_SSOT.md · docs/DROP_RATES_SSOT.md (harvest_node) + */ + +/** F / interact harvest radius (metres) — product: nearest node within 5 m */ +export const HARVEST_RANGE_M = 5; + +/** Seconds between harvest swings on the same node */ +export const HARVEST_SWING_CD = 0.55; + +/** Default hits to deplete a node (lab pacing) */ +export const DEFAULT_NODE_HP = 3; + +const ROCK_DIR = './models/dev-island'; +const ICON_MINERALS = './icons/dev-island/minerals'; +const ICON_INV = './icons/dev-island/inventory'; + +/** Shore / pad décor (not harvestable) — rockforms + walls for map read */ +export const DECOR_MESH_POOL = Object.freeze([ + `${ROCK_DIR}/rock__rockform_arch_medium.glb`, + `${ROCK_DIR}/rock__rockform_column_medium.glb`, + `${ROCK_DIR}/rock__rockform_wall_short1_medium.glb`, + `${ROCK_DIR}/rock__rockform_wall_long_medium.glb`, + `${ROCK_DIR}/rock__rockwall_straight_medium.glb`, + `${ROCK_DIR}/rock__rockwall_corner_medium.glb`, + `${ROCK_DIR}/rock__debris1_medium.glb`, + `${ROCK_DIR}/rock__rock5_natural_dark.glb` +]); + +/** + * Preferred rock isolates for harvestable nodes (SI ~human-scale debris/boulders). + * Natural medium variants first; base mesh as fallback. + */ +export const ROCK_MESH_POOL = Object.freeze([ + `${ROCK_DIR}/rock__rock1_natural_medium.glb`, + `${ROCK_DIR}/rock__rock2_natural_medium.glb`, + `${ROCK_DIR}/rock__rock3_natural_medium.glb`, + `${ROCK_DIR}/rock__rock4_natural_medium.glb`, + `${ROCK_DIR}/rock__rock5_natural_medium.glb`, + `${ROCK_DIR}/rock__rock6_natural_medium.glb`, + `${ROCK_DIR}/rock__rock7_natural_medium.glb`, + `${ROCK_DIR}/rock__rock8_natural_medium.glb`, + `${ROCK_DIR}/rock__rock1.glb`, + `${ROCK_DIR}/rock__rock2.glb`, + `${ROCK_DIR}/rock__rock3.glb` +]); + +export const ORE_MESH_POOL = Object.freeze([ + `${ROCK_DIR}/rock__rock9_natural_medium.glb`, + `${ROCK_DIR}/rock__rock10_natural_medium.glb`, + `${ROCK_DIR}/rock__rock9_natural_dark.glb`, + `${ROCK_DIR}/rock__rock10_cave_medium.glb`, + `${ROCK_DIR}/rock__rock5_cave_dark.glb` +]); + +export const PEBBLE_MESH_POOL = Object.freeze([ + `${ROCK_DIR}/rock__pebbles1.glb`, + `${ROCK_DIR}/rock__pebbles2.glb`, + `${ROCK_DIR}/rock__pebbles3.glb`, + `${ROCK_DIR}/rock__pebbles4.glb`, + `${ROCK_DIR}/rock__debris1.glb`, + `${ROCK_DIR}/rock__debris2.glb` +]); + +/** + * Tool affinity tags. Equipped weapon matches if weaponType/id/name hits any tag. + * TOOL / t0-tool / pick / axe family cover mining + chopping on the lab. + */ +export const TOOL_TAGS = Object.freeze({ + pickaxe: ['tool', 'pick', 'pickaxe', 'mine', 't0-tool', 'hammer'], + axe: ['tool', 'axe', 'hatchet', 'chop', 't0-axe', 't0-tool'], + sickle: ['tool', 'sickle', 'scythe', 'knife', 't0-tool'], + hand: [] // always allowed +}); + +/** + * @typedef {object} HarvestLootSpec + * @property {string} id + * @property {string} name + * @property {number} [tier] + * @property {string} [iconUrl] + * @property {string} [category] + * @property {[number, number]} [qty] + */ + +/** + * @typedef {object} HarvestNodeDef + * @property {string} id + * @property {string} label + * @property {'rock'|'ore'|'wood'|'herb'|'fiber'} classId + * @property {keyof typeof TOOL_TAGS | 'hand'} tool + * @property {string[]} meshPool + * @property {number} hp + * @property {number} [scale] + * @property {number} [respawnS] + * @property {HarvestLootSpec[]} loot + * @property {string} [tint] hex for highlight ring + */ + +/** @type {Record} */ +export const HARVEST_NODE_DEFS = Object.freeze({ + rock_boulder: { + id: 'rock_boulder', + label: 'Stone boulder', + classId: 'rock', + tool: 'pickaxe', + meshPool: [...ROCK_MESH_POOL], + hp: DEFAULT_NODE_HP, + scale: 1.0, + respawnS: 28, + tint: '#9aa3ad', + loot: [ + { + id: 'mat-stone', + name: 'Stone', + tier: 0, + category: 'materials', + iconUrl: `${ICON_MINERALS}/FD_Minerals_Stones.png`, + qty: [2, 5] + } + ] + }, + rock_ore: { + id: 'rock_ore', + label: 'Ore outcrop', + classId: 'ore', + tool: 'pickaxe', + meshPool: [...ORE_MESH_POOL], + hp: DEFAULT_NODE_HP + 1, + scale: 0.95, + respawnS: 36, + tint: '#c9a227', + loot: [ + { + id: 'mat-ore-chunk', + name: 'Ore chunk', + tier: 1, + category: 'materials', + iconUrl: `${ICON_MINERALS}/FD_Minerals_Stones.png`, + qty: [1, 3] + }, + { + id: 'mat-stone', + name: 'Stone', + tier: 0, + category: 'materials', + iconUrl: `${ICON_MINERALS}/FD_Minerals_Stones.png`, + qty: [1, 2] + } + ] + }, + rock_pebbles: { + id: 'rock_pebbles', + label: 'Pebble cluster', + classId: 'rock', + tool: 'hand', + meshPool: [...PEBBLE_MESH_POOL], + hp: 1, + scale: 0.85, + respawnS: 18, + tint: '#8b7e6a', + loot: [ + { + id: 'mat-pebble', + name: 'Pebbles', + tier: 0, + category: 'materials', + iconUrl: `${ICON_MINERALS}/FD_Minerals_Stones.png`, + qty: [1, 4] + } + ] + }, + herb_patch: { + id: 'herb_patch', + label: 'Herb patch', + classId: 'herb', + tool: 'hand', + meshPool: [], // procedural plant stub + hp: 1, + scale: 1, + respawnS: 22, + tint: '#4caf6a', + loot: [ + { + id: 'mat-herb', + name: 'Wild herb', + tier: 0, + category: 'materials', + iconUrl: `${ICON_INV}/Hearts_Yellow_1.png`, + qty: [1, 2] + } + ] + } +}); + +/** + * Default pad layout — rings of harvestables for the lab island. + * Positions are relative (angle, radiusFrac of islandRadius). + * Spawn cluster is close so F harvest is reachable without a long run. + */ +export const DEFAULT_HARVEST_LAYOUT = Object.freeze([ + // Spawn cluster (~4–8 m) — tutorial harvest + { defId: 'rock_pebbles', angle: 0.4, r: 0.1 }, + { defId: 'herb_patch', angle: 1.2, r: 0.09 }, + { defId: 'rock_pebbles', angle: 2.1, r: 0.11 }, + { defId: 'herb_patch', angle: 3.4, r: 0.1 }, + { defId: 'rock_boulder', angle: 5.0, r: 0.12 }, + { defId: 'rock_pebbles', angle: 5.8, r: 0.095 }, + // Inner ring — hand + herbs + { defId: 'rock_pebbles', angle: 0.2, r: 0.22 }, + { defId: 'herb_patch', angle: 1.1, r: 0.2 }, + { defId: 'rock_pebbles', angle: 2.0, r: 0.24 }, + { defId: 'herb_patch', angle: 3.0, r: 0.21 }, + { defId: 'rock_pebbles', angle: 4.2, r: 0.23 }, + { defId: 'herb_patch', angle: 5.2, r: 0.2 }, + // Mid ring — stone boulders + { defId: 'rock_boulder', angle: 0.5, r: 0.38 }, + { defId: 'rock_boulder', angle: 1.6, r: 0.4 }, + { defId: 'rock_boulder', angle: 2.7, r: 0.37 }, + { defId: 'rock_boulder', angle: 3.8, r: 0.41 }, + { defId: 'rock_boulder', angle: 4.9, r: 0.39 }, + { defId: 'rock_boulder', angle: 5.8, r: 0.4 }, + // Outer ring — ore + { defId: 'rock_ore', angle: 0.9, r: 0.58 }, + { defId: 'rock_ore', angle: 2.3, r: 0.6 }, + { defId: 'rock_ore', angle: 3.5, r: 0.56 }, + { defId: 'rock_ore', angle: 4.7, r: 0.59 }, + { defId: 'rock_ore', angle: 5.9, r: 0.57 }, + // Shore practice boulders + { defId: 'rock_boulder', angle: 1.3, r: 0.72 }, + { defId: 'rock_boulder', angle: 4.0, r: 0.7 }, + { defId: 'rock_ore', angle: 2.8, r: 0.74 } +]); + +/** + * Non-interactive décor (rockforms / walls) for map silhouette. + * r near shore so pad center stays clear for combat + harvest. + */ +export const DEFAULT_DECOR_LAYOUT = Object.freeze([ + { mesh: 0, angle: 0.3, r: 0.82, yaw: 0.4, scale: 1.4 }, + { mesh: 1, angle: 1.0, r: 0.85, yaw: 1.2, scale: 1.2 }, + { mesh: 2, angle: 1.8, r: 0.8, yaw: 0.1, scale: 1.5 }, + { mesh: 3, angle: 2.6, r: 0.86, yaw: 2.0, scale: 1.3 }, + { mesh: 4, angle: 3.4, r: 0.83, yaw: 0.7, scale: 1.6 }, + { mesh: 5, angle: 4.2, r: 0.84, yaw: 1.5, scale: 1.25 }, + { mesh: 6, angle: 5.0, r: 0.81, yaw: 0.2, scale: 1.1 }, + { mesh: 7, angle: 5.7, r: 0.87, yaw: 2.4, scale: 1.35 }, + { mesh: 0, angle: 0.8, r: 0.78, yaw: 1.0, scale: 1.15 }, + { mesh: 2, angle: 3.9, r: 0.79, yaw: 0.5, scale: 1.45 } +]); + +/** + * Training dummy pads for combat focus (not harvest). + * Closer ring so Tab soft-lock works from spawn. + * @type {{ angle: number, r: number, label: string }[]} + */ +export const DEFAULT_DUMMY_LAYOUT = Object.freeze([ + { angle: 0.0, r: 0.18, label: 'Dummy · North' }, + { angle: Math.PI * 0.5, r: 0.18, label: 'Dummy · East' }, + { angle: Math.PI, r: 0.18, label: 'Dummy · South' }, + { angle: Math.PI * 1.5, r: 0.18, label: 'Dummy · West' }, + { angle: Math.PI * 0.25, r: 0.28, label: 'Dummy · NE' }, + { angle: Math.PI * 1.25, r: 0.28, label: 'Dummy · SW' } +]); + +/** + * Whether equipped weapon satisfies the node tool requirement. + * @param {import('../api/t0WeaponCatalog.js').EquippableWeapon|null|undefined} weapon + * @param {HarvestNodeDef} def + */ +export function toolMatches(weapon, def) { + if (!def || def.tool === 'hand') return true; + if (!weapon) return false; + const tags = TOOL_TAGS[def.tool] || []; + if (!tags.length) return true; + const hay = `${weapon.id || ''} ${weapon.name || ''} ${weapon.weaponType || ''}`.toLowerCase(); + // Any TOOL weapon type covers pick/axe lab tools (t0-tool uses sword_shield attack) + if (/tool/i.test(weapon.weaponType || '') || /tool/i.test(weapon.id || '')) return true; + return tags.some((t) => hay.includes(t)); +} + +/** + * Human-readable tool need for toasts. + * @param {HarvestNodeDef} def + */ +export function toolLabel(def) { + if (!def || def.tool === 'hand') return 'hands'; + if (def.tool === 'pickaxe') return 'pickaxe / tool'; + if (def.tool === 'axe') return 'axe / tool'; + if (def.tool === 'sickle') return 'sickle / tool'; + return def.tool; +} + +/** + * Roll loot bag entries from a node def. + * @param {HarvestNodeDef} def + * @param {() => number} [rng] + */ +export function rollNodeLoot(def, rng = Math.random) { + const out = []; + for (const spec of def.loot || []) { + const [a, b] = Array.isArray(spec.qty) ? spec.qty : [1, 1]; + const qty = Math.max(1, Math.floor(a + rng() * (b - a + 1))); + out.push({ + id: spec.id, + name: spec.name, + tier: spec.tier ?? 0, + qty, + category: spec.category || 'materials', + iconUrl: spec.iconUrl || null, + modelUrl: null, + weaponType: null, + source: 'harvest_node', + nodeClass: def.classId + }); + } + return out; +} + +/** + * Pick a mesh URL from the def pool. + * @param {HarvestNodeDef} def + * @param {number} [seed] + */ +export function pickMeshUrl(def, seed = 0) { + const pool = def?.meshPool || []; + if (!pool.length) return null; + const i = Math.abs(Math.floor(seed)) % pool.length; + return pool[i]; +} diff --git a/src/world/nodePalette.js b/src/world/nodePalette.js new file mode 100644 index 0000000..e6ce74f --- /dev/null +++ b/src/world/nodePalette.js @@ -0,0 +1,504 @@ +/** + * DevNode palette SSOT — creative resources for /devnode authoring. + * + * Extends (does not fork): + * - devIslandCatalog.js harvest + décor rocks + * - deployableContract.js harvestable / buildable / enemy kinds + * - GrowingForest procedural trees + * - IslandHeightfield biomes knobs + * + * Each entry: kind + family + asset pointers + harvest/pve hooks when live. + * Draft layouts export JSON consumable by DevIslandHarvest / world loaders. + * + * @see docs/DEVNODE_SSOT.md + */ + +import { + DECOR_MESH_POOL, + HARVEST_NODE_DEFS, + ORE_MESH_POOL, + PEBBLE_MESH_POOL, + ROCK_MESH_POOL +} from './devIslandCatalog.js'; + +/** Biome presets — drive terrain amp/seed + default scatter families */ +export const BIOME_PRESETS = Object.freeze([ + { + id: 'temperate_meadow', + label: 'Temperate meadow', + terrain: { seed: 17, amp: 0.7, flatCore: 10 }, + families: ['tree', 'flower', 'rock', 'herb', 'animal_passive'] + }, + { + id: 'coastal_cliff', + label: 'Coastal cliff', + terrain: { seed: 41, amp: 1.1, flatCore: 6 }, + families: ['cliff', 'rock', 'ore', 'tree'] + }, + { + id: 'deep_forest', + label: 'Deep forest', + terrain: { seed: 99, amp: 0.9, flatCore: 8 }, + families: ['tree', 'hemp', 'herb', 'rock', 'pve_mob'] + }, + { + id: 'mine_sprawl', + label: 'Mine sprawl', + terrain: { seed: 7, amp: 0.55, flatCore: 12 }, + families: ['ore', 'rock', 'cliff', 'pve_mob'] + }, + { + id: 'highland', + label: 'Highland', + terrain: { seed: 63, amp: 1.2, flatCore: 5 }, + families: ['cliff', 'ore', 'rock', 'animal_passive'] + }, + { + id: 'home_island', + label: 'Home island (NPC)', + terrain: { seed: 21, amp: 0.55, flatCore: 14 }, + families: ['home_scenery', 'farm_module', 'tree', 'herb', 'flower', 'rock'], + note: 'River village · lake · farm modular — flatter pad for building' + }, + { + id: 'river_valley', + label: 'River valley', + terrain: { seed: 88, amp: 0.75, flatCore: 9 }, + families: ['home_scenery', 'tree', 'rock', 'herb', 'animal_passive'], + note: 'Village + water feature focus' + } +]); + +/** + * Family → deployable kind (Admin F2/F3 alignment) + * @type {Record} + */ +export const NODE_FAMILIES = Object.freeze({ + terrain: { + label: 'Terrain', + adminTab: 'world', + deployableKind: 'buildable', + note: 'Heightfield knobs only — not a mesh node' + }, + cliff: { + label: 'Cliffs / walls', + adminTab: 'assets', + deployableKind: 'buildable', + note: 'Rockform walls · cliffs · décor' + }, + tree: { + label: 'Trees', + adminTab: 'assets', + deployableKind: 'harvestable', + note: 'GrowingForest + wood tool' + }, + rock: { + label: 'Rocks', + adminTab: 'assets', + deployableKind: 'harvestable', + note: 'dev-island rock_* defs' + }, + ore: { + label: 'Ore', + adminTab: 'assets', + deployableKind: 'harvestable', + note: 'pickaxe · ore outcrops' + }, + flower: { + label: 'Flowers', + adminTab: 'assets', + deployableKind: 'harvestable', + note: 'Herb-class · hand gather' + }, + hemp: { + label: 'Hemp / fiber', + adminTab: 'assets', + deployableKind: 'harvestable', + note: 'Fiber · sickle/hand' + }, + herb: { + label: 'Herbs', + adminTab: 'assets', + deployableKind: 'harvestable', + note: 'Existing herb_patch' + }, + animal_passive: { + label: 'Animals (passive)', + adminTab: 'creatures', + deployableKind: 'npc', + note: 'F3 creature drafts · stub mesh until CDN fauna' + }, + pve_mob: { + label: 'PvE enemies', + adminTab: 'creatures', + deployableKind: 'enemy', + note: 'Training dummy + grudge6 enemy drafts' + }, + /** Home-island scenery kits (farm · lake · river village) */ + home_scenery: { + label: 'Home island scenery', + adminTab: 'world', + deployableKind: 'buildable', + note: 'island-scenery GLBs · HomeIslandScenery runtime' + }, + farm_module: { + label: 'Farm modules', + adminTab: 'assets', + deployableKind: 'buildable', + note: 'Modular dirt/grass/trees from farm_modular_pack' + }, + fishing_dock: { + label: 'Fishing docks', + adminTab: 'assets', + deployableKind: 'buildable', + note: 'T1 berth anywhere · T2/T3 upgrade inside own claim flag · boat housing' + } +}); + +/** + * @typedef {object} NodePaletteEntry + * @property {string} id + * @property {string} label + * @property {keyof typeof NODE_FAMILIES} family + * @property {'mesh'|'procedural'|'creature'|'terrain_knob'} source + * @property {string[]} [meshPool] + * @property {string} [harvestDefId] link to HARVEST_NODE_DEFS + * @property {string} [tint] + * @property {number} [defaultScale] + * @property {string} [tool] + * @property {string} [note] + * @property {boolean} [ready] false = palette placeholder (export still ok) + */ + +/** @type {NodePaletteEntry[]} */ +export const NODE_PALETTE = Object.freeze([ + // —— Rocks / ore (live assets) —— + { + id: 'node.rock_boulder', + label: 'Stone boulder', + family: 'rock', + source: 'mesh', + harvestDefId: 'rock_boulder', + meshPool: [...ROCK_MESH_POOL], + defaultScale: 1, + tool: 'pickaxe', + tint: '#9aa3ad', + ready: true + }, + { + id: 'node.rock_ore', + label: 'Ore outcrop', + family: 'ore', + source: 'mesh', + harvestDefId: 'rock_ore', + meshPool: [...ORE_MESH_POOL], + defaultScale: 0.95, + tool: 'pickaxe', + tint: '#c9a227', + ready: true + }, + { + id: 'node.rock_pebbles', + label: 'Pebble cluster', + family: 'rock', + source: 'mesh', + harvestDefId: 'rock_pebbles', + meshPool: [...PEBBLE_MESH_POOL], + defaultScale: 0.85, + tool: 'hand', + tint: '#8b7e6a', + ready: true + }, + // —— Cliffs / décor walls —— + { + id: 'node.cliff_wall', + label: 'Rock wall (straight)', + family: 'cliff', + source: 'mesh', + meshPool: DECOR_MESH_POOL.filter((u) => /wall_straight|rockwall_straight/i.test(u)), + defaultScale: 1.4, + tint: '#6a7078', + ready: true, + note: 'Decor — not harvestable' + }, + { + id: 'node.cliff_arch', + label: 'Rockform arch', + family: 'cliff', + source: 'mesh', + meshPool: DECOR_MESH_POOL.filter((u) => /arch/i.test(u)), + defaultScale: 1.5, + tint: '#707880', + ready: true + }, + { + id: 'node.cliff_column', + label: 'Rock column', + family: 'cliff', + source: 'mesh', + meshPool: DECOR_MESH_POOL.filter((u) => /column/i.test(u)), + defaultScale: 1.3, + tint: '#686e76', + ready: true + }, + // —— Trees (procedural GrowingForest language) —— + { + id: 'node.tree_grow', + label: 'Growing tree', + family: 'tree', + source: 'procedural', + defaultScale: 1, + tool: 'axe', + tint: '#3d8b4a', + ready: true, + note: 'forestoutline / GrowingForest instance' + }, + // —— Flora —— + { + id: 'node.herb_patch', + label: 'Herb patch', + family: 'herb', + source: 'mesh', + harvestDefId: 'herb_patch', + meshPool: [], + defaultScale: 1, + tool: 'hand', + tint: '#4caf6a', + ready: true + }, + { + id: 'node.flower_patch', + label: 'Wildflowers', + family: 'flower', + source: 'procedural', + defaultScale: 0.9, + tool: 'hand', + tint: '#e8a0c0', + ready: true, + note: 'Procedural stub until flora GLB pack on CDN' + }, + // —— Home-island scenery (lake · village · farm pack) —— + { + id: 'node.scenery_lake', + label: 'Low-poly lake', + family: 'home_scenery', + source: 'mesh', + meshPool: ['./models/island-scenery/low_poly_lake.glb'], + defaultScale: 1, + tint: '#3a8ab8', + ready: true, + note: 'SI-fit water feature · HomeIslandScenery' + }, + { + id: 'node.scenery_village', + label: 'River village', + family: 'home_scenery', + source: 'mesh', + meshPool: ['./models/island-scenery/river_village.glb'], + defaultScale: 1, + tint: '#8a6a4a', + ready: true, + note: 'NPC home hub · cm author → SI fit' + }, + { + id: 'node.scenery_farm', + label: 'Farm modular pack', + family: 'farm_module', + source: 'mesh', + meshPool: ['./models/island-scenery/farm_modular_pack.glb'], + defaultScale: 1, + tint: '#5a8a3a', + ready: true, + note: 'Dirt/grass/trees kit · modular home fields' + }, + { + id: 'node.dock_t1', + label: 'Dock T1 (Berth)', + family: 'fishing_dock', + source: 'mesh', + meshPool: ['./models/fish/docks/dock_t1.glb'], + defaultScale: 1, + tint: '#6a8aaa', + ready: true, + note: 'Building · place anywhere · 1 boat slot · no claim' + }, + { + id: 'node.dock_t2', + label: 'Dock T2 (Harbor)', + family: 'fishing_dock', + source: 'mesh', + meshPool: ['./models/fish/docks/dock_t2.glb'], + defaultScale: 1, + tint: '#5a7a9a', + ready: true, + note: 'Upgrade inside **own claim flag** · 2 boats' + }, + { + id: 'node.dock_t3', + label: 'Dock T3 (Port)', + family: 'fishing_dock', + source: 'mesh', + meshPool: ['./models/fish/docks/dock_t3.glb'], + defaultScale: 1, + tint: '#4a6a8a', + ready: true, + note: 'Max port · own claim flag · 4 boats' + }, + { + id: 'node.home_spawn', + label: 'Home spawn marker', + family: 'home_scenery', + source: 'procedural', + defaultScale: 1, + tint: '#7fd6ff', + ready: true, + note: 'Player/NPC home start pin for home-island layouts' + }, + { + id: 'node.hemp', + label: 'Hemp stand', + family: 'hemp', + source: 'procedural', + defaultScale: 1.1, + tool: 'sickle', + tint: '#6b8f3c', + ready: true, + note: 'Fiber gather · sickle preferred' + }, + // —— Fauna / PvE (F3 alignment — placeholders ready for drafts) —— + { + id: 'node.animal_deer', + label: 'Deer (passive)', + family: 'animal_passive', + source: 'creature', + defaultScale: 1, + tint: '#a08060', + ready: false, + note: 'Spawn via F3 creature draft · mesh TBD CDN' + }, + { + id: 'node.animal_boar', + label: 'Boar (passive→aggro)', + family: 'animal_passive', + source: 'creature', + defaultScale: 1, + tint: '#705040', + ready: false + }, + { + id: 'node.pve_dummy', + label: 'Training dummy', + family: 'pve_mob', + source: 'creature', + defaultScale: 1, + tint: '#c45c4a', + ready: true, + note: 'DevIslandHarvest spawnTrainingDummies' + }, + { + id: 'node.pve_grunt', + label: 'PvE grunt (race kit)', + family: 'pve_mob', + source: 'creature', + defaultScale: 1, + tint: '#8b3a3a', + ready: false, + note: 'grudge6 enemy · F3 create draft' + } +]); + +export function paletteByFamily(family) { + return NODE_PALETTE.filter((e) => e.family === family); +} + +export function paletteEntry(id) { + return NODE_PALETTE.find((e) => e.id === id) || null; +} + +/** + * Resolve harvest def if palette entry links one. + * @param {string} paletteId + */ +export function harvestDefForPalette(paletteId) { + const e = paletteEntry(paletteId); + if (!e?.harvestDefId) return null; + return HARVEST_NODE_DEFS[e.harvestDefId] || null; +} + +/** + * Empty layout document for export / localStorage. + * @param {string} [biomeId] + */ +export function createEmptyNodeLayout(biomeId = 'temperate_meadow') { + const biome = BIOME_PRESETS.find((b) => b.id === biomeId) || BIOME_PRESETS[0]; + return { + version: 1, + source: 'casting-devnode', + biomeId: biome.id, + terrain: { ...biome.terrain }, + nodes: [], + createdAt: new Date().toISOString() + }; +} + +/** + * @typedef {object} PlacedNode + * @property {string} id instance id + * @property {string} paletteId + * @property {number} x + * @property {number} z + * @property {number} [y] + * @property {number} [yaw] + * @property {number} [scale] + */ + +/** + * Validate layout for import. + * @param {unknown} raw + */ +export function validateNodeLayout(raw) { + if (!raw || typeof raw !== 'object') return { ok: false, error: 'not an object' }; + const o = /** @type {Record} */ (raw); + if (o.version !== 1 && o.version !== undefined) { + return { ok: false, error: `unsupported version ${o.version}` }; + } + if (!Array.isArray(o.nodes)) return { ok: false, error: 'nodes[] required' }; + return { ok: true, layout: o }; +} + +/** Creative resource map — where to pull assets (no parallel CDN invent) */ +export const CREATIVE_RESOURCE_MAP = Object.freeze({ + rocks_cliffs: { + local: 'public/models/dev-island/rock__*', + note: 'Baked rock pack already in lab' + }, + harvest_defs: { + code: 'src/world/devIslandCatalog.js', + note: 'HARVEST_NODE_DEFS + layouts' + }, + trees: { + code: 'src/world/GrowingForest.js', + ref: 'Desktop forestoutline.html + snakey trees', + note: 'Procedural grow + instanced' + }, + terrain: { + code: 'src/world/IslandHeightfield.js', + ref: 'snakey-locomotion · three-stylized' + }, + weapons_loot: { + cdn: 'info.grudge-studio.com/api/v1/master-weapon-prefabs.json', + note: 'World drop icons/models' + }, + creatures: { + admin: 'F3 Creatures', + cdn: 'assets.grudge-studio.com/asset-packs/toon-rts-characters/glb/characters/', + note: 'Enemy/ally drafts — no second AI brain' + }, + flora_future: { + note: 'Nature stylized packs via grudge-warlords-assets / D1 — wire meshPool when uploaded' + }, + forestoutline: { + path: 'C:/Users/nugye/Desktop/forestoutline.html', + note: 'Instanced tree CONFIG + leaf LOD reference only' + } +}); diff --git a/src/world/terrainGround.js b/src/world/terrainGround.js new file mode 100644 index 0000000..6d9176a --- /dev/null +++ b/src/world/terrainGround.js @@ -0,0 +1,74 @@ +/** + * Single ground projection for aim / path / markers. + * + * Order (easy, no second heightmap): + * 1. Raycast island terrain mesh if present + * 2. Else ray ∩ y=0 plane, then lift Y via sample(x,z) + * + * Used by MouseAim + PathDrawer only — do not duplicate height math elsewhere. + * + * @see IslandHeightfield.sample + */ + +import { Plane, Vector3 } from 'three'; + +const PLANE = new Plane(new Vector3(0, 1, 0), 0); +const _scratch = new Vector3(); + +/** + * @typedef {{ mesh?: import('three').Object3D|null, sample?: ((x:number,z:number)=>number)|null }} TerrainGround + */ + +/** + * @param {import('three').Raycaster} raycaster ray already setFromCamera + * @param {import('three').Vector3} out + * @param {TerrainGround|null|undefined} terrain + * @returns {boolean} + */ +export function projectToTerrain(raycaster, out, terrain) { + if (terrain?.mesh) { + const hits = raycaster.intersectObject(terrain.mesh, false); + if (hits[0]) { + out.copy(hits[0].point); + return true; + } + } + + if (raycaster.ray.intersectPlane(PLANE, out) == null) return false; + + if (typeof terrain?.sample === 'function') { + const y = terrain.sample(out.x, out.z); + if (Number.isFinite(y)) out.y = y; + } + return true; +} + +/** + * Lift an existing XZ point onto terrain (no ray). + * @param {number} x + * @param {number} z + * @param {TerrainGround|null|undefined} terrain + * @param {number} [fallbackY=0] + */ +export function surfaceY(x, z, terrain, fallbackY = 0) { + if (typeof terrain?.sample === 'function') { + const y = terrain.sample(x, z); + if (Number.isFinite(y)) return y; + } + return fallbackY; +} + +/** + * Build the one terrain handle App passes around (avoid N lambdas). + * @param {{ mesh?: import('three').Object3D, sample?: (x:number,z:number)=>number }|null} island + * @returns {TerrainGround|null} + */ +export function terrainHandle(island) { + if (!island?.sample && !island?.mesh) return null; + return { + mesh: island.mesh || null, + sample: island.sample ? (x, z) => island.sample(x, z) : null + }; +} + +export { PLANE as GROUND_PLANE, _scratch as terrainScratch }; diff --git a/src/world/terrainLayers.js b/src/world/terrainLayers.js new file mode 100644 index 0000000..b49f621 --- /dev/null +++ b/src/world/terrainLayers.js @@ -0,0 +1,151 @@ +/** + * Three-layer terrain SSOT (Casting lab + fleet grass islands). + * + * | Layer | Name | Role | Source patterns | + * |-------|------|------|-----------------| + * | **L0** | Height field | One `heightAt(x,z)` for mesh · feet · Rapier · aim · forest | snakey-locomotion, three-stylized Terrain | + * | **L1** | Surface ground | Visual mesh / dirt-meadow colors on L0 | three-stylized grounds, IslandHeightfield | + * | **L2** | Vegetation | Instanced grass + growing forest | three-stylized Grass, forestoutline.html, snakey trees | + * | **L3** | Detail scatter | Rocks / harvest / wildflowers | DevIslandHarvest, three-stylized wildflowers | + * + * **Hard rules** + * 1. One height function only — never a second heightmap for feet vs grass. + * 2. Forest / grass sample L0 via `heightSample` callback. + * 3. Water is a sibling layer (`StageWater`), not L1. + * 4. SI: 1 unit = 1 m; human ~1.8 m yardstick for hill amp. + * + * Learn / study (do not vendor whole repos as parallel engines): + * - https://github.com/muratkamci/snakey-locomotion + * - https://github.com/Steve245270533/three-stylized + * - https://simonstorlschulke.github.io/threejs-examples/?scene=0 (infinite stream later) + * - Desktop forestoutline.html (instanced procedural trees + leaf texture) + * + * @see docs/THREE_LAYER_TERRAIN_SSOT.md + * @see IslandHeightfield.js · GrowingForest.js · StylizedGrassLayer.js + */ + +import { IslandHeightfield, heightAt, terrainOpts } from './IslandHeightfield.js'; +import { GrowingForest } from './GrowingForest.js'; +import { StylizedGrassLayer } from './StylizedGrassLayer.js'; +import { settings } from '../config/settings.js'; +import { WORLD } from '../config/worldScale.js'; + +/** Layer ids — same language as grudge-player-and-grass */ +export const TERRAIN_LAYER = Object.freeze({ + L0_HEIGHT: 'L0_height', + L1_SURFACE: 'L1_surface', + L2_VEGETATION: 'L2_vegetation', + L3_DETAIL: 'L3_detail', + WATER: 'water' +}); + +/** + * Build layered terrain for a scene. + * + * @param {{ + * scene: import('three').Scene, + * heightfield?: IslandHeightfield|null, + * forest?: boolean, + * grass?: boolean, + * onToast?: (s: string) => void + * }} opts + */ +export function mountTerrainLayers(opts) { + const t = settings.terrain || {}; + const heightfield = + opts.heightfield ?? + (t.enabled !== false ? new IslandHeightfield() : null); + + const heightSample = (x, z) => + heightfield?.sample?.(x, z) ?? heightAt(x, z, terrainOpts()); + + /** @type {{ heightfield: IslandHeightfield|null, forest: GrowingForest|null, grass: StylizedGrassLayer|null, heightSample: Function }} */ + const layers = { + heightfield, + forest: null, + grass: null, + heightSample, + layerIds: [TERRAIN_LAYER.L0_HEIGHT, TERRAIN_LAYER.L1_SURFACE] + }; + + if (heightfield?.mesh) { + // Idempotent: App may already have added mesh at boot + if (!heightfield.mesh.parent) opts.scene.add(heightfield.mesh); + heightfield.mesh.userData.terrainLayer = TERRAIN_LAYER.L1_SURFACE; + heightfield.mesh.userData.heightLayer = TERRAIN_LAYER.L0_HEIGHT; + } + + if (opts.forest !== false && t.forestEnabled !== false && heightfield) { + layers.forest = new GrowingForest({ + scene: opts.scene, + heightSample, + count: t.forestCount ?? 48, + islandRadius: WORLD.islandRadius * 0.9, + clearRadius: t.forestClearRadius ?? 11, + seed: (t.seed ?? 17) + 900, + onToast: opts.onToast + }); + layers.layerIds.push(TERRAIN_LAYER.L2_VEGETATION); + } + + if (opts.grass !== false && t.grassEnabled !== false && heightfield) { + layers.grass = new StylizedGrassLayer({ + scene: opts.scene, + heightSample, + islandRadius: WORLD.islandRadius * 0.88, + clearRadius: t.grassClearRadius ?? 6, + density: t.grassDensity ?? 28, + seed: (t.seed ?? 17) + 401, + bladeMaxHeight: t.grassBladeMax ?? 0.55 + }); + if (!layers.layerIds.includes(TERRAIN_LAYER.L2_VEGETATION)) { + layers.layerIds.push(TERRAIN_LAYER.L2_VEGETATION); + } + } + + layers.layerIds.push(TERRAIN_LAYER.L3_DETAIL); + + return layers; +} + +/** + * Agent-facing summary of learned sources → layers. + */ +export const TERRAIN_LEARNED = Object.freeze({ + L0: { + name: 'Height field', + patterns: [ + 'snakey heightAt multi-band FBM', + 'three-stylized terrainDegree / seed', + 'Rapier Float32 heightfield' + ], + code: 'IslandHeightfield.heightAt / sample' + }, + L1: { + name: 'Surface ground', + patterns: [ + 'three-stylized dirt/meadow groundColor', + 'vertex meadow↔dirt↔shore colors' + ], + code: 'IslandHeightfield.mesh' + }, + L2: { + name: 'Vegetation', + patterns: [ + 'three-stylized instanced grass + wind', + 'forestoutline instanced trees + leaf sway/texture', + 'snakey reactive grass / tree climb surfaces (climb later)' + ], + code: 'StylizedGrassLayer · GrowingForest' + }, + L3: { + name: 'Detail', + patterns: ['harvest rocks', 'wildflowers optional', 'decals'], + code: 'DevIslandHarvest' + }, + streaming: { + name: 'Infinite tiles (later)', + patterns: ['simonstorlschulke threejs-examples scene=0 infinite terrain'], + code: 'chunk keys shared L0–L2 — not active on island pad' + } +}); diff --git a/src/world/trainingRoomDeploy.js b/src/world/trainingRoomDeploy.js new file mode 100644 index 0000000..8a71a3f --- /dev/null +++ b/src/world/trainingRoomDeploy.js @@ -0,0 +1,259 @@ +/** + * Training Room production deploy chain. + * + * Load order for map layout (best production → lab fallback): + * 1. localStorage (author session from /devnode) + * 2. Published fetch: same-origin maps/ → info → objectstore → CDN + * 3. Built-in createTrainingRoomLayout() + * + * Node contract for Open/Forge handoff matches production world habits: + * id · meshKey · kind · position · physicsLayer · location + * + * @see docs/TRAINING_ROOM_SSOT.md · grudge-production-world · grudge-d1-r2 + */ + +import { + ASSETS_URL, + TRAINING_ROOM_LAYOUT_KEYS, + TRAINING_ROOM_R2_PREFIX, + fleetDeploySnapshot, + resolveLabAssetUrl +} from '../config/fleetEnv.js'; +import { + TRAINING_ROOM_MAP_ID, + TRAINING_ROOM_LABEL, + createTrainingRoomLayout, + loadTrainingRoomLayoutFromStorage, + stampTrainingRoomLayout, + paletteIdToHarvestDef +} from './trainingRoomMap.js'; +import { HARVEST_NODE_DEFS, pickMeshUrl } from './devIslandCatalog.js'; +import { validateNodeLayout } from './nodePalette.js'; + +/** + * Fetch first OK JSON from candidate URLs. + * @param {string[]} urls + * @param {number} [timeoutMs] + */ +async function fetchFirstJson(urls, timeoutMs = 6000) { + for (const url of urls) { + if (!url) continue; + try { + const ctrl = new AbortController(); + const t = setTimeout(() => ctrl.abort(), timeoutMs); + const res = await fetch(url, { + signal: ctrl.signal, + headers: { Accept: 'application/json' }, + cache: 'no-cache' + }); + clearTimeout(t); + if (!res.ok) continue; + const body = await res.json(); + if (body && typeof body === 'object') { + return { ok: true, url, body }; + } + } catch { + /* try next */ + } + } + return { ok: false, url: null, body: null }; +} + +/** + * Resolve production layout for play boot. + * @param {{ skipStorage?: boolean, preferPublished?: boolean }} [opts] + */ +export async function loadTrainingRoomLayoutForPlay(opts = {}) { + if (!opts.skipStorage) { + const stored = loadTrainingRoomLayoutFromStorage(); + if (stored?.nodes?.length) { + return { + layout: stampTrainingRoomLayout(stored), + source: 'localStorage', + publishedUrl: null + }; + } + } + + // Published chain — same-origin first (always on casting Vercel) + const keys = TRAINING_ROOM_LAYOUT_KEYS; + const urls = [ + keys.local, + // Prefer info catalog authority when promoted + keys.info, + keys.objectstore, + keys.cdn + ]; + if (opts.preferPublished) { + // Prod smoke: try remote catalogs before local bundle + urls.splice(0, urls.length, keys.info, keys.objectstore, keys.cdn, keys.local); + } + + const got = await fetchFirstJson(urls); + if (got.ok) { + const v = validateNodeLayout(got.body); + if (v.ok) { + return { + layout: stampTrainingRoomLayout(v.layout), + source: 'published', + publishedUrl: got.url + }; + } + } + + const builtIn = createTrainingRoomLayout(); + return { + layout: stampTrainingRoomLayout(builtIn), + source: 'builtin', + publishedUrl: null + }; +} + +/** + * Convert Training Room layout → production world mesh nodes (Open/Forge handoff). + * Does not invent UUIDs — ids stay lab-stable until ObjectStore mint. + * + * @param {object} layout + * @returns {object[]} + */ +export function layoutToWorldMeshNodes(layout) { + const nodes = layout?.nodes || []; + const out = []; + for (const n of nodes) { + const defId = paletteIdToHarvestDef(n.paletteId); + const def = defId ? HARVEST_NODE_DEFS[defId] : null; + let meshKey = null; + let kind = 'prop'; + if (def) { + kind = 'harvestable'; + const url = pickMeshUrl(def, (n.id || '').length) || def.meshPool?.[0]; + meshKey = urlToR2Key(url); + } else if (String(n.paletteId || '').includes('pve') || String(n.paletteId || '').includes('dummy')) { + kind = 'enemy'; + meshKey = null; // procedural dummy — client spawns kit later + } else if (String(n.paletteId || '').includes('cliff') || String(n.paletteId || '').includes('wall')) { + kind = 'buildable'; + meshKey = `${TRAINING_ROOM_R2_PREFIX}/dev-island/rock__rockform_wall_short1_medium.glb`; + } else if (String(n.paletteId || '').includes('tree')) { + kind = 'harvestable'; + meshKey = null; // GrowingForest procedural + } + + out.push({ + id: n.id || `node_${out.length}`, + mapId: TRAINING_ROOM_MAP_ID, + paletteId: n.paletteId, + meshKey, + kind, + position: { + x: Number(n.x) || 0, + y: Number(n.y) || 0, + z: Number(n.z) || 0 + }, + yaw: Number(n.yaw) || 0, + scale: Number(n.scale) || 1, + physicsLayer: kind === 'enemy' ? 'NPC' : kind === 'harvestable' ? 'Item' : 'Default', + collider: kind === 'harvestable' || kind === 'buildable' ? 'static' : 'none', + location: { + mapId: TRAINING_ROOM_MAP_ID, + label: TRAINING_ROOM_LABEL, + sector: 'casting_lab' + }, + harvestDefId: defId || undefined + }); + } + return out; +} + +/** + * Local path → R2 key for D1 index / promote. + * @param {string|null|undefined} url + */ +export function urlToR2Key(url) { + if (!url) return null; + const s = String(url); + if (/^https?:\/\//i.test(s)) { + try { + const u = new URL(s); + return u.pathname.replace(/^\//, ''); + } catch { + return s; + } + } + const bare = s.replace(/^\.\//, '').replace(/^\//, ''); + if (bare.startsWith('models/dev-island/')) { + return `${TRAINING_ROOM_R2_PREFIX}/${bare.slice('models/'.length)}`; + } + if (bare.startsWith('icons/dev-island/')) { + return `${TRAINING_ROOM_R2_PREFIX}/${bare}`; + } + return bare; +} + +/** + * D1-ready asset index rows for Training Room binaries (upload checklist). + * Binary still lives on R2; D1 only indexes. + * + * @param {string[]} meshUrls relative lab paths + */ +export function d1IndexRowsForMeshes(meshUrls) { + const rows = []; + const seen = new Set(); + for (const u of meshUrls || []) { + const key = urlToR2Key(u); + if (!key || seen.has(key)) continue; + seen.add(key); + rows.push({ + key, + cdnUrl: `${ASSETS_URL}/${key}`, + kind: 'mesh', + tags: ['casting', 'training_room', 'devisland', 'lab'], + mapId: TRAINING_ROOM_MAP_ID, + source: 'casting-lab-promote' + }); + } + return rows; +} + +/** + * Export package for promote (ObjectStore / R2 / D1 operators). + * @param {object} layout + */ +export function buildTrainingRoomPromotePackage(layout) { + const stamped = stampTrainingRoomLayout(layout || createTrainingRoomLayout()); + const worldNodes = layoutToWorldMeshNodes(stamped); + const meshUrls = []; + for (const n of stamped.nodes || []) { + const defId = paletteIdToHarvestDef(n.paletteId); + const def = defId ? HARVEST_NODE_DEFS[defId] : null; + if (def?.meshPool?.length) meshUrls.push(...def.meshPool); + } + return { + contract: 'grudge.trainingRoomPromote/v1', + mapId: TRAINING_ROOM_MAP_ID, + mapLabel: TRAINING_ROOM_LABEL, + layout: stamped, + worldMeshNodes: worldNodes, + d1Index: d1IndexRowsForMeshes(meshUrls), + r2Prefix: TRAINING_ROOM_R2_PREFIX, + publishTargets: { + layoutJson: [ + `objectstore:/api/v1/maps/training_room/layout.json`, + `info:/api/v1/maps/training_room/layout.json`, + `r2:${TRAINING_ROOM_R2_PREFIX}/layout.default.json` + ], + binaries: `r2:${TRAINING_ROOM_R2_PREFIX}/dev-island/*` + }, + fleet: fleetDeploySnapshot(), + createdAt: new Date().toISOString() + }; +} + +/** + * Resolve harvest mesh URL for runtime load (local first, CDN promote optional). + * @param {string} url + * @param {{ preferCdn?: boolean }} [opts] + */ +export function resolveTrainingRoomMeshUrl(url, opts = {}) { + return resolveLabAssetUrl(url, opts); +} diff --git a/src/world/trainingRoomMap.js b/src/world/trainingRoomMap.js new file mode 100644 index 0000000..1d20bc1 --- /dev/null +++ b/src/world/trainingRoomMap.js @@ -0,0 +1,272 @@ +/** + * Training Room = DevIsland = the one casting.* island map. + * + * Play (`index.html`), DevNode (`devnode.html`), and harvest runtime all + * share this map profile. No second island / second height stack. + * + * Built with: + * L0 IslandHeightfield (FBM height · Rapier heightfield) + * L1 same mesh (meadow/dirt/shore vertex colors) + * L2 StylizedGrassLayer + GrowingForest + * L3 DevIslandHarvest rocks/ore/herbs + decor + training dummies + * Water StageWater + OpenSeaShells horizon + * + * @see docs/TRAINING_ROOM_SSOT.md + * @see docs/THREE_LAYER_TERRAIN_SSOT.md + */ + +import { WORLD } from '../config/worldScale.js'; +import { settings } from '../config/settings.js'; +import { + DEFAULT_DECOR_LAYOUT, + DEFAULT_DUMMY_LAYOUT, + DEFAULT_HARVEST_LAYOUT, + DECOR_MESH_POOL, + HARVEST_NODE_DEFS +} from './devIslandCatalog.js'; +import { createEmptyNodeLayout, harvestDefForPalette, paletteEntry } from './nodePalette.js'; + +/** Canonical map id (play + editor + export) */ +export const TRAINING_ROOM_MAP_ID = 'training_room'; +/** Product aliases — same thing */ +export const TRAINING_ROOM_ALIASES = Object.freeze(['devisland', 'dev_island', 'devnode']); + +export const TRAINING_ROOM_LABEL = 'Training Room · DevIsland'; + +/** localStorage key shared by DevNode export and play import */ +export const TRAINING_ROOM_LAYOUT_KEY = 'grudge.casting.training_room.layout.v1'; +/** Legacy DevNode key — still read for migration */ +export const DEVNODE_LAYOUT_LEGACY_KEY = 'grudge.casting.devnode.layout.v1'; + +/** + * What builds each world layer on casting.* right now. + * Agent / product SSOT — do not invent parallel builders. + */ +export const TRAINING_ROOM_BUILDERS = Object.freeze({ + mapId: TRAINING_ROOM_MAP_ID, + label: TRAINING_ROOM_LABEL, + host: 'casting.grudge-studio.com · casting-abilities-threejs.vercel.app', + playEntry: 'index.html', + editorEntry: 'devnode.html', + layers: { + L0_height: { + code: 'IslandHeightfield', + path: 'src/world/IslandHeightfield.js', + physics: 'PhysicsWorld.addHeightfield (Rapier)', + sample: 'terrainHandle / IslandHeightfield.sample', + learned: ['snakey-locomotion heightAt', 'three.js physics_rapier_terrain'] + }, + L1_surface: { + code: 'IslandHeightfield.mesh', + path: 'src/world/IslandHeightfield.js', + note: 'Vertex meadow/dirt/shore — Ground.mesh hidden when terrain on', + learned: ['three-stylized grounds'] + }, + L2_vegetation: { + grass: { code: 'StylizedGrassLayer', path: 'src/world/StylizedGrassLayer.js' }, + forest: { code: 'GrowingForest', path: 'src/world/GrowingForest.js' }, + learned: ['three-stylized grass', 'Desktop forestoutline.html', 'snakey trees'] + }, + L3_detail: { + harvest: { code: 'DevIslandHarvest', path: 'src/world/DevIslandHarvest.js' }, + catalog: { code: 'devIslandCatalog', path: 'src/world/devIslandCatalog.js' }, + editor: { code: 'DevNodeEditor + nodePalette', path: 'src/devnode · src/world/nodePalette.js' }, + note: 'Rocks/ore/herbs/dummies/decor — author on /devnode, play on index' + }, + water: { + code: 'StageWater + OpenSeaShells', + path: 'src/world/StageWater.js · OpenSeaShells.js', + note: 'Sibling of land — freeride / windsurf, not L1' + }, + mount: { + code: 'mountTerrainLayers (optional bundle)', + path: 'src/world/terrainLayers.js', + note: 'App still mounts L0–L2 inline; same modules' + } + }, + settingsKey: 'settings.terrain', + worldKey: 'WORLD.islandRadius · shoreBand · waterY' +}); + +/** + * Terrain knobs for the Training Room map (settings.terrain defaults). + * @returns {{ seed: number, amp: number, flatCore: number, segments: number, grid: number }} + */ +export function trainingRoomTerrain() { + const t = settings.terrain || {}; + return { + seed: t.seed ?? 17, + amp: t.amp ?? 0.85, + flatCore: t.flatCore ?? 8, + segments: t.segments ?? 96, + grid: t.grid ?? 65 + }; +} + +/** + * Polar catalog slot → world XZ on island pad. + * @param {{ angle: number, r?: number }} slot + * @param {number} [islandRadius] + */ +export function polarToXZ(slot, islandRadius = WORLD.islandRadius) { + const r = (islandRadius ?? 40) * (slot.r ?? 0.4); + return { + x: Math.cos(slot.angle) * r, + z: Math.sin(slot.angle) * r + }; +} + +/** + * Map harvest defId → nodePalette id when known. + * @param {string} defId + */ +export function harvestDefToPaletteId(defId) { + const map = { + rock_boulder: 'node.rock_boulder', + rock_ore: 'node.rock_ore', + rock_pebbles: 'node.rock_pebbles', + herb_patch: 'node.herb_patch' + }; + return map[defId] || null; +} + +/** + * Palette id → harvest defId. + * @param {string} paletteId + */ +export function paletteIdToHarvestDef(paletteId) { + const e = paletteEntry(paletteId); + if (e?.harvestDefId) return e.harvestDefId; + const def = harvestDefForPalette(paletteId); + return def?.id || null; +} + +/** + * Built-in Training Room layout as DevNode document (cartesian). + * Merges harvest ring + cliff decor + dummy pads into one node list. + */ +export function createTrainingRoomLayout() { + const terrain = trainingRoomTerrain(); + const R = WORLD.islandRadius ?? 51; + /** @type {import('./nodePalette.js').PlacedNode[]} */ + const nodes = []; + let i = 0; + + for (const slot of DEFAULT_HARVEST_LAYOUT) { + const pid = harvestDefToPaletteId(slot.defId); + if (!pid) continue; + const { x, z } = polarToXZ(slot, R); + nodes.push({ + id: `tr_h_${i++}`, + paletteId: pid, + x, + z, + yaw: (slot.angle || 0) + 0.3, + scale: HARVEST_NODE_DEFS[slot.defId]?.scale ?? 1 + }); + } + + for (const slot of DEFAULT_DECOR_LAYOUT) { + const url = DECOR_MESH_POOL[slot.mesh % DECOR_MESH_POOL.length] || ''; + let paletteId = 'node.cliff_wall'; + if (/arch/i.test(url)) paletteId = 'node.cliff_arch'; + else if (/column/i.test(url)) paletteId = 'node.cliff_column'; + const { x, z } = polarToXZ(slot, R); + nodes.push({ + id: `tr_d_${i++}`, + paletteId, + x, + z, + yaw: slot.yaw ?? 0, + scale: slot.scale ?? 1.3 + }); + } + + for (const slot of DEFAULT_DUMMY_LAYOUT) { + const { x, z } = polarToXZ(slot, R); + nodes.push({ + id: `tr_p_${i++}`, + paletteId: 'node.pve_dummy', + x, + z, + yaw: slot.angle + Math.PI, + scale: 1, + label: slot.label + }); + } + + return { + version: 1, + source: 'casting-training-room', + mapId: TRAINING_ROOM_MAP_ID, + mapLabel: TRAINING_ROOM_LABEL, + biomeId: 'temperate_meadow', + terrain: { ...terrain }, + nodes, + createdAt: new Date().toISOString() + }; +} + +/** + * Normalize any layout JSON to Training Room map stamp. + * @param {object} layout + */ +export function stampTrainingRoomLayout(layout) { + const base = createEmptyNodeLayout(layout?.biomeId || 'temperate_meadow'); + return { + ...base, + ...layout, + version: 1, + source: layout?.source || 'casting-training-room', + mapId: TRAINING_ROOM_MAP_ID, + mapLabel: TRAINING_ROOM_LABEL, + terrain: { ...trainingRoomTerrain(), ...(layout?.terrain || {}) }, + nodes: Array.isArray(layout?.nodes) ? layout.nodes : [] + }; +} + +/** + * Load layout from localStorage (new key, then legacy DevNode). + * @returns {object|null} + */ +export function loadTrainingRoomLayoutFromStorage() { + if (typeof localStorage === 'undefined') return null; + for (const key of [TRAINING_ROOM_LAYOUT_KEY, DEVNODE_LAYOUT_LEGACY_KEY]) { + try { + const raw = localStorage.getItem(key); + if (!raw) continue; + const o = JSON.parse(raw); + if (o && Array.isArray(o.nodes)) return stampTrainingRoomLayout(o); + } catch { + /* next */ + } + } + return null; +} + +/** + * Persist layout under canonical + legacy keys so DevNode/play stay synced. + * @param {object} layout + */ +export function saveTrainingRoomLayoutToStorage(layout) { + if (typeof localStorage === 'undefined') return; + const stamped = stampTrainingRoomLayout(layout); + const json = JSON.stringify(stamped); + try { + localStorage.setItem(TRAINING_ROOM_LAYOUT_KEY, json); + localStorage.setItem(DEVNODE_LAYOUT_LEGACY_KEY, json); + } catch { + /* quota */ + } + return stamped; +} + +/** + * True if map id is this island (training room / devisland / devnode). + * @param {string} [id] + */ +export function isTrainingRoomMap(id) { + if (!id) return true; + const s = String(id).toLowerCase(); + return s === TRAINING_ROOM_MAP_ID || TRAINING_ROOM_ALIASES.includes(s); +} diff --git a/vercel.json b/vercel.json new file mode 100644 index 0000000..adfb2f7 --- /dev/null +++ b/vercel.json @@ -0,0 +1,157 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "framework": "vite", + "buildCommand": "npm run build", + "outputDirectory": "dist", + "alias": [ + "casting.grudge.studio", + "casting.grudge-studio.com" + ], + "functions": { + "api/cdn-proxy.js": { + "maxDuration": 30, + "memory": 1024 + } + }, + "rewrites": [ + { + "source": "/api/assets/:path*", + "destination": "/api/cdn-proxy?key=:path*" + }, + { + "source": "/api/open/:path*", + "destination": "https://open.grudge-studio.com/:path*" + }, + { + "source": "/api/objectstore/:path*", + "destination": "https://grudge-objectstore.pages.dev/api/:path*" + }, + { + "source": "/api/info/:path*", + "destination": "https://info.grudge-studio.com/api/:path*" + }, + { + "source": "/api/health", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/health" + }, + { + "source": "/api/colyseus/health", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/colyseus/health" + }, + { + "source": "/api/characters", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/characters" + }, + { + "source": "/api/characters/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/characters/:path*" + }, + { + "source": "/api/account", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/account" + }, + { + "source": "/api/account/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/account/:path*" + }, + { + "source": "/api/professions/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/professions/:path*" + }, + { + "source": "/api/inventory/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/inventory/:path*" + }, + { + "source": "/api/auth/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/auth/:path*" + }, + { + "source": "/api/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/api/:path*" + }, + { + "source": "/matchmake/:path*", + "destination": "https://grudge-api-production-0d46.up.railway.app/matchmake/:path*" + }, + { + "source": "/((?!api/|models/|assets/|icons/|ui/|anims/|hdri/|skills/|maps/|hud/|textures/|devnode|favicon).*)", + "destination": "/index.html" + } + ], + "headers": [ + { + "source": "/index.html", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } + ] + }, + { + "source": "/", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" } + ] + }, + { + "source": "/textures/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=86400" }, + { "key": "Access-Control-Allow-Origin", "value": "*" } + ] + }, + { + "source": "/ui/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=86400" }, + { "key": "Access-Control-Allow-Origin", "value": "*" } + ] + }, + { + "source": "/hud/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=86400" }, + { "key": "Access-Control-Allow-Origin", "value": "*" } + ] + }, + { + "source": "/(.*)", + "headers": [ + { "key": "Cross-Origin-Opener-Policy", "value": "same-origin-allow-popups" } + ] + }, + { + "source": "/models/ride/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=86400, immutable" }, + { "key": "Access-Control-Allow-Origin", "value": "*" } + ] + }, + { + "source": "/models/dev-island/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=86400, immutable" }, + { "key": "Access-Control-Allow-Origin", "value": "*" } + ] + }, + { + "source": "/maps/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=300" }, + { "key": "Access-Control-Allow-Origin", "value": "*" } + ] + }, + { + "source": "/api/assets/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "public, max-age=86400" }, + { "key": "Referrer-Policy", "value": "same-origin" } + ] + }, + { + "source": "/api/(.*)", + "headers": [ + { "key": "Cache-Control", "value": "no-store" } + ] + } + ] +} diff --git a/vite.config.js b/vite.config.js index 7fc69a4..d82308b 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,17 +1,88 @@ import { defineConfig } from 'vite'; +import { resolve } from 'node:path'; +/** + * rapier3d-compat embeds wasm as base64 and calls init(Uint8Array). + * wasm-bindgen wants init({ module_or_path }) — wrap bytes so console stays clean. + * @see node_modules/@dimforge/rapier3d-compat/rapier.mjs function xA + */ +function fixRapierInitDeprecation() { + const needle = + 'Object.getPrototypeOf(I)===Object.prototype?({module_or_path:I}=I):console.warn("using deprecated parameters for the initialization function; pass a single object instead")'; + const replacement = + 'Object.getPrototypeOf(I)===Object.prototype?({module_or_path:I}=I):(I={module_or_path:I},I=I.module_or_path)'; + return { + name: 'fix-rapier-init-deprecation', + enforce: 'pre', + transform(code, id) { + if (!id.includes('@dimforge/rapier3d-compat')) return null; + if (!code.includes('using deprecated parameters for the initialization function')) { + return null; + } + if (!code.includes(needle)) return null; + return { code: code.replaceAll(needle, replacement), map: null }; + } + }; +} + +/** + * Fleet pin: three ^0.185 + Rapier WASM (rapier3d-compat). + * @see grudge-3d-game-packages · grudge-rapier + */ export default defineConfig({ - base: './', + // Absolute base for Vercel root host — relative `./` can break module graph on some redirects + base: '/', + plugins: [fixRapierInitDeprecation()], server: { host: '127.0.0.1', port: 5173, - open: false + open: false, + proxy: { + '/api/assets': { + target: 'https://assets.grudge-studio.com', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/api\/assets/, ''), + configure: (proxy) => { + proxy.on('proxyReq', (proxyReq) => { + proxyReq.setHeader('Referer', 'https://assets.grudge-studio.com/'); + }); + } + }, + '/api/open': { + target: 'https://open.grudge-studio.com', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/api\/open/, '') + }, + '/api/info': { + target: 'https://info.grudge-studio.com', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/api\/info/, '/api') + }, + '/api/objectstore': { + target: 'https://grudge-objectstore.pages.dev', + changeOrigin: true, + rewrite: (p) => p.replace(/^\/api\/objectstore/, '/api') + }, + '/api': { + target: 'https://grudge-api-production-0d46.up.railway.app', + changeOrigin: true + } + } }, build: { target: 'es2022', sourcemap: true, - chunkSizeWarningLimit: 2000 + chunkSizeWarningLimit: 2500, + rollupOptions: { + input: { + main: resolve(__dirname, 'index.html'), + devnode: resolve(__dirname, 'devnode.html') + } + } + }, + optimizeDeps: { + exclude: ['@dimforge/rapier3d-compat'] }, - // Large binary assets (FBX / HDR) live in /public and are served untouched. - assetsInclude: ['**/*.fbx', '**/*.hdr'] + // HDR + ride GLBs in /public; character kits from CDN. + assetsInclude: ['**/*.hdr', '**/*.glb', '**/*.wasm'] }); diff --git a/worker/.wrangler-dist/README.md b/worker/.wrangler-dist/README.md new file mode 100644 index 0000000..6251cbd --- /dev/null +++ b/worker/.wrangler-dist/README.md @@ -0,0 +1 @@ +This folder contains the built output assets for the worker "grudge-weapon-skill-drafts" generated at 2026-08-10T04:36:53.995Z. \ No newline at end of file diff --git a/worker/.wrangler-dist/index.js b/worker/.wrangler-dist/index.js new file mode 100644 index 0000000..8e3e760 --- /dev/null +++ b/worker/.wrangler-dist/index.js @@ -0,0 +1,407 @@ +var __defProp = Object.defineProperty; +var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); + +// src/index.js +var ALLOWED_ORIGINS = [ + "https://casting.grudge.studio", + "https://www.casting.grudge.studio", + "https://casting.grudge-studio.com", + "https://casting-abilities-threejs.vercel.app", + "https://grudge-multiverse.vercel.app", + "https://multiverse.grudge-studio.com", + "https://info.grudge-studio.com", + "http://localhost:5173", + "http://localhost:4173", + "http://127.0.0.1:5173" +]; +function corsHeaders(req) { + const origin = req.headers.get("Origin") || ""; + let host = ""; + try { + host = origin ? new URL(origin).hostname : ""; + } catch { + host = ""; + } + const allow = !origin || ALLOWED_ORIGINS.includes(origin) || host === "casting.grudge.studio" || host.endsWith(".casting.grudge.studio") || host === "grudge.studio" || host.endsWith(".grudge.studio") || host.endsWith(".grudge-studio.com") || host.endsWith(".vercel.app"); + return { + "Access-Control-Allow-Origin": allow ? origin || "*" : ALLOWED_ORIGINS[0], + "Access-Control-Allow-Methods": "GET, PUT, POST, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Grudge-Source", + "Access-Control-Max-Age": "86400", + Vary: "Origin" + }; +} +__name(corsHeaders, "corsHeaders"); +function json(data, status = 200, req) { + return new Response(JSON.stringify(data, null, 2), { + status, + headers: { + "Content-Type": "application/json; charset=utf-8", + ...corsHeaders(req) + } + }); +} +__name(json, "json"); +function stubId() { + return "production"; +} +__name(stubId, "stubId"); +var index_default = { + async fetch(request, env) { + if (request.method === "OPTIONS") { + return new Response(null, { status: 204, headers: corsHeaders(request) }); + } + const url = new URL(request.url); + const path = url.pathname.replace(/\/+$/, "") || "/"; + if (path === "/api/health" || path === "/health") { + return json( + { + ok: true, + service: "grudge-weapon-skill-drafts", + environment: env.ENVIRONMENT || "unknown", + catalogVersion: env.CATALOG_VERSION || "1", + durableObject: "WeaponSkillDrafts", + /** Dev → production control plane (Casting lab) */ + controlPlane: "https://casting.grudge.studio", + publicUrl: "https://weapon-skills.grudge-studio.com", + time: (/* @__PURE__ */ new Date()).toISOString() + }, + 200, + request + ); + } + if (path.startsWith("/api/v1/")) { + const id = env.WEAPON_SKILL_DRAFTS.idFromName(stubId()); + const stub = env.WEAPON_SKILL_DRAFTS.get(id); + return stub.fetch(request); + } + return json( + { + error: "not_found", + routes: [ + "GET /api/health", + "GET|PUT /api/v1/bundle", + "GET /api/v1/skills", + "GET|PUT|DELETE /api/v1/skills/:id", + "GET|PUT /api/v1/equip-catalog", + "POST /api/v1/equip-catalog/weapon", + "POST /api/v1/promote" + ] + }, + 404, + request + ); + } +}; +var WeaponSkillDrafts = class { + static { + __name(this, "WeaponSkillDrafts"); + } + constructor(state, env) { + this.state = state; + this.env = env; + this.storage = state.storage; + } + async fetch(request) { + const url = new URL(request.url); + const path = url.pathname.replace(/\/+$/, "") || "/"; + const method = request.method.toUpperCase(); + try { + if (path === "/api/v1/bundle" && method === "GET") { + return json(await this.getBundle(), 200, request); + } + if (path === "/api/v1/bundle" && method === "PUT") { + const body = await request.json(); + const result = await this.putBundle(body, request); + return json(result, 200, request); + } + if (path === "/api/v1/skills" && method === "GET") { + const skills = await this.storage.get("skills") || {}; + return json( + { + contract: "grudge.weaponSkillList/v1", + count: Object.keys(skills).length, + skills: Object.values(skills) + }, + 200, + request + ); + } + const skillMatch = path.match(/^\/api\/v1\/skills\/([^/]+)$/); + if (skillMatch) { + const id = decodeURIComponent(skillMatch[1]); + if (method === "GET") { + const skills = await this.storage.get("skills") || {}; + const s = skills[id]; + if (!s) return json({ error: "not_found", id }, 404, request); + return json(s, 200, request); + } + if (method === "PUT") { + const body = await request.json(); + const saved = await this.putSkill(id, body); + return json(saved, 200, request); + } + if (method === "DELETE") { + await this.deleteSkill(id); + return json({ ok: true, deleted: id }, 200, request); + } + } + if (path === "/api/v1/equip-catalog" && method === "GET") { + return json(await this.getEquipCatalog(), 200, request); + } + if (path === "/api/v1/equip-catalog" && method === "PUT") { + const body = await request.json(); + const result = await this.putEquipCatalog(body, request); + return json(result, 200, request); + } + if (path === "/api/v1/equip-catalog/weapon" && method === "POST") { + const body = await request.json(); + const result = await this.upsertEquipWeapon(body); + return json(result, 200, request); + } + if (path === "/api/v1/promote" && method === "POST") { + const body = await request.json().catch(() => ({})); + const result = await this.promote(body, request); + return json(result, 200, request); + } + if (path === "/api/v1/meta" && method === "GET") { + const meta = await this.storage.get("meta") || {}; + return json(meta, 200, request); + } + return json({ error: "not_found", path }, 404, request); + } catch (e) { + return json( + { error: "server_error", message: e?.message || String(e) }, + 500, + request + ); + } + } + async getBundle() { + const skills = await this.storage.get("skills") || {}; + const equip = await this.storage.get("equipCatalog") || { weapons: [] }; + const meta = await this.storage.get("meta") || {}; + const list = Object.values(skills); + return { + contract: "grudge.weaponSkillPrefabBundle/v1", + durableObject: { + name: "WeaponSkillDrafts", + keyPrefix: "mv:skill:", + replication: "cloudflare-do", + instance: "production" + }, + count: list.length, + skills: list, + equipCatalog: equip, + meta, + exportedAt: (/* @__PURE__ */ new Date()).toISOString() + }; + } + /** + * Accept Multiverse exportDurableBundle() or partial { skills: [] }. + */ + async putBundle(body, request) { + if (!body || typeof body !== "object") { + throw new Error("body required"); + } + const skillsIn = Array.isArray(body.skills) ? body.skills : body.skills ? Object.values(body.skills) : []; + const map = await this.storage.get("skills") || {}; + let upserted = 0; + for (const s of skillsIn) { + const normalized = normalizeSkill(s); + if (!normalized.id) continue; + map[normalized.id] = normalized; + upserted++; + } + await this.storage.put("skills", map); + if (body.equipCatalog || body.weapons) { + await this.putEquipCatalog(body.equipCatalog || { weapons: body.weapons }, request); + } + const meta = { + ...await this.storage.get("meta") || {}, + lastBundleAt: (/* @__PURE__ */ new Date()).toISOString(), + lastSource: request.headers.get("X-Grudge-Source") || body.surfaces?.[0] || "unknown", + lastContract: body.contract || null, + skillCount: Object.keys(map).length, + environment: this.env.ENVIRONMENT || "development" + }; + await this.storage.put("meta", meta); + return { + ok: true, + upserted, + total: Object.keys(map).length, + meta + }; + } + async putSkill(id, body) { + const map = await this.storage.get("skills") || {}; + const normalized = normalizeSkill({ ...body, id }); + map[id] = normalized; + await this.storage.put("skills", map); + return normalized; + } + async deleteSkill(id) { + const map = await this.storage.get("skills") || {}; + delete map[id]; + await this.storage.put("skills", map); + } + /** + * Mirror Casting ensureWeaponCatalog() / equipWeaponById shape: + * { weapons: EquippableWeapon[], byId: Record } + */ + async getEquipCatalog() { + const stored = await this.storage.get("equipCatalog") || { + weapons: [], + version: 1 + }; + const weapons = stored.weapons || []; + const byId = {}; + for (const w of weapons) { + if (w?.id) byId[w.id] = w; + } + const skills = await this.storage.get("skills") || {}; + for (const s of Object.values(skills)) { + const rt = s.runtime || s; + const wid = s.weaponId || s.identity?.weaponId || null; + if (wid && !byId[wid]) { + byId[wid] = skillToEquippable(s); + weapons.push(byId[wid]); + } + } + return { + contract: "grudge.equipWeaponCatalog/v1", + mirrors: "casting.equipWeaponById", + version: stored.version || 1, + count: weapons.length, + weapons, + byId, + meta: await this.storage.get("meta") || {} + }; + } + async putEquipCatalog(body, request) { + const weapons = Array.isArray(body?.weapons) ? body.weapons : []; + const catalog = { + version: (body?.version || 0) + 1, + weapons: weapons.map(normalizeWeapon), + updatedAt: (/* @__PURE__ */ new Date()).toISOString(), + source: request.headers.get("X-Grudge-Source") || "put" + }; + await this.storage.put("equipCatalog", catalog); + return { ok: true, count: catalog.weapons.length, version: catalog.version }; + } + async upsertEquipWeapon(body) { + const w = normalizeWeapon(body); + if (!w.id) throw new Error("weapon.id required"); + const stored = await this.storage.get("equipCatalog") || { + weapons: [], + version: 1 + }; + const weapons = stored.weapons || []; + const i = weapons.findIndex((x) => x.id === w.id); + if (i >= 0) weapons[i] = { ...weapons[i], ...w }; + else weapons.push(w); + const catalog = { + version: (stored.version || 1) + 1, + weapons, + updatedAt: (/* @__PURE__ */ new Date()).toISOString() + }; + await this.storage.put("equipCatalog", catalog); + return { ok: true, weapon: w, version: catalog.version }; + } + async promote(body, request) { + const meta = { + ...await this.storage.get("meta") || {}, + promotedAt: (/* @__PURE__ */ new Date()).toISOString(), + promotedBy: request.headers.get("X-Grudge-Source") || "casting.grudge.studio", + productionLabel: body?.label || "production", + note: body?.note || null, + controlPlane: "https://casting.grudge.studio", + environment: "production" + }; + await this.storage.put("meta", meta); + const skills = await this.storage.get("skills") || {}; + for (const id of Object.keys(skills)) { + skills[id] = { + ...skills[id], + deploy: { + ...skills[id].deploy || {}, + multiverse: true, + casting: true, + production: true, + promotedAt: meta.promotedAt + } + }; + } + await this.storage.put("skills", skills); + const equip = await this.getEquipCatalog(); + return { + ok: true, + meta, + skillCount: Object.keys(skills).length, + equipCount: equip.count, + message: "Catalog promoted for production equipWeaponById consumers" + }; + } +}; +function normalizeSkill(s) { + if (s?.contract === "grudge.weaponSkillPrefab/v1" || s?.identity) { + const id = s.identity?.id || s.id; + return { + ...s, + id, + name: s.identity?.name || s.name, + updatedAt: s.updatedAt || Date.now() + }; + } + return { + contract: "grudge.weaponSkillPrefab/v1", + id: s.id, + name: s.name || s.id, + ...s, + updatedAt: s.updatedAt || Date.now() + }; +} +__name(normalizeSkill, "normalizeSkill"); +function normalizeWeapon(w) { + if (!w) return {}; + return { + id: w.id, + name: w.name || w.id, + weaponType: w.weaponType || w.category || "SWORD", + animPack: w.animPack || "sword_shield", + meshSlot: w.meshSlot || "sword", + modelUrl: w.modelUrl || w.assets?.modelUrl || null, + icon: w.icon || w.assets?.icon || null, + iconUrl: w.iconUrl || w.assets?.iconUrl || null, + defaultSlot3Id: w.defaultSlot3Id || null, + slot1: w.slot1 || null, + slot2: w.slot2 || null, + slot3Options: w.slot3Options || [], + skills: w.skills || null, + source: w.source || "do", + updatedAt: Date.now() + }; +} +__name(normalizeWeapon, "normalizeWeapon"); +function skillToEquippable(s) { + const rt = s.runtime || {}; + const assets = s.assets || {}; + const id = s.identity?.weaponId || `draft-${s.id || s.identity?.id}`; + return normalizeWeapon({ + id, + name: s.identity?.name || s.name || id, + weaponType: s.identity?.weaponType || s.weaponType || "SWORD", + animPack: rt.animPack || s.animPack || "sword_shield", + meshSlot: rt.meshSlot || s.meshSlot || "sword", + modelUrl: assets.modelUrl || s.assetUrl, + icon: assets.icon || s.icon, + iconUrl: assets.iconUrl, + source: "skill-draft" + }); +} +__name(skillToEquippable, "skillToEquippable"); +export { + WeaponSkillDrafts, + index_default as default +}; +//# sourceMappingURL=index.js.map diff --git a/worker/.wrangler-dist/index.js.map b/worker/.wrangler-dist/index.js.map new file mode 100644 index 0000000..26f958a --- /dev/null +++ b/worker/.wrangler-dist/index.js.map @@ -0,0 +1,8 @@ +{ + "version": 3, + "sources": ["../src/index.js"], + "sourceRoot": ".wrangler-dist", + "sourcesContent": ["/**\n * grudge-weapon-skill-drafts \u2014 Cloudflare Worker + Durable Object\n *\n * Accepts Multiverse weapon skill prefab bundles and mirrors Casting\n * equipWeaponById catalog for production handoff from the Casting lab.\n *\n * Routes (all CORS open for fleet lab hosts):\n * GET /api/health\n * GET /api/v1/bundle\n * PUT /api/v1/bundle body: grudge.weaponSkillPrefabBundle/v1\n * GET /api/v1/skills\n * GET /api/v1/skills/:id\n * PUT /api/v1/skills/:id\n * DELETE /api/v1/skills/:id\n * GET /api/v1/equip-catalog equipWeaponById mirror\n * PUT /api/v1/equip-catalog merge/replace equippable weapons\n * POST /api/v1/equip-catalog/weapon\n * POST /api/v1/promote stamp production deploy metadata\n */\n\n/** Control plane: casting.grudge.studio (dev \u2192 production promote). */\nconst ALLOWED_ORIGINS = [\n \"https://casting.grudge.studio\",\n \"https://www.casting.grudge.studio\",\n \"https://casting.grudge-studio.com\",\n \"https://casting-abilities-threejs.vercel.app\",\n \"https://grudge-multiverse.vercel.app\",\n \"https://multiverse.grudge-studio.com\",\n \"https://info.grudge-studio.com\",\n \"http://localhost:5173\",\n \"http://localhost:4173\",\n \"http://127.0.0.1:5173\",\n];\n\nfunction corsHeaders(req) {\n const origin = req.headers.get(\"Origin\") || \"\";\n let host = \"\";\n try {\n host = origin ? new URL(origin).hostname : \"\";\n } catch {\n host = \"\";\n }\n const allow =\n !origin ||\n ALLOWED_ORIGINS.includes(origin) ||\n host === \"casting.grudge.studio\" ||\n host.endsWith(\".casting.grudge.studio\") ||\n host === \"grudge.studio\" ||\n host.endsWith(\".grudge.studio\") ||\n host.endsWith(\".grudge-studio.com\") ||\n host.endsWith(\".vercel.app\");\n return {\n \"Access-Control-Allow-Origin\": allow ? origin || \"*\" : ALLOWED_ORIGINS[0],\n \"Access-Control-Allow-Methods\": \"GET, PUT, POST, DELETE, OPTIONS\",\n \"Access-Control-Allow-Headers\": \"Content-Type, Authorization, X-Grudge-Source\",\n \"Access-Control-Max-Age\": \"86400\",\n Vary: \"Origin\",\n };\n}\n\nfunction json(data, status = 200, req) {\n return new Response(JSON.stringify(data, null, 2), {\n status,\n headers: {\n \"Content-Type\": \"application/json; charset=utf-8\",\n ...corsHeaders(req),\n },\n });\n}\n\nfunction stubId() {\n // Single production catalog DO instance (global fleet draft store)\n return \"production\";\n}\n\nexport default {\n async fetch(request, env) {\n if (request.method === \"OPTIONS\") {\n return new Response(null, { status: 204, headers: corsHeaders(request) });\n }\n\n const url = new URL(request.url);\n const path = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n\n if (path === \"/api/health\" || path === \"/health\") {\n return json(\n {\n ok: true,\n service: \"grudge-weapon-skill-drafts\",\n environment: env.ENVIRONMENT || \"unknown\",\n catalogVersion: env.CATALOG_VERSION || \"1\",\n durableObject: \"WeaponSkillDrafts\",\n /** Dev \u2192 production control plane (Casting lab) */\n controlPlane: \"https://casting.grudge.studio\",\n publicUrl: \"https://weapon-skills.grudge-studio.com\",\n time: new Date().toISOString(),\n },\n 200,\n request,\n );\n }\n\n // Route all v1 data plane through DO\n if (path.startsWith(\"/api/v1/\")) {\n const id = env.WEAPON_SKILL_DRAFTS.idFromName(stubId());\n const stub = env.WEAPON_SKILL_DRAFTS.get(id);\n return stub.fetch(request);\n }\n\n return json(\n {\n error: \"not_found\",\n routes: [\n \"GET /api/health\",\n \"GET|PUT /api/v1/bundle\",\n \"GET /api/v1/skills\",\n \"GET|PUT|DELETE /api/v1/skills/:id\",\n \"GET|PUT /api/v1/equip-catalog\",\n \"POST /api/v1/equip-catalog/weapon\",\n \"POST /api/v1/promote\",\n ],\n },\n 404,\n request,\n );\n },\n};\n\n/**\n * Durable Object \u2014 SQLite-backed store for skill drafts + equip catalog.\n */\nexport class WeaponSkillDrafts {\n constructor(state, env) {\n this.state = state;\n this.env = env;\n this.storage = state.storage;\n }\n\n async fetch(request) {\n const url = new URL(request.url);\n const path = url.pathname.replace(/\\/+$/, \"\") || \"/\";\n const method = request.method.toUpperCase();\n\n try {\n if (path === \"/api/v1/bundle\" && method === \"GET\") {\n return json(await this.getBundle(), 200, request);\n }\n if (path === \"/api/v1/bundle\" && method === \"PUT\") {\n const body = await request.json();\n const result = await this.putBundle(body, request);\n return json(result, 200, request);\n }\n\n if (path === \"/api/v1/skills\" && method === \"GET\") {\n const skills = (await this.storage.get(\"skills\")) || {};\n return json(\n {\n contract: \"grudge.weaponSkillList/v1\",\n count: Object.keys(skills).length,\n skills: Object.values(skills),\n },\n 200,\n request,\n );\n }\n\n const skillMatch = path.match(/^\\/api\\/v1\\/skills\\/([^/]+)$/);\n if (skillMatch) {\n const id = decodeURIComponent(skillMatch[1]);\n if (method === \"GET\") {\n const skills = (await this.storage.get(\"skills\")) || {};\n const s = skills[id];\n if (!s) return json({ error: \"not_found\", id }, 404, request);\n return json(s, 200, request);\n }\n if (method === \"PUT\") {\n const body = await request.json();\n const saved = await this.putSkill(id, body);\n return json(saved, 200, request);\n }\n if (method === \"DELETE\") {\n await this.deleteSkill(id);\n return json({ ok: true, deleted: id }, 200, request);\n }\n }\n\n if (path === \"/api/v1/equip-catalog\" && method === \"GET\") {\n return json(await this.getEquipCatalog(), 200, request);\n }\n if (path === \"/api/v1/equip-catalog\" && method === \"PUT\") {\n const body = await request.json();\n const result = await this.putEquipCatalog(body, request);\n return json(result, 200, request);\n }\n if (path === \"/api/v1/equip-catalog/weapon\" && method === \"POST\") {\n const body = await request.json();\n const result = await this.upsertEquipWeapon(body);\n return json(result, 200, request);\n }\n\n if (path === \"/api/v1/promote\" && method === \"POST\") {\n const body = await request.json().catch(() => ({}));\n const result = await this.promote(body, request);\n return json(result, 200, request);\n }\n\n if (path === \"/api/v1/meta\" && method === \"GET\") {\n const meta = (await this.storage.get(\"meta\")) || {};\n return json(meta, 200, request);\n }\n\n return json({ error: \"not_found\", path }, 404, request);\n } catch (e) {\n return json(\n { error: \"server_error\", message: e?.message || String(e) },\n 500,\n request,\n );\n }\n }\n\n async getBundle() {\n const skills = (await this.storage.get(\"skills\")) || {};\n const equip = (await this.storage.get(\"equipCatalog\")) || { weapons: [] };\n const meta = (await this.storage.get(\"meta\")) || {};\n const list = Object.values(skills);\n return {\n contract: \"grudge.weaponSkillPrefabBundle/v1\",\n durableObject: {\n name: \"WeaponSkillDrafts\",\n keyPrefix: \"mv:skill:\",\n replication: \"cloudflare-do\",\n instance: \"production\",\n },\n count: list.length,\n skills: list,\n equipCatalog: equip,\n meta,\n exportedAt: new Date().toISOString(),\n };\n }\n\n /**\n * Accept Multiverse exportDurableBundle() or partial { skills: [] }.\n */\n async putBundle(body, request) {\n if (!body || typeof body !== \"object\") {\n throw new Error(\"body required\");\n }\n const skillsIn = Array.isArray(body.skills)\n ? body.skills\n : body.skills\n ? Object.values(body.skills)\n : [];\n const map = (await this.storage.get(\"skills\")) || {};\n let upserted = 0;\n for (const s of skillsIn) {\n const normalized = normalizeSkill(s);\n if (!normalized.id) continue;\n map[normalized.id] = normalized;\n upserted++;\n }\n await this.storage.put(\"skills\", map);\n\n // Optional equip catalog in same push\n if (body.equipCatalog || body.weapons) {\n await this.putEquipCatalog(body.equipCatalog || { weapons: body.weapons }, request);\n }\n\n const meta = {\n ...(await this.storage.get(\"meta\")) || {},\n lastBundleAt: new Date().toISOString(),\n lastSource: request.headers.get(\"X-Grudge-Source\") || body.surfaces?.[0] || \"unknown\",\n lastContract: body.contract || null,\n skillCount: Object.keys(map).length,\n environment: this.env.ENVIRONMENT || \"development\",\n };\n await this.storage.put(\"meta\", meta);\n\n return {\n ok: true,\n upserted,\n total: Object.keys(map).length,\n meta,\n };\n }\n\n async putSkill(id, body) {\n const map = (await this.storage.get(\"skills\")) || {};\n const normalized = normalizeSkill({ ...body, id });\n map[id] = normalized;\n await this.storage.put(\"skills\", map);\n return normalized;\n }\n\n async deleteSkill(id) {\n const map = (await this.storage.get(\"skills\")) || {};\n delete map[id];\n await this.storage.put(\"skills\", map);\n }\n\n /**\n * Mirror Casting ensureWeaponCatalog() / equipWeaponById shape:\n * { weapons: EquippableWeapon[], byId: Record }\n */\n async getEquipCatalog() {\n const stored = (await this.storage.get(\"equipCatalog\")) || {\n weapons: [],\n version: 1,\n };\n const weapons = stored.weapons || [];\n const byId = {};\n for (const w of weapons) {\n if (w?.id) byId[w.id] = w;\n }\n // Merge skill drafts that reference weaponType \u2192 soft equip entries\n const skills = (await this.storage.get(\"skills\")) || {};\n for (const s of Object.values(skills)) {\n const rt = s.runtime || s;\n const wid = s.weaponId || s.identity?.weaponId || null;\n if (wid && !byId[wid]) {\n byId[wid] = skillToEquippable(s);\n weapons.push(byId[wid]);\n }\n }\n return {\n contract: \"grudge.equipWeaponCatalog/v1\",\n mirrors: \"casting.equipWeaponById\",\n version: stored.version || 1,\n count: weapons.length,\n weapons,\n byId,\n meta: (await this.storage.get(\"meta\")) || {},\n };\n }\n\n async putEquipCatalog(body, request) {\n const weapons = Array.isArray(body?.weapons) ? body.weapons : [];\n const catalog = {\n version: (body?.version || 0) + 1,\n weapons: weapons.map(normalizeWeapon),\n updatedAt: new Date().toISOString(),\n source: request.headers.get(\"X-Grudge-Source\") || \"put\",\n };\n await this.storage.put(\"equipCatalog\", catalog);\n return { ok: true, count: catalog.weapons.length, version: catalog.version };\n }\n\n async upsertEquipWeapon(body) {\n const w = normalizeWeapon(body);\n if (!w.id) throw new Error(\"weapon.id required\");\n const stored = (await this.storage.get(\"equipCatalog\")) || {\n weapons: [],\n version: 1,\n };\n const weapons = stored.weapons || [];\n const i = weapons.findIndex((x) => x.id === w.id);\n if (i >= 0) weapons[i] = { ...weapons[i], ...w };\n else weapons.push(w);\n const catalog = {\n version: (stored.version || 1) + 1,\n weapons,\n updatedAt: new Date().toISOString(),\n };\n await this.storage.put(\"equipCatalog\", catalog);\n return { ok: true, weapon: w, version: catalog.version };\n }\n\n async promote(body, request) {\n const meta = {\n ...(await this.storage.get(\"meta\")) || {},\n promotedAt: new Date().toISOString(),\n promotedBy: request.headers.get(\"X-Grudge-Source\") || \"casting.grudge.studio\",\n productionLabel: body?.label || \"production\",\n note: body?.note || null,\n controlPlane: \"https://casting.grudge.studio\",\n environment: \"production\",\n };\n await this.storage.put(\"meta\", meta);\n // Stamp all skills as promoted\n const skills = (await this.storage.get(\"skills\")) || {};\n for (const id of Object.keys(skills)) {\n skills[id] = {\n ...skills[id],\n deploy: {\n ...(skills[id].deploy || {}),\n multiverse: true,\n casting: true,\n production: true,\n promotedAt: meta.promotedAt,\n },\n };\n }\n await this.storage.put(\"skills\", skills);\n const equip = await this.getEquipCatalog();\n return {\n ok: true,\n meta,\n skillCount: Object.keys(skills).length,\n equipCount: equip.count,\n message: \"Catalog promoted for production equipWeaponById consumers\",\n };\n }\n}\n\nfunction normalizeSkill(s) {\n // Accept Multiverse exportSkillPrefab or flat edit\n if (s?.contract === \"grudge.weaponSkillPrefab/v1\" || s?.identity) {\n const id = s.identity?.id || s.id;\n return {\n ...s,\n id,\n name: s.identity?.name || s.name,\n updatedAt: s.updatedAt || Date.now(),\n };\n }\n return {\n contract: \"grudge.weaponSkillPrefab/v1\",\n id: s.id,\n name: s.name || s.id,\n ...s,\n updatedAt: s.updatedAt || Date.now(),\n };\n}\n\nfunction normalizeWeapon(w) {\n if (!w) return {};\n return {\n id: w.id,\n name: w.name || w.id,\n weaponType: w.weaponType || w.category || \"SWORD\",\n animPack: w.animPack || \"sword_shield\",\n meshSlot: w.meshSlot || \"sword\",\n modelUrl: w.modelUrl || w.assets?.modelUrl || null,\n icon: w.icon || w.assets?.icon || null,\n iconUrl: w.iconUrl || w.assets?.iconUrl || null,\n defaultSlot3Id: w.defaultSlot3Id || null,\n slot1: w.slot1 || null,\n slot2: w.slot2 || null,\n slot3Options: w.slot3Options || [],\n skills: w.skills || null,\n source: w.source || \"do\",\n updatedAt: Date.now(),\n };\n}\n\n/** Soft equippable weapon from a skill draft (for catalog completeness). */\nfunction skillToEquippable(s) {\n const rt = s.runtime || {};\n const assets = s.assets || {};\n const id = s.identity?.weaponId || `draft-${s.id || s.identity?.id}`;\n return normalizeWeapon({\n id,\n name: s.identity?.name || s.name || id,\n weaponType: s.identity?.weaponType || s.weaponType || \"SWORD\",\n animPack: rt.animPack || s.animPack || \"sword_shield\",\n meshSlot: rt.meshSlot || s.meshSlot || \"sword\",\n modelUrl: assets.modelUrl || s.assetUrl,\n icon: assets.icon || s.icon,\n iconUrl: assets.iconUrl,\n source: \"skill-draft\",\n });\n}\n"], + "mappings": ";;;;AAqBA,IAAM,kBAAkB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,YAAY,KAAK;AACxB,QAAM,SAAS,IAAI,QAAQ,IAAI,QAAQ,KAAK;AAC5C,MAAI,OAAO;AACX,MAAI;AACF,WAAO,SAAS,IAAI,IAAI,MAAM,EAAE,WAAW;AAAA,EAC7C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QACJ,CAAC,UACD,gBAAgB,SAAS,MAAM,KAC/B,SAAS,2BACT,KAAK,SAAS,wBAAwB,KACtC,SAAS,mBACT,KAAK,SAAS,gBAAgB,KAC9B,KAAK,SAAS,oBAAoB,KAClC,KAAK,SAAS,aAAa;AAC7B,SAAO;AAAA,IACL,+BAA+B,QAAQ,UAAU,MAAM,gBAAgB,CAAC;AAAA,IACxE,gCAAgC;AAAA,IAChC,gCAAgC;AAAA,IAChC,0BAA0B;AAAA,IAC1B,MAAM;AAAA,EACR;AACF;AAxBS;AA0BT,SAAS,KAAK,MAAM,SAAS,KAAK,KAAK;AACrC,SAAO,IAAI,SAAS,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG;AAAA,IACjD;AAAA,IACA,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,GAAG,YAAY,GAAG;AAAA,IACpB;AAAA,EACF,CAAC;AACH;AARS;AAUT,SAAS,SAAS;AAEhB,SAAO;AACT;AAHS;AAKT,IAAO,gBAAQ;AAAA,EACb,MAAM,MAAM,SAAS,KAAK;AACxB,QAAI,QAAQ,WAAW,WAAW;AAChC,aAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,SAAS,YAAY,OAAO,EAAE,CAAC;AAAA,IAC1E;AAEA,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,OAAO,IAAI,SAAS,QAAQ,QAAQ,EAAE,KAAK;AAEjD,QAAI,SAAS,iBAAiB,SAAS,WAAW;AAChD,aAAO;AAAA,QACL;AAAA,UACE,IAAI;AAAA,UACJ,SAAS;AAAA,UACT,aAAa,IAAI,eAAe;AAAA,UAChC,gBAAgB,IAAI,mBAAmB;AAAA,UACvC,eAAe;AAAA;AAAA,UAEf,cAAc;AAAA,UACd,WAAW;AAAA,UACX,OAAM,oBAAI,KAAK,GAAE,YAAY;AAAA,QAC/B;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,WAAW,UAAU,GAAG;AAC/B,YAAM,KAAK,IAAI,oBAAoB,WAAW,OAAO,CAAC;AACtD,YAAM,OAAO,IAAI,oBAAoB,IAAI,EAAE;AAC3C,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B;AAEA,WAAO;AAAA,MACL;AAAA,QACE,OAAO;AAAA,QACP,QAAQ;AAAA,UACN;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAKO,IAAM,oBAAN,MAAwB;AAAA,EAnI/B,OAmI+B;AAAA;AAAA;AAAA,EAC7B,YAAY,OAAO,KAAK;AACtB,SAAK,QAAQ;AACb,SAAK,MAAM;AACX,SAAK,UAAU,MAAM;AAAA,EACvB;AAAA,EAEA,MAAM,MAAM,SAAS;AACnB,UAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;AAC/B,UAAM,OAAO,IAAI,SAAS,QAAQ,QAAQ,EAAE,KAAK;AACjD,UAAM,SAAS,QAAQ,OAAO,YAAY;AAE1C,QAAI;AACF,UAAI,SAAS,oBAAoB,WAAW,OAAO;AACjD,eAAO,KAAK,MAAM,KAAK,UAAU,GAAG,KAAK,OAAO;AAAA,MAClD;AACA,UAAI,SAAS,oBAAoB,WAAW,OAAO;AACjD,cAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,cAAM,SAAS,MAAM,KAAK,UAAU,MAAM,OAAO;AACjD,eAAO,KAAK,QAAQ,KAAK,OAAO;AAAA,MAClC;AAEA,UAAI,SAAS,oBAAoB,WAAW,OAAO;AACjD,cAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACtD,eAAO;AAAA,UACL;AAAA,YACE,UAAU;AAAA,YACV,OAAO,OAAO,KAAK,MAAM,EAAE;AAAA,YAC3B,QAAQ,OAAO,OAAO,MAAM;AAAA,UAC9B;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAEA,YAAM,aAAa,KAAK,MAAM,8BAA8B;AAC5D,UAAI,YAAY;AACd,cAAM,KAAK,mBAAmB,WAAW,CAAC,CAAC;AAC3C,YAAI,WAAW,OAAO;AACpB,gBAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACtD,gBAAM,IAAI,OAAO,EAAE;AACnB,cAAI,CAAC,EAAG,QAAO,KAAK,EAAE,OAAO,aAAa,GAAG,GAAG,KAAK,OAAO;AAC5D,iBAAO,KAAK,GAAG,KAAK,OAAO;AAAA,QAC7B;AACA,YAAI,WAAW,OAAO;AACpB,gBAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,gBAAM,QAAQ,MAAM,KAAK,SAAS,IAAI,IAAI;AAC1C,iBAAO,KAAK,OAAO,KAAK,OAAO;AAAA,QACjC;AACA,YAAI,WAAW,UAAU;AACvB,gBAAM,KAAK,YAAY,EAAE;AACzB,iBAAO,KAAK,EAAE,IAAI,MAAM,SAAS,GAAG,GAAG,KAAK,OAAO;AAAA,QACrD;AAAA,MACF;AAEA,UAAI,SAAS,2BAA2B,WAAW,OAAO;AACxD,eAAO,KAAK,MAAM,KAAK,gBAAgB,GAAG,KAAK,OAAO;AAAA,MACxD;AACA,UAAI,SAAS,2BAA2B,WAAW,OAAO;AACxD,cAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,cAAM,SAAS,MAAM,KAAK,gBAAgB,MAAM,OAAO;AACvD,eAAO,KAAK,QAAQ,KAAK,OAAO;AAAA,MAClC;AACA,UAAI,SAAS,kCAAkC,WAAW,QAAQ;AAChE,cAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,cAAM,SAAS,MAAM,KAAK,kBAAkB,IAAI;AAChD,eAAO,KAAK,QAAQ,KAAK,OAAO;AAAA,MAClC;AAEA,UAAI,SAAS,qBAAqB,WAAW,QAAQ;AACnD,cAAM,OAAO,MAAM,QAAQ,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAClD,cAAM,SAAS,MAAM,KAAK,QAAQ,MAAM,OAAO;AAC/C,eAAO,KAAK,QAAQ,KAAK,OAAO;AAAA,MAClC;AAEA,UAAI,SAAS,kBAAkB,WAAW,OAAO;AAC/C,cAAM,OAAQ,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAM,CAAC;AAClD,eAAO,KAAK,MAAM,KAAK,OAAO;AAAA,MAChC;AAEA,aAAO,KAAK,EAAE,OAAO,aAAa,KAAK,GAAG,KAAK,OAAO;AAAA,IACxD,SAAS,GAAG;AACV,aAAO;AAAA,QACL,EAAE,OAAO,gBAAgB,SAAS,GAAG,WAAW,OAAO,CAAC,EAAE;AAAA,QAC1D;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,YAAY;AAChB,UAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACtD,UAAM,QAAS,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAM,EAAE,SAAS,CAAC,EAAE;AACxE,UAAM,OAAQ,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAM,CAAC;AAClD,UAAM,OAAO,OAAO,OAAO,MAAM;AACjC,WAAO;AAAA,MACL,UAAU;AAAA,MACV,eAAe;AAAA,QACb,MAAM;AAAA,QACN,WAAW;AAAA,QACX,aAAa;AAAA,QACb,UAAU;AAAA,MACZ;AAAA,MACA,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,MACR,cAAc;AAAA,MACd;AAAA,MACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,MAAM,SAAS;AAC7B,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,YAAM,IAAI,MAAM,eAAe;AAAA,IACjC;AACA,UAAM,WAAW,MAAM,QAAQ,KAAK,MAAM,IACtC,KAAK,SACL,KAAK,SACH,OAAO,OAAO,KAAK,MAAM,IACzB,CAAC;AACP,UAAM,MAAO,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACnD,QAAI,WAAW;AACf,eAAW,KAAK,UAAU;AACxB,YAAM,aAAa,eAAe,CAAC;AACnC,UAAI,CAAC,WAAW,GAAI;AACpB,UAAI,WAAW,EAAE,IAAI;AACrB;AAAA,IACF;AACA,UAAM,KAAK,QAAQ,IAAI,UAAU,GAAG;AAGpC,QAAI,KAAK,gBAAgB,KAAK,SAAS;AACrC,YAAM,KAAK,gBAAgB,KAAK,gBAAgB,EAAE,SAAS,KAAK,QAAQ,GAAG,OAAO;AAAA,IACpF;AAEA,UAAM,OAAO;AAAA,MACX,GAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAM,CAAC;AAAA,MACxC,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MACrC,YAAY,QAAQ,QAAQ,IAAI,iBAAiB,KAAK,KAAK,WAAW,CAAC,KAAK;AAAA,MAC5E,cAAc,KAAK,YAAY;AAAA,MAC/B,YAAY,OAAO,KAAK,GAAG,EAAE;AAAA,MAC7B,aAAa,KAAK,IAAI,eAAe;AAAA,IACvC;AACA,UAAM,KAAK,QAAQ,IAAI,QAAQ,IAAI;AAEnC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA,OAAO,OAAO,KAAK,GAAG,EAAE;AAAA,MACxB;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,IAAI,MAAM;AACvB,UAAM,MAAO,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACnD,UAAM,aAAa,eAAe,EAAE,GAAG,MAAM,GAAG,CAAC;AACjD,QAAI,EAAE,IAAI;AACV,UAAM,KAAK,QAAQ,IAAI,UAAU,GAAG;AACpC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,IAAI;AACpB,UAAM,MAAO,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACnD,WAAO,IAAI,EAAE;AACb,UAAM,KAAK,QAAQ,IAAI,UAAU,GAAG;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,kBAAkB;AACtB,UAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAM;AAAA,MACzD,SAAS,CAAC;AAAA,MACV,SAAS;AAAA,IACX;AACA,UAAM,UAAU,OAAO,WAAW,CAAC;AACnC,UAAM,OAAO,CAAC;AACd,eAAW,KAAK,SAAS;AACvB,UAAI,GAAG,GAAI,MAAK,EAAE,EAAE,IAAI;AAAA,IAC1B;AAEA,UAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACtD,eAAW,KAAK,OAAO,OAAO,MAAM,GAAG;AACrC,YAAM,KAAK,EAAE,WAAW;AACxB,YAAM,MAAM,EAAE,YAAY,EAAE,UAAU,YAAY;AAClD,UAAI,OAAO,CAAC,KAAK,GAAG,GAAG;AACrB,aAAK,GAAG,IAAI,kBAAkB,CAAC;AAC/B,gBAAQ,KAAK,KAAK,GAAG,CAAC;AAAA,MACxB;AAAA,IACF;AACA,WAAO;AAAA,MACL,UAAU;AAAA,MACV,SAAS;AAAA,MACT,SAAS,OAAO,WAAW;AAAA,MAC3B,OAAO,QAAQ;AAAA,MACf;AAAA,MACA;AAAA,MACA,MAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAM,CAAC;AAAA,IAC7C;AAAA,EACF;AAAA,EAEA,MAAM,gBAAgB,MAAM,SAAS;AACnC,UAAM,UAAU,MAAM,QAAQ,MAAM,OAAO,IAAI,KAAK,UAAU,CAAC;AAC/D,UAAM,UAAU;AAAA,MACd,UAAU,MAAM,WAAW,KAAK;AAAA,MAChC,SAAS,QAAQ,IAAI,eAAe;AAAA,MACpC,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClC,QAAQ,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAAA,IACpD;AACA,UAAM,KAAK,QAAQ,IAAI,gBAAgB,OAAO;AAC9C,WAAO,EAAE,IAAI,MAAM,OAAO,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,QAAQ;AAAA,EAC7E;AAAA,EAEA,MAAM,kBAAkB,MAAM;AAC5B,UAAM,IAAI,gBAAgB,IAAI;AAC9B,QAAI,CAAC,EAAE,GAAI,OAAM,IAAI,MAAM,oBAAoB;AAC/C,UAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,cAAc,KAAM;AAAA,MACzD,SAAS,CAAC;AAAA,MACV,SAAS;AAAA,IACX;AACA,UAAM,UAAU,OAAO,WAAW,CAAC;AACnC,UAAM,IAAI,QAAQ,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;AAChD,QAAI,KAAK,EAAG,SAAQ,CAAC,IAAI,EAAE,GAAG,QAAQ,CAAC,GAAG,GAAG,EAAE;AAAA,QAC1C,SAAQ,KAAK,CAAC;AACnB,UAAM,UAAU;AAAA,MACd,UAAU,OAAO,WAAW,KAAK;AAAA,MACjC;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AACA,UAAM,KAAK,QAAQ,IAAI,gBAAgB,OAAO;AAC9C,WAAO,EAAE,IAAI,MAAM,QAAQ,GAAG,SAAS,QAAQ,QAAQ;AAAA,EACzD;AAAA,EAEA,MAAM,QAAQ,MAAM,SAAS;AAC3B,UAAM,OAAO;AAAA,MACX,GAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAM,CAAC;AAAA,MACxC,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,MACnC,YAAY,QAAQ,QAAQ,IAAI,iBAAiB,KAAK;AAAA,MACtD,iBAAiB,MAAM,SAAS;AAAA,MAChC,MAAM,MAAM,QAAQ;AAAA,MACpB,cAAc;AAAA,MACd,aAAa;AAAA,IACf;AACA,UAAM,KAAK,QAAQ,IAAI,QAAQ,IAAI;AAEnC,UAAM,SAAU,MAAM,KAAK,QAAQ,IAAI,QAAQ,KAAM,CAAC;AACtD,eAAW,MAAM,OAAO,KAAK,MAAM,GAAG;AACpC,aAAO,EAAE,IAAI;AAAA,QACX,GAAG,OAAO,EAAE;AAAA,QACZ,QAAQ;AAAA,UACN,GAAI,OAAO,EAAE,EAAE,UAAU,CAAC;AAAA,UAC1B,YAAY;AAAA,UACZ,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,YAAY,KAAK;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AACA,UAAM,KAAK,QAAQ,IAAI,UAAU,MAAM;AACvC,UAAM,QAAQ,MAAM,KAAK,gBAAgB;AACzC,WAAO;AAAA,MACL,IAAI;AAAA,MACJ;AAAA,MACA,YAAY,OAAO,KAAK,MAAM,EAAE;AAAA,MAChC,YAAY,MAAM;AAAA,MAClB,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAEA,SAAS,eAAe,GAAG;AAEzB,MAAI,GAAG,aAAa,iCAAiC,GAAG,UAAU;AAChE,UAAM,KAAK,EAAE,UAAU,MAAM,EAAE;AAC/B,WAAO;AAAA,MACL,GAAG;AAAA,MACH;AAAA,MACA,MAAM,EAAE,UAAU,QAAQ,EAAE;AAAA,MAC5B,WAAW,EAAE,aAAa,KAAK,IAAI;AAAA,IACrC;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU;AAAA,IACV,IAAI,EAAE;AAAA,IACN,MAAM,EAAE,QAAQ,EAAE;AAAA,IAClB,GAAG;AAAA,IACH,WAAW,EAAE,aAAa,KAAK,IAAI;AAAA,EACrC;AACF;AAlBS;AAoBT,SAAS,gBAAgB,GAAG;AAC1B,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,SAAO;AAAA,IACL,IAAI,EAAE;AAAA,IACN,MAAM,EAAE,QAAQ,EAAE;AAAA,IAClB,YAAY,EAAE,cAAc,EAAE,YAAY;AAAA,IAC1C,UAAU,EAAE,YAAY;AAAA,IACxB,UAAU,EAAE,YAAY;AAAA,IACxB,UAAU,EAAE,YAAY,EAAE,QAAQ,YAAY;AAAA,IAC9C,MAAM,EAAE,QAAQ,EAAE,QAAQ,QAAQ;AAAA,IAClC,SAAS,EAAE,WAAW,EAAE,QAAQ,WAAW;AAAA,IAC3C,gBAAgB,EAAE,kBAAkB;AAAA,IACpC,OAAO,EAAE,SAAS;AAAA,IAClB,OAAO,EAAE,SAAS;AAAA,IAClB,cAAc,EAAE,gBAAgB,CAAC;AAAA,IACjC,QAAQ,EAAE,UAAU;AAAA,IACpB,QAAQ,EAAE,UAAU;AAAA,IACpB,WAAW,KAAK,IAAI;AAAA,EACtB;AACF;AAnBS;AAsBT,SAAS,kBAAkB,GAAG;AAC5B,QAAM,KAAK,EAAE,WAAW,CAAC;AACzB,QAAM,SAAS,EAAE,UAAU,CAAC;AAC5B,QAAM,KAAK,EAAE,UAAU,YAAY,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE;AAClE,SAAO,gBAAgB;AAAA,IACrB;AAAA,IACA,MAAM,EAAE,UAAU,QAAQ,EAAE,QAAQ;AAAA,IACpC,YAAY,EAAE,UAAU,cAAc,EAAE,cAAc;AAAA,IACtD,UAAU,GAAG,YAAY,EAAE,YAAY;AAAA,IACvC,UAAU,GAAG,YAAY,EAAE,YAAY;AAAA,IACvC,UAAU,OAAO,YAAY,EAAE;AAAA,IAC/B,MAAM,OAAO,QAAQ,EAAE;AAAA,IACvB,SAAS,OAAO;AAAA,IAChB,QAAQ;AAAA,EACV,CAAC;AACH;AAfS;", + "names": [] +} diff --git a/worker/package-lock.json b/worker/package-lock.json new file mode 100644 index 0000000..ee35643 --- /dev/null +++ b/worker/package-lock.json @@ -0,0 +1,1539 @@ +{ + "name": "grudge-weapon-skill-drafts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "grudge-weapon-skill-drafts", + "version": "1.0.0", + "devDependencies": { + "wrangler": "^4.87.0" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz", + "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz", + "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": ">1.20260305.0 <2.0.0-0" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260801.1.tgz", + "integrity": "sha512-wuJWbXpKvncJi1P0GKS+iYpN5tHdb7JPJJ/+6ZQe8zzovHHVMkLJPNBsgWpqeUhpM3g9qTwEKd2rglNKejuh5A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260801.1.tgz", + "integrity": "sha512-kwoZiTpnhNrF3+APx84Q/oAqvJ3sU9yefGagwm/ASaH/2W19x0vghkW/r4qCoHCK0WW7EPugZ+aXjgPMRtlq1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260801.1.tgz", + "integrity": "sha512-r0vAxCZH+Jih9Unm1yoyiByPNWNgawcKciOHDm5Q37ZVGOkKLsT9AtLe3yLSaul76WrKqtf+JP2n0WW32VBLJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260801.1.tgz", + "integrity": "sha512-zWgpdZtSozvIgzQNmQiDSF8yEOQJUkRAWNsDXXzAAoy+fCn8YUoSibj3mpFSbZRvbUldeBEaW6SCdC2VEMkhNQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260801.1.tgz", + "integrity": "sha512-2oQz+Ksu4ji6e/+ZoYX+tWQEcxAii2p7l+iR8kx48W1llMalaufAsmVxTlk+3/vrM7D3/2c0iK448e0UQTcIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", + "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.1" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", + "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", + "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", + "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", + "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", + "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", + "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", + "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", + "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", + "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", + "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", + "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", + "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", + "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", + "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", + "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", + "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", + "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.1" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", + "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", + "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", + "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", + "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", + "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", + "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", + "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", + "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz", + "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz", + "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz", + "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz", + "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/miniflare": { + "version": "5.20260801.1-alpha", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260801.1-alpha.tgz", + "integrity": "sha512-BHPVzIDA6mbx7LefxpvkXW7DHx9FKB9GorZatbnrrFTt3CVMU8zuUpbgyCuebwDKcTTOZos43ta8GQ0eMVEpxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "sharp": "0.35.2", + "undici": "7.29.0", + "workerd": "1.20260801.1", + "ws": "8.21.0", + "youch": "4.1.0-beta.10" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", + "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.4" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.2", + "@img/sharp-darwin-x64": "0.35.2", + "@img/sharp-freebsd-wasm32": "0.35.2", + "@img/sharp-libvips-darwin-arm64": "1.3.1", + "@img/sharp-libvips-darwin-x64": "1.3.1", + "@img/sharp-libvips-linux-arm": "1.3.1", + "@img/sharp-libvips-linux-arm64": "1.3.1", + "@img/sharp-libvips-linux-ppc64": "1.3.1", + "@img/sharp-libvips-linux-riscv64": "1.3.1", + "@img/sharp-libvips-linux-s390x": "1.3.1", + "@img/sharp-libvips-linux-x64": "1.3.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", + "@img/sharp-libvips-linuxmusl-x64": "1.3.1", + "@img/sharp-linux-arm": "0.35.2", + "@img/sharp-linux-arm64": "0.35.2", + "@img/sharp-linux-ppc64": "0.35.2", + "@img/sharp-linux-riscv64": "0.35.2", + "@img/sharp-linux-s390x": "0.35.2", + "@img/sharp-linux-x64": "0.35.2", + "@img/sharp-linuxmusl-arm64": "0.35.2", + "@img/sharp-linuxmusl-x64": "0.35.2", + "@img/sharp-webcontainers-wasm32": "0.35.2", + "@img/sharp-win32-arm64": "0.35.2", + "@img/sharp-win32-ia32": "0.35.2", + "@img/sharp-win32-x64": "0.35.2" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/workerd": { + "version": "1.20260801.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260801.1.tgz", + "integrity": "sha512-/g9JGTyqnHtoIscpBHqKD8swE2V4StBs2i69PmLiOhH45OP95jCFICl4F1hKlgN57rqfni5LiCitttoX5OOkVA==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20260801.1", + "@cloudflare/workerd-darwin-arm64": "1.20260801.1", + "@cloudflare/workerd-linux-64": "1.20260801.1", + "@cloudflare/workerd-linux-arm64": "1.20260801.1", + "@cloudflare/workerd-windows-64": "1.20260801.1" + } + }, + "node_modules/wrangler": { + "version": "4.120.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.120.0.tgz", + "integrity": "sha512-cBmu/MeaB/fPacC0JpATs4duTOCagBxrZo+vBzuTX06tLzwSyAHE1drlHUZ8rP0VqVz1fy3ReGYTiHdKkoHltg==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.5.0", + "@cloudflare/unenv-preset": "2.16.1", + "blake3-wasm": "2.1.5", + "esbuild": "0.28.1", + "miniflare": "5.20260801.1-alpha", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20260801.1" + }, + "bin": { + "cf-wrangler": "bin/cf-wrangler.js", + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "fsevents": "2.3.3" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^5.20260801.1" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + } + } +} diff --git a/worker/package.json b/worker/package.json new file mode 100644 index 0000000..5fcd599 --- /dev/null +++ b/worker/package.json @@ -0,0 +1,15 @@ +{ + "name": "grudge-weapon-skill-drafts", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy", + "deploy:prod": "wrangler deploy --env production", + "types": "wrangler types" + }, + "devDependencies": { + "wrangler": "^4.87.0" + } +} diff --git a/worker/src/index.js b/worker/src/index.js new file mode 100644 index 0000000..beed07a --- /dev/null +++ b/worker/src/index.js @@ -0,0 +1,463 @@ +/** + * grudge-weapon-skill-drafts — Cloudflare Worker + Durable Object + * + * Accepts Multiverse weapon skill prefab bundles and mirrors Casting + * equipWeaponById catalog for production handoff from the Casting lab. + * + * Routes (all CORS open for fleet lab hosts): + * GET /api/health + * GET /api/v1/bundle + * PUT /api/v1/bundle body: grudge.weaponSkillPrefabBundle/v1 + * GET /api/v1/skills + * GET /api/v1/skills/:id + * PUT /api/v1/skills/:id + * DELETE /api/v1/skills/:id + * GET /api/v1/equip-catalog equipWeaponById mirror + * PUT /api/v1/equip-catalog merge/replace equippable weapons + * POST /api/v1/equip-catalog/weapon + * POST /api/v1/promote stamp production deploy metadata + */ + +/** Control plane: casting.grudge.studio (dev → production promote). */ +const ALLOWED_ORIGINS = [ + "https://casting.grudge.studio", + "https://www.casting.grudge.studio", + "https://casting.grudge-studio.com", + "https://casting-abilities-threejs.vercel.app", + "https://grudge-multiverse.vercel.app", + "https://multiverse.grudge-studio.com", + "https://info.grudge-studio.com", + "http://localhost:5173", + "http://localhost:4173", + "http://127.0.0.1:5173", +]; + +function corsHeaders(req) { + const origin = req.headers.get("Origin") || ""; + let host = ""; + try { + host = origin ? new URL(origin).hostname : ""; + } catch { + host = ""; + } + const allow = + !origin || + ALLOWED_ORIGINS.includes(origin) || + host === "casting.grudge.studio" || + host.endsWith(".casting.grudge.studio") || + host === "grudge.studio" || + host.endsWith(".grudge.studio") || + host.endsWith(".grudge-studio.com") || + host.endsWith(".vercel.app"); + return { + "Access-Control-Allow-Origin": allow ? origin || "*" : ALLOWED_ORIGINS[0], + "Access-Control-Allow-Methods": "GET, PUT, POST, DELETE, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Grudge-Source", + "Access-Control-Max-Age": "86400", + Vary: "Origin", + }; +} + +function json(data, status = 200, req) { + return new Response(JSON.stringify(data, null, 2), { + status, + headers: { + "Content-Type": "application/json; charset=utf-8", + ...corsHeaders(req), + }, + }); +} + +function stubId() { + // Single production catalog DO instance (global fleet draft store) + return "production"; +} + +export default { + async fetch(request, env) { + if (request.method === "OPTIONS") { + return new Response(null, { status: 204, headers: corsHeaders(request) }); + } + + const url = new URL(request.url); + const path = url.pathname.replace(/\/+$/, "") || "/"; + + if (path === "/api/health" || path === "/health") { + return json( + { + ok: true, + service: "grudge-weapon-skill-drafts", + environment: env.ENVIRONMENT || "unknown", + catalogVersion: env.CATALOG_VERSION || "1", + durableObject: "WeaponSkillDrafts", + /** Dev → production control plane (Casting lab) */ + controlPlane: "https://casting.grudge.studio", + publicUrl: "https://weapon-skills.grudge-studio.com", + time: new Date().toISOString(), + }, + 200, + request, + ); + } + + // Route all v1 data plane through DO + if (path.startsWith("/api/v1/")) { + const id = env.WEAPON_SKILL_DRAFTS.idFromName(stubId()); + const stub = env.WEAPON_SKILL_DRAFTS.get(id); + return stub.fetch(request); + } + + return json( + { + error: "not_found", + routes: [ + "GET /api/health", + "GET|PUT /api/v1/bundle", + "GET /api/v1/skills", + "GET|PUT|DELETE /api/v1/skills/:id", + "GET|PUT /api/v1/equip-catalog", + "POST /api/v1/equip-catalog/weapon", + "POST /api/v1/promote", + ], + }, + 404, + request, + ); + }, +}; + +/** + * Durable Object — SQLite-backed store for skill drafts + equip catalog. + */ +export class WeaponSkillDrafts { + constructor(state, env) { + this.state = state; + this.env = env; + this.storage = state.storage; + } + + async fetch(request) { + const url = new URL(request.url); + const path = url.pathname.replace(/\/+$/, "") || "/"; + const method = request.method.toUpperCase(); + + try { + if (path === "/api/v1/bundle" && method === "GET") { + return json(await this.getBundle(), 200, request); + } + if (path === "/api/v1/bundle" && method === "PUT") { + const body = await request.json(); + const result = await this.putBundle(body, request); + return json(result, 200, request); + } + + if (path === "/api/v1/skills" && method === "GET") { + const skills = (await this.storage.get("skills")) || {}; + return json( + { + contract: "grudge.weaponSkillList/v1", + count: Object.keys(skills).length, + skills: Object.values(skills), + }, + 200, + request, + ); + } + + const skillMatch = path.match(/^\/api\/v1\/skills\/([^/]+)$/); + if (skillMatch) { + const id = decodeURIComponent(skillMatch[1]); + if (method === "GET") { + const skills = (await this.storage.get("skills")) || {}; + const s = skills[id]; + if (!s) return json({ error: "not_found", id }, 404, request); + return json(s, 200, request); + } + if (method === "PUT") { + const body = await request.json(); + const saved = await this.putSkill(id, body); + return json(saved, 200, request); + } + if (method === "DELETE") { + await this.deleteSkill(id); + return json({ ok: true, deleted: id }, 200, request); + } + } + + if (path === "/api/v1/equip-catalog" && method === "GET") { + return json(await this.getEquipCatalog(), 200, request); + } + if (path === "/api/v1/equip-catalog" && method === "PUT") { + const body = await request.json(); + const result = await this.putEquipCatalog(body, request); + return json(result, 200, request); + } + if (path === "/api/v1/equip-catalog/weapon" && method === "POST") { + const body = await request.json(); + const result = await this.upsertEquipWeapon(body); + return json(result, 200, request); + } + + if (path === "/api/v1/promote" && method === "POST") { + const body = await request.json().catch(() => ({})); + const result = await this.promote(body, request); + return json(result, 200, request); + } + + if (path === "/api/v1/meta" && method === "GET") { + const meta = (await this.storage.get("meta")) || {}; + return json(meta, 200, request); + } + + return json({ error: "not_found", path }, 404, request); + } catch (e) { + return json( + { error: "server_error", message: e?.message || String(e) }, + 500, + request, + ); + } + } + + async getBundle() { + const skills = (await this.storage.get("skills")) || {}; + const equip = (await this.storage.get("equipCatalog")) || { weapons: [] }; + const meta = (await this.storage.get("meta")) || {}; + const list = Object.values(skills); + return { + contract: "grudge.weaponSkillPrefabBundle/v1", + durableObject: { + name: "WeaponSkillDrafts", + keyPrefix: "mv:skill:", + replication: "cloudflare-do", + instance: "production", + }, + count: list.length, + skills: list, + equipCatalog: equip, + meta, + exportedAt: new Date().toISOString(), + }; + } + + /** + * Accept Multiverse exportDurableBundle() or partial { skills: [] }. + */ + async putBundle(body, request) { + if (!body || typeof body !== "object") { + throw new Error("body required"); + } + const skillsIn = Array.isArray(body.skills) + ? body.skills + : body.skills + ? Object.values(body.skills) + : []; + const map = (await this.storage.get("skills")) || {}; + let upserted = 0; + for (const s of skillsIn) { + const normalized = normalizeSkill(s); + if (!normalized.id) continue; + map[normalized.id] = normalized; + upserted++; + } + await this.storage.put("skills", map); + + // Optional equip catalog in same push + if (body.equipCatalog || body.weapons) { + await this.putEquipCatalog(body.equipCatalog || { weapons: body.weapons }, request); + } + + const meta = { + ...(await this.storage.get("meta")) || {}, + lastBundleAt: new Date().toISOString(), + lastSource: request.headers.get("X-Grudge-Source") || body.surfaces?.[0] || "unknown", + lastContract: body.contract || null, + skillCount: Object.keys(map).length, + environment: this.env.ENVIRONMENT || "development", + }; + await this.storage.put("meta", meta); + + return { + ok: true, + upserted, + total: Object.keys(map).length, + meta, + }; + } + + async putSkill(id, body) { + const map = (await this.storage.get("skills")) || {}; + const normalized = normalizeSkill({ ...body, id }); + map[id] = normalized; + await this.storage.put("skills", map); + return normalized; + } + + async deleteSkill(id) { + const map = (await this.storage.get("skills")) || {}; + delete map[id]; + await this.storage.put("skills", map); + } + + /** + * Mirror Casting ensureWeaponCatalog() / equipWeaponById shape: + * { weapons: EquippableWeapon[], byId: Record } + */ + async getEquipCatalog() { + const stored = (await this.storage.get("equipCatalog")) || { + weapons: [], + version: 1, + }; + const weapons = stored.weapons || []; + const byId = {}; + for (const w of weapons) { + if (w?.id) byId[w.id] = w; + } + // Merge skill drafts that reference weaponType → soft equip entries + const skills = (await this.storage.get("skills")) || {}; + for (const s of Object.values(skills)) { + const rt = s.runtime || s; + const wid = s.weaponId || s.identity?.weaponId || null; + if (wid && !byId[wid]) { + byId[wid] = skillToEquippable(s); + weapons.push(byId[wid]); + } + } + return { + contract: "grudge.equipWeaponCatalog/v1", + mirrors: "casting.equipWeaponById", + version: stored.version || 1, + count: weapons.length, + weapons, + byId, + meta: (await this.storage.get("meta")) || {}, + }; + } + + async putEquipCatalog(body, request) { + const weapons = Array.isArray(body?.weapons) ? body.weapons : []; + const catalog = { + version: (body?.version || 0) + 1, + weapons: weapons.map(normalizeWeapon), + updatedAt: new Date().toISOString(), + source: request.headers.get("X-Grudge-Source") || "put", + }; + await this.storage.put("equipCatalog", catalog); + return { ok: true, count: catalog.weapons.length, version: catalog.version }; + } + + async upsertEquipWeapon(body) { + const w = normalizeWeapon(body); + if (!w.id) throw new Error("weapon.id required"); + const stored = (await this.storage.get("equipCatalog")) || { + weapons: [], + version: 1, + }; + const weapons = stored.weapons || []; + const i = weapons.findIndex((x) => x.id === w.id); + if (i >= 0) weapons[i] = { ...weapons[i], ...w }; + else weapons.push(w); + const catalog = { + version: (stored.version || 1) + 1, + weapons, + updatedAt: new Date().toISOString(), + }; + await this.storage.put("equipCatalog", catalog); + return { ok: true, weapon: w, version: catalog.version }; + } + + async promote(body, request) { + const meta = { + ...(await this.storage.get("meta")) || {}, + promotedAt: new Date().toISOString(), + promotedBy: request.headers.get("X-Grudge-Source") || "casting.grudge.studio", + productionLabel: body?.label || "production", + note: body?.note || null, + controlPlane: "https://casting.grudge.studio", + environment: "production", + }; + await this.storage.put("meta", meta); + // Stamp all skills as promoted + const skills = (await this.storage.get("skills")) || {}; + for (const id of Object.keys(skills)) { + skills[id] = { + ...skills[id], + deploy: { + ...(skills[id].deploy || {}), + multiverse: true, + casting: true, + production: true, + promotedAt: meta.promotedAt, + }, + }; + } + await this.storage.put("skills", skills); + const equip = await this.getEquipCatalog(); + return { + ok: true, + meta, + skillCount: Object.keys(skills).length, + equipCount: equip.count, + message: "Catalog promoted for production equipWeaponById consumers", + }; + } +} + +function normalizeSkill(s) { + // Accept Multiverse exportSkillPrefab or flat edit + if (s?.contract === "grudge.weaponSkillPrefab/v1" || s?.identity) { + const id = s.identity?.id || s.id; + return { + ...s, + id, + name: s.identity?.name || s.name, + updatedAt: s.updatedAt || Date.now(), + }; + } + return { + contract: "grudge.weaponSkillPrefab/v1", + id: s.id, + name: s.name || s.id, + ...s, + updatedAt: s.updatedAt || Date.now(), + }; +} + +function normalizeWeapon(w) { + if (!w) return {}; + return { + id: w.id, + name: w.name || w.id, + weaponType: w.weaponType || w.category || "SWORD", + animPack: w.animPack || "sword_shield", + meshSlot: w.meshSlot || "sword", + modelUrl: w.modelUrl || w.assets?.modelUrl || null, + icon: w.icon || w.assets?.icon || null, + iconUrl: w.iconUrl || w.assets?.iconUrl || null, + defaultSlot3Id: w.defaultSlot3Id || null, + slot1: w.slot1 || null, + slot2: w.slot2 || null, + slot3Options: w.slot3Options || [], + skills: w.skills || null, + source: w.source || "do", + updatedAt: Date.now(), + }; +} + +/** Soft equippable weapon from a skill draft (for catalog completeness). */ +function skillToEquippable(s) { + const rt = s.runtime || {}; + const assets = s.assets || {}; + const id = s.identity?.weaponId || `draft-${s.id || s.identity?.id}`; + return normalizeWeapon({ + id, + name: s.identity?.name || s.name || id, + weaponType: s.identity?.weaponType || s.weaponType || "SWORD", + animPack: rt.animPack || s.animPack || "sword_shield", + meshSlot: rt.meshSlot || s.meshSlot || "sword", + modelUrl: assets.modelUrl || s.assetUrl, + icon: assets.icon || s.icon, + iconUrl: assets.iconUrl, + source: "skill-draft", + }); +} diff --git a/worker/wrangler.jsonc b/worker/wrangler.jsonc new file mode 100644 index 0000000..76232ab --- /dev/null +++ b/worker/wrangler.jsonc @@ -0,0 +1,56 @@ +{ + "$schema": "./node_modules/wrangler/config-schema.json", + "name": "grudge-weapon-skill-drafts", + "main": "src/index.js", + "compatibility_date": "2026-07-01", + "compatibility_flags": ["nodejs_compat"], + "workers_dev": true, + "observability": { "enabled": true }, + "vars": { + "ENVIRONMENT": "development", + "CATALOG_VERSION": "1" + }, + "durable_objects": { + "bindings": [ + { + "name": "WEAPON_SKILL_DRAFTS", + "class_name": "WeaponSkillDrafts" + } + ] + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["WeaponSkillDrafts"] + } + ], + "env": { + "production": { + "name": "grudge-weapon-skill-drafts", + "vars": { + "ENVIRONMENT": "production", + "CATALOG_VERSION": "1" + }, + "routes": [ + { + "pattern": "weapon-skills.grudge-studio.com/*", + "zone_name": "grudge-studio.com" + } + ], + "durable_objects": { + "bindings": [ + { + "name": "WEAPON_SKILL_DRAFTS", + "class_name": "WeaponSkillDrafts" + } + ] + }, + "migrations": [ + { + "tag": "v1", + "new_sqlite_classes": ["WeaponSkillDrafts"] + } + ] + } + } +}